Related
When a client installs the app, they have the option to click on the app name in the list of apps on the /admin/apps page.
When they click that page, my PHP index file for my app receives these $_GET vars:
hmac = some_long_alphanumaeric_hmac
locale = en
protocol = https://
shop = example-shop.myshopify.com
timestamp = 1535609063
To verify a webhook from Shopify, I successfully use this:
function verify_webhook($data, $hmac_header, $app_api_secret) {
$calculated_hmac = base64_encode(hash_hmac('sha256', $data, $app_api_secret, true));
return ($hmac_header == $calculated_hmac);
}
// Set vars for Shopify webhook verification
$hmac_header = $_SERVER['HTTP_X_SHOPIFY_HMAC_SHA256'];
$data = file_get_contents('php://input');
$verified = verify_webhook($data, $hmac_header, MY_APP_API_SECRET);
Is it possible to verify an app admin page visit is from a Shopify client that has the app installed?
PS: I've looked through both, the Embedded Apps API (but I can't figure out if that's even the right documentation or if I'm doing something wrong), as well as the GitHub example provided (which has no instructions on how to verify an Embedded App admin page visit).
UPDATE:
I've tried various other ways, discovering some ridiculous problems along the way, but still no luck.
The method I understand should be used to verify a Shopify HMAC is something akin to this:
function verify_hmac($hmac = NULL, $shopify_app_api_secret) {
$params_array = array();
$hmac = $hmac ? $hmac : $_GET['hmac'];
unset($_GET['hmac']);
foreach($_GET as $key => $value){
$key = str_replace("%","%25",$key);
$key = str_replace("&","%26",$key);
$key = str_replace("=","%3D",$key);
$value = str_replace("%","%25",$value);
$value = str_replace("&","%26",$value);
$params_array[] = $key . "=" . $value;
}
$params_string = join('&', $params_array);
$computed_hmac = hash_hmac('sha256', $params_string, $shopify_app_api_secret);
return hash_equals($hmac, $computed_hmac);
}
But the line $params_string = join('&', $params_array); causes an annoying problem by encoding ×tamp as xtamp ... Using http_build_query($params_array) results in the same ridiculous thing. Found others having this same problem here. Basically resolved by encoding the & as &, to arrive at $params_string = join('&', $params_array);.
My final version is like this, but still doesn't work (all the commented code is what else I've tried to no avail):
function verify_hmac($hmac = NULL, $shopify_app_api_secret) {
$params_array = array();
$hmac = $hmac ? $hmac : $_GET['hmac'];
unset($_GET['hmac']);
// unset($_GET['protocol']);
// unset($_GET['locale']);
foreach($_GET as $key => $value){
$key = str_replace("%","%25",$key);
$key = str_replace("&","%26",$key);
$key = str_replace("=","%3D",$key);
$value = str_replace("%","%25",$value);
$value = str_replace("&","%26",$value);
$params_array[] = $key . "=" . $value;
// This commented out method below was an attempt to see if
// the imporperly encoded query param characters were causing issues
/*
if (!isset($params_string) || empty($params_string)) {
$params_string = $key . "=" . $value;
}
else {
$params_string = $params_string . "&" . $key . "=" . $value;
}
*/
}
// $params_string = join('&', $params_array);
// echo $params_string;
// $computed_hmac = base64_encode(hash_hmac('sha256', $params_string, $shopify_app_api_secret, true));
// $computed_hmac = base64_encode(hash_hmac('sha256', $params_string, $shopify_app_api_secret, false));
// $computed_hmac = hash_hmac('sha256', $params_string, $shopify_app_api_secret, false);
// $computed_hmac = hash_hmac('sha256', $params_string, $shopify_app_api_secret, true);
$computed_hmac = hash_hmac('sha256', http_build_query($params_array), $shopify_app_api_secret);
return hash_equals($hmac, $computed_hmac);
}
If you get a hit from Shopify, the first thing you do is check in your persistence layer if you have the shop registered. If you do, and you have a session of some kind setup, you are free to render your App to that shop. If you do not have the shop persisted, you go through the oAuth cycle to get an authentication token to use on the shop, which you persist along with the shop and new session.
For any routes or end points in your shop where you are receiving webhooks, of course those requests have no session, so you use the HMAC security approach to figure out what to do. So your question is clearly straddling two different concepts, each handled differently. The documentation is pretty clear on the differences.
Here is the relevant documentation: https://shopify.dev/tutorials/authenticate-with-oauth#verification. This info by Sandeep was also very helpful too: https://community.shopify.com/c/Shopify-APIs-SDKs/HMAC-verify-app-install-request-using-php/m-p/140097#comment-253000.
Here is what worked for me:
function verify_visiter() // returns true or false
{
// check that timestamp is recent to ensure that this is not a 'replay' of a request that has been intercepted previously (man in the middle attack)
if (!isset($_GET['timestamp'])) return false;
$seconds_in_a_day = 24 * 60 * 60;
$older_than_a_day = $_GET['timestamp'] < (time() - $seconds_in_a_day);
if ($older_than_a_day) return false;
$shared_secret = Your_Shopify_app_shared_secret;
$hmac_header = $_GET['hmac'];
unset($_GET['hmac']);
$data = urldecode(http_build_query($_GET));
$calculated_hmac = hash_hmac('sha256', $data, $shared_secret, false);
return hash_equals($hmac_header, $calculated_hmac);
}
$verified = verify_visiter();
if (!$verified) {
exit('User verification failed.');
}
// ... everything else...
public function authenticateCalls($data = NULL, $bypassTimeCheck = FALSE)
{
$da = array();
foreach($data as $key => $val)
{
$da[$key] = $val;
}
if(isset($da['hmac']))
{
unset($da['hmac']);
}
ksort($da);
// Timestamp check; 1 hour tolerance
if (!$bypassTimeCheck)
{
if (($da['timestamp'] - time() > 3600))
{
return false;
}
}
// HMAC Validation
$queryString = http_build_query($da);
$match = $data['hmac'];
$calculated = hash_hmac('sha256', $queryString, $this->_API['API_SECRET']);
return $calculated === $match;
}
This is my code :
function verifyRequest($request, $secret) {
// Per the Shopify docs:
// Everything except hmac and signature...
$hmac = $request['hmac'];
unset($request['hmac']);
unset($request['signature']);
// Sorted lexilogically...
ksort($request);
// Special characters replaced...
foreach ($request as $k => $val) {
$k = str_replace('%', '%25', $k);
$k = str_replace('&', '%26', $k);
$k = str_replace('=', '%3D', $k);
$val = str_replace('%', '%25', $val);
$val = str_replace('&', '%26', $val);
$params[$k] = $val;
}
echo $http = "protocol=". urldecode("https://").http_build_query( $params) ;
echo $test = hash_hmac("sha256", $http , $secret);
// enter code hereVerified when equal
return $hmac === $test;
}
The hmac from shopi and hmac created from my code is not matching.
What am I doing wrong?
You only need to include the request parameters when creating the list of key-value pairs - don't need "protocol=https://".
https://help.shopify.com/api/getting-started/authentication/oauth#verification
You'll need to urldecode() the result of http_build_query(). It returns a url-encoded query string.
http://php.net/manual/en/function.http-build-query.php
Instead of:
echo $http = "protocol=". urldecode("https://").http_build_query( $params) ;
echo $test = hash_hmac("sha256", $http , $secret);
Something like this:
$http = urldecode(http_build_query($params));
$test = hash_hmac('sha256', $http, $secret);
hmac can be calculated in any programming language using sha256 cryptographic algorithm.
However the doc for hmac verification is provided by shopify but still there is confusion among app developers how to implement it correctly.
Here is the code in php for hmac verification.
Ref. http://code.codify.club
<?php
function verifyHmac()
{
$ar= [];
$hmac = $_GET['hmac'];
unset($_GET['hmac']);
foreach($_GET as $key=>$value){
$key=str_replace("%","%25",$key);
$key=str_replace("&","%26",$key);
$key=str_replace("=","%3D",$key);
$value=str_replace("%","%25",$value);
$value=str_replace("&","%26",$value);
$ar[] = $key."=".$value;
}
$str = join('&',$ar);
$ver_hmac = hash_hmac('sha256',$str,"YOUR-APP-SECRET-KEY",false);
if($ver_hmac==$hmac)
{
echo 'hmac verified';
}
}
?>
Notice for other requests like the App Proxy a HMAC will not be preset, so you'll need to calculate the signature. Here a function that caters for both types of requests including webhooks:
public function authorize(Request $request)
{
if( isset($request['hmac']) || isset($request['signature']) ){
try {
$signature = $request->except(['hmac', 'signature']);
ksort($signature);
foreach ($signature as $k => $val) {
$k = str_replace('%', '%25', $k);
$k = str_replace('&', '%26', $k);
$k = str_replace('=', '%3D', $k);
$val = str_replace('%', '%25', $val);
$val = str_replace('&', '%26', $val);
$signature[$k] = $val;
}
if(isset($request['hmac'])){
$test = hash_hmac('sha256', http_build_query($signature), env('SHOPIFY_API_SECRET'));
if($request->input('hmac') === $test){
return true;
}
} elseif(isset($request['signature'])){
$test = hash_hmac('sha256', str_replace('&', '', urldecode(http_build_query($signature))), env('SHOPIFY_API_SECRET'));
if($request->input('signature') === $test){
return true;
}
}
} catch (Exception $e) {
Bugsnag::notifyException($e);
}
} else { // If webhook
$calculated_hmac = base64_encode(hash_hmac('sha256', $request->getContent(), env('SHOPIFY_API_SECRET'), true));
return hash_equals($request->server('HTTP_X_SHOPIFY_HMAC_SHA256'), $calculated_hmac);
}
return false;
}
The above example uses some Laravel functions, so уоu may want to replace them if you use a different framework.
I've got the following to do this:
// Remove the 'hmac' parameter from the query string
$query_string_rebuilt = removeParamFromQueryString($_SERVER['QUERY_STRING'], 'hmac');
// Check the HMAC
if(!checkHMAC($_GET['hmac'], $query_string_rebuilt, $shopify_api_secret_key)) {
// Error code here
}
/**
* #param string $comparison_data
* #param string $data
* #param string $key
* #param string $algorithm
* #param bool $binary
* #return bool
*/
function checkHMAC($comparison_data, $data, $key, $algorithm = 'sha256', $binary=false) {
// Check the HMAC
$hash_hmac = hash_hmac($algorithm, $data, $key, $binary);
// Return true if there's a match
if($hash_hmac === $comparison_data) {
return true;
}
return false;
}
/**
* #param string $query_string
* #param string $param_to_remove
* #return string
*/
function removeParamFromQueryString(string $query_string, string $param_to_remove) {
parse_str($query_string, $query_string_into_array);
unset($query_string_into_array[$param_to_remove]);
return http_build_query($query_string_into_array);
}
Suddenly the code to get Twitter login URL stopped working.
Error returned by generated URL to get outh_token is:
<error code="32">Could not authenticate you.</error>
URL generated to get outh_token is:
https://api.twitter.com/oauth/request_token?oauth_callback=http://project.local.com/dashboard/redirect-from-twitter&oauth_consumer_key=if5hivS82GJURRJMp60CjXQVd&oauth_nonce=1426244533&oauth_signature=Ag16Q1uwiiAJsLu5Zbl3oy1hil4%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1426244533&oauth_version=1.0
It seems as if something is wrong with signature.
Following is my code to generate signature:
public function buildsignature($method, $params, $url, $access_token_secret = null)
{
$keys = $this->_urlencode_rfc3986(array_keys($params));
$values = $this->_urlencode_rfc3986(array_values($params));
$params = array_combine($keys, $values);
uksort($params, 'strcmp');
// convert params to string
foreach ($params as $k => $v) {
$pairs[] = $this->_urlencode_rfc3986($k) . '=' . $this->_urlencode_rfc3986($v);
}
$concatenatedParams = implode('&', $pairs);
// form base string (first key)
$baseString = $method . "&" . $this->_urlencode_rfc3986($url) . "&" . $this->_urlencode_rfc3986($concatenatedParams);
// form secret (second key)
if ($access_token_secret) {
$key_parts = array(
self::CLIENT_SECRET,
$access_token_secret
);
$key_parts = $this->_urlencode_rfc3986($key_parts);
$secret = implode('&', $key_parts);
//$secret = rawurlencode(self::CLIENT_SECRET) . '&' . rawurlencode($access_token_secret);
} else {
$secret = $this->_urlencode_rfc3986(self::CLIENT_SECRET)."&";
}
return base64_encode(hash_hmac('sha1', $baseString, $secret, TRUE));
}
I tried answers from:
http://stackoverflow.com/questions/9861756/signin-with-twitter-stopped-working-suddenly
But I'm testing it on my local system, so I hope that there isn't due to any time gap. It's not creating the URL to be used for login purpose.
I modified my code and replaced:
$pairs[] = $this->_urlencode_rfc3986($k) . '=' . $this->_urlencode_rfc3986($v);
with
$pairs[] = $k . '=' . $v;
where $this->_urlencode_rfc3986() is
public function _urlencode_rfc3986($input)
{
if (is_array($input)) {
return array_map($this->_urlencode_rfc3986, $input);
}
else if (is_scalar($input)) {
return str_replace('+',' ',str_replace('%7E', '~', rawurlencode($input)));
}
else{
return '';
}
}
Now that's crazy because that previous code was working from last few months, it stopped suddenly. But the question is how could someone find such issues.
I build Android app with billing-in app(version 3). I want verify purchase in my server PHP with openssl_verify().
I neet four values:
$data, $signature, $public_key and $SIGNATURE_ALGORITHM. I found a solution here, but it not understand to me in what form should be $data?
I get responseData in Android app:
'{
"orderId":"12999763169054705758.1371079406387615",
"packageName":"com.example.app",
"productId":"exampleSku",
"purchaseTime":1345678900000,
"purchaseState":0,
"developerPayload":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
"purchaseToken":"rojeslcdyyiapnqcynkjyyjh"
}'
I have signature from app.
How do I need to convert string so I can use it in the function php openssl_verify()?
Thanks.
I use in my project
public function verifySignatureTransaction($signed_data, $signature, $public_key_base64) {
$key = "-----BEGIN PUBLIC KEY-----\n" .
chunk_split($public_key_base64, 64, "\n") .
'-----END PUBLIC KEY-----';
//using PHP to create an RSA key
$key = openssl_pkey_get_public($key);
if ($key === false) {
throw new \InvalidArgumentException("Public key not valid");
}
$signature = base64_decode($signature);
//using PHP's native support to verify the signature
$result = openssl_verify(
$signed_data,
$signature,
$key,
OPENSSL_ALGO_SHA1
);
if (0 === $result) {
return false;
} else {
if (1 !== $result) {
return false;
} else {
return true;
}
}
}
And $signed_data its json string without formatting
I want to add GET parameters to URLs that may and may not contain GET parameters without repeating ? or &.
Example:
If I want to add category=action
$url="http://www.acme.com";
// will add ?category=action at the end
$url="http://www.acme.com/movies?sort=popular";
// will add &category=action at the end
If you notice I'm trying to not repeat the question mark if it's found.
The URL is just a string.
What is a reliable way to append a specific GET parameter?
Basic method
$query = parse_url($url, PHP_URL_QUERY);
// Returns a string if the URL has parameters or NULL if not
if ($query) {
$url .= '&category=1';
} else {
$url .= '?category=1';
}
More advanced
$url = 'http://example.com/search?keyword=test&category=1&tags[]=fun&tags[]=great';
$url_parts = parse_url($url);
// If URL doesn't have a query string.
if (isset($url_parts['query'])) { // Avoid 'Undefined index: query'
parse_str($url_parts['query'], $params);
} else {
$params = array();
}
$params['category'] = 2; // Overwrite if exists
$params['tags'][] = 'cool'; // Allows multiple values
// Note that this will url_encode all values
$url_parts['query'] = http_build_query($params);
// If you have pecl_http
echo http_build_url($url_parts);
// If not
echo $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'] . '?' . $url_parts['query'];
You should put this in a function at least, if not a class.
Here's a shorter version of the accepted answer:
$url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?') . 'category=action';
Edit: as discussed in the accepted answer, this is flawed in that it doesn't check to see if category already exists. A better solution would be to treat the $_GET for what it is - an array - and use functions like in_array().
$data = array('foo'=>'bar',
'baz'=>'boom',
'cow'=>'milk',
'php'=>'hypertext processor');
$queryString = http_build_query($data);
//$queryString = foo=bar&baz=boom&cow=milk&php=hypertext+processor
echo 'http://domain.com?'.$queryString;
//output: http://domain.com?foo=bar&baz=boom&cow=milk&php=hypertext+processor
This function overwrites an existing argument
function addToURL( $key, $value, $url) {
$info = parse_url( $url );
parse_str( $info['query'], $query );
return $info['scheme'] . '://' . $info['host'] . $info['path'] . '?' . http_build_query( $query ? array_merge( $query, array($key => $value ) ) : array( $key => $value ) );
}
Example with updating existent parameters.
Also url_encode used, and possibility to don't specify parameter value
<?
/**
* Add parameter to URL
* #param string $url
* #param string $key
* #param string $value
* #return string result URL
*/
function addToUrl($url, $key, $value = null) {
$query = parse_url($url, PHP_URL_QUERY);
if ($query) {
parse_str($query, $queryParams);
$queryParams[$key] = $value;
$url = str_replace("?$query", '?' . http_build_query($queryParams), $url);
} else {
$url .= '?' . urlencode($key) . '=' . urlencode($value);
}
return $url;
}
Use strpos to detect a ?. Since ? can only appear in the URL at the beginning of a query string, you know if its there get params already exist and you need to add params using &
function addGetParamToUrl(&$url, $varName, $value)
{
// is there already an ?
if (strpos($url, "?"))
{
$url .= "&" . $varName . "=" . $value;
}
else
{
$url .= "?" . $varName . "=" . $value;
}
}
<?php
$url1 = '/test?a=4&b=3';
$url2 = 'www.baidu.com/test?a=4&b=3&try_count=1';
$url3 = 'http://www.baidu.com/test?a=4&b=3&try_count=2';
$url4 = '/test';
function add_or_update_params($url,$key,$value){
$a = parse_url($url);
$query = $a['query'] ? $a['query'] : '';
parse_str($query,$params);
$params[$key] = $value;
$query = http_build_query($params);
$result = '';
if($a['scheme']){
$result .= $a['scheme'] . ':';
}
if($a['host']){
$result .= '//' . $a['host'];
}
if($a['path']){
$result .= $a['path'];
}
if($query){
$result .= '?' . $query;
}
return $result;
}
echo add_or_update_params($url1,'try_count',1);
echo "\n";
echo add_or_update_params($url2,'try_count',2);
echo "\n";
echo add_or_update_params($url3,'try_count',3);
echo "\n";
echo add_or_update_params($url4,'try_count',4);
echo "\n";
/**
* #example addParamToUrl('/path/to/?find=1', array('find' => array('search', 2), 'FILTER' => 'STATUS'))
* #example addParamToUrl('//example.com/path/to/?find=1', array('find' => array('search', 2), 'FILTER' => 'STATUS'))
* #example addParamToUrl('https://example.com/path/to/?find=1&FILTER=Y', array('find' => array('search', 2), 'FILTER' => 'STATUS'))
*
* #param $url string url
* #param array $addParams
*
* #return string
*/
function addParamToUrl($url, array $addParams) {
if (!is_array($addParams)) {
return $url;
}
$info = parse_url($url);
$query = array();
if ($info['query']) {
parse_str($info['query'], $query);
}
if (!is_array($query)) {
$query = array();
}
$params = array_merge($query, $addParams);
$result = '';
if ($info['scheme']) {
$result .= $info['scheme'] . ':';
}
if ($info['host']) {
$result .= '//' . $info['host'];
}
if ($info['path']) {
$result .= $info['path'];
}
if ($params) {
$result .= '?' . http_build_query($params);
}
return $result;
}
$parameters = array();
foreach ($get as $key => $value)
{
$parameters[] = $key.'='.$value;
}
$url = 'http://example.com/movies?'.implode('&', $parameters);
One-liner:
$url .= (strpos($url, '?') ? '&' : '?') . http_build_query($additionalParams);
using http_build_query is recommended because it encodes special characters (for example spaces or # in email addresses)
Improved version for 2022
This allows existing parameters to be replaced, and also preserves existing URL fragment (the part after # at the end of an URL)
function addParametersToUrl(string $url, array $newParams): string
{
$url = parse_url($url);
parse_str($url['query'] ?? '', $existingParams);
$newQuery = array_merge($existingParams, $newParams);
$newUrl = $url['scheme'] . '://' . $url['host'] . ($url['path'] ?? '');
if ($newQuery) {
$newUrl .= '?' . http_build_query($newQuery);
}
if (isset($url['fragment'])) {
$newUrl .= '#' . $url['fragment'];
}
return $newUrl;
}
Testing:
$newParams = [
'newKey' => 'newValue',
'existingKey' => 'new',
];
echo addParametersToUrl('https://www.example.com', $newParams);
// https://www.example.com?newKey=newValue&existingKey=new
echo addParametersToUrl('https://www.example.com/', $newParams);
// https://www.example.com/dir/?newKey=newValue&existingKey=new
echo addParametersToUrl('https://www.example.com/dir/', $newParams);
// https://www.example.com/dir/?newKey=newValue&existingKey=new
echo addParametersToUrl('https://www.example.com/dir/file?foo=bar', $newParams);
// https://www.example.com/dir/file?foo=bar&newKey=newValue&existingKey=new
echo addParametersToUrl('https://www.example.com/dir/file?foo=bar&existingKey=old', $newParams);
// https://www.example.com/dir/file?foo=bar&existingKey=new&newKey=newValue
echo addParametersToUrl('https://www.example.com/dir/file?foo=bar&existingKey=old#hash', $newParams);
// https://www.example.com/dir/file?foo=bar&existingKey=new&newKey=newValue#hash
echo addParametersToUrl('https://www.example.com/dir/file#hash', $newParams);
// https://www.example.com/dir/file?newKey=newValue&existingKey=new#hash
echo addParametersToUrl('https://www.example.com/dir/file?foo=bar#hash', $newParams);
// https://www.example.com/dir/file?foo=bar&newKey=newValue&existingKey=new#hash
I think you should do it something like this.
class myURL {
protected $baseURL, $requestParameters;
public function __construct ($newURL) {
$this->baseurl = $newURL;
$this->requestParameters = array();
}
public function addParameter ($parameter) {
$this->requestParameters[] = $parameter;
}
public function __toString () {
return $this->baseurl.
( count($this->requestParameters) ?
'?'.implode('&', $this->requestParameters) :
''
);
}
}
$url1 = new myURL ('http://www.acme.com');
$url2 = new myURL ('http://www.acme.com');
$url2->addParameter('sort=popular');
$url2->addParameter('category=action');
$url1->addParameter('category=action');
echo $url1."\n".$url2;
After searching for many resources/answers on this topic, I decided to code my own. Based on #TaylorOtwell's answer here, this is how I process incoming $_GET request and modify/manipulate each element.
Assuming the url is: http://domain.com/category/page.php?a=b&x=y
And I want only one parameter for sorting: either ?desc=column_name or ?asc=column_name. This way, single url parameter is enough to sort and order simultaneously. So the URL will be http://domain.com/category/page.php?a=b&x=y&desc=column_name on first click of the associated table header row.
Then I have table row headings that I want to sort DESC on my first click, and ASC on the second click of the same heading. (Each first click should "ORDER BY column DESC" first) And if there is no sorting, it will sort by "date then id" by default.
You may improve it further, like you may add cleaning/filtering functions to each $_GET component but the below structure lays the foundation.
foreach ($_GET AS $KEY => $VALUE){
if ($KEY == 'desc'){
$SORT = $VALUE;
$ORDER = "ORDER BY $VALUE DESC";
$URL_ORDER = $URL_ORDER . "&asc=$VALUE";
} elseif ($KEY == 'asc'){
$SORT = $VALUE;
$ORDER = "ORDER BY $VALUE ASC";
$URL_ORDER = $URL_ORDER . "&desc=$VALUE";
} else {
$URL_ORDER .= "&$KEY=$VALUE";
$URL .= "&$KEY=$VALUE";
}
}
if (!$ORDER){$ORDER = 'ORDER BY date DESC, id DESC';}
if ($URL_ORDER){$URL_ORDER = $_SERVER[SCRIPT_URL] . '?' . trim($URL_ORDER, '&');}
if ($URL){$URL = $_SERVER[SCRIPT_URL] . '?' . trim($URL, '&');}
(You may use $_SERVER[SCRIPT_URI] for full URL beginning with http://domain.com)
Then I use resulting $ORDER I get above, in the MySQL query:
"SELECT * FROM table WHERE limiter = 'any' $ORDER";
Now the function to look at the URL if there is a previous sorting and add sorting (and ordering) parameter to URL with "?" or "&" according to the sequence:
function sort_order ($_SORT){
global $SORT, $URL_ORDER, $URL;
if ($SORT == $_SORT){
return $URL_ORDER;
} else {
if (strpos($URL, '?') !== false){
return "$URL&desc=$_SORT";
} else {
return "$URL?desc=$_SORT";
}
}
}
Finally, the table row header to use the function:
echo "<th><a href='".sort_order('id')."'>ID</a></th>";
Summary: this will read the URL, modify each of the $_GET components and make the final URL with parameters of your choice with the correct form of usage of "?" and "&"
public function addGetParamToUrl($url, $params)
{
foreach ($params as $param) {
if (strpos($url, "?"))
{
$url .= "&" .http_build_query($param);
}
else
{
$url .= "?" .http_build_query($param);
}
}
return $url;
}
another improved function version. Mix of existing answers with small improvements (port support) and bugfixes (checking keys properly).
/**
* #param string $url original url to modify - can be relative, partial etc
* #param array $paramsOverride associative array, can be empty
* #return string modified url
*/
protected function overrideUrlQueryParams($url, $paramsOverride){
if (!is_array($paramsOverride)){
return $url;
}
$url_parts = parse_url($url);
if (isset($url_parts['query'])) {
parse_str($url_parts['query'], $params);
} else {
$params = [];
}
$params = array_merge($params, $paramsOverride);
$res = '';
if(isset($url_parts['scheme'])) {
$res .= $url_parts['scheme'] . ':';
}
if(isset($url_parts['host'])) {
$res .= '//' . $url_parts['host'];
}
if(isset($url_parts['port'])) {
$res .= ':' . $url_parts['port'];
}
if (isset($url_parts['path'])) {
$res .= $url_parts['path'];
}
if (count($params) > 0) {
$res .= '?' . http_build_query($params);
}
return $res;
}
Try this function to add URL parameters.
Then you can disable the link when parameter is set so there is no url parameter duplicate.
<?php
function addQueryString($a)
{
if (empty($_SERVER['QUERY_STRING']))
return '?' . $a;
else if (!empty($_SERVER['QUERY_STRING']))
return '?' . $_SERVER['QUERY_STRING'] . '&' . $a;
}
?>
test
sat
In case you are using WordPress you can simply use
add_query_args(['sort' => 'asc'], 'http:/example.com/?search=news')
Docs https://developer.wordpress.org/reference/functions/add_query_arg/