@@ -141,18 +141,14 @@ class ShopifyClient
141141 */
142142 public function __construct ($ config )
143143 {
144- //Remove https:// and trailing slash (if provided)
145- $ config ['ShopUrl ' ] = preg_replace ('#^https?://|/$# ' , '' ,$ config ['ShopUrl ' ]);
146144
147- if (isset ($ config ['ApiKey ' ]) && isset ($ config ['Password ' ])) {
148- $ apiKey = $ config ['ApiKey ' ];
149- $ password = $ config ['Password ' ];
150145
151- $ config ['ApiUrl ' ] = "https:// $ apiKey: $ password@ " . $ config ['ShopUrl ' ] . '/admin/ ' ;
146+ if (isset ($ config ['ApiKey ' ]) && isset ($ config ['Password ' ])) {
147+ $ config ['ApiUrl ' ] = ShopifyClient::getAdminUrl ($ config ['ShopUrl ' ], $ config ['ApiKey ' ], $ config ['Password ' ]);
152148 } elseif (!isset ($ config ['AccessToken ' ])) {
153149 throw new SdkException ("Either AccessToken or ApiKey+Password Combination must be provided! " );
154150 } else {
155- $ config ['ApiUrl ' ] = ' https:// ' . $ config ['ShopUrl ' ] . ' /admin/ ' ;
151+ $ config ['ApiUrl ' ] = ShopifyClient:: getAdminUrl ( $ config ['ShopUrl ' ]) ;
156152 }
157153
158154 $ this ->config = $ config ;
@@ -180,7 +176,7 @@ public function __get($resourceName)
180176 * @param string $resourceName
181177 * @param array $arguments
182178 *
183- * @throws SdkException if the $name is not a valid resource.
179+ * @throws SdkException if the $name is not a valid ShopifyAPI resource.
184180 *
185181 * @return ShopifyAPI
186182 */
@@ -205,4 +201,134 @@ public function __call($resourceName, $arguments)
205201
206202 return $ resource ;
207203 }
204+
205+ /**
206+ * Return the admin url, based on a given shop url
207+ *
208+ * @param string $shopUrl
209+ * @param string $apiKey
210+ * @param string $apiPassword
211+ * @return string
212+ */
213+ public static function getAdminUrl ($ shopUrl , $ apiKey = null , $ apiPassword = null )
214+ {
215+ //Remove https:// and trailing slash (if provided)
216+ $ shopUrl = preg_replace ('#^https?://|/$# ' , '' , $ shopUrl );
217+
218+ if ($ apiKey && $ apiPassword ) {
219+ $ adminUrl = "https:// $ apiKey: $ apiPassword@ $ shopUrl/admin/ " ;
220+ } else {
221+ $ adminUrl = "https:// $ shopUrl/admin/ " ;
222+ }
223+ return $ adminUrl ;
224+ }
225+
226+ /**
227+ * Verify if the request is made from shopify using hmac hash value
228+ *
229+ * @throws SdkException if hmac is not found in the url parameters
230+ *
231+ * @param string $sharedSecret Shared Secret of the Shopify App
232+ *
233+ * @return bool
234+ */
235+ public static function verifyShopifyRequest ($ sharedSecret )
236+ {
237+ $ data = $ _GET ;
238+ //Get the hmac and remove it from array
239+ if (isset ($ data ['hmac ' ])) {
240+ $ hmac = $ data ['hmac ' ];
241+ unset($ data ['hmac ' ]);
242+ } else {
243+ throw new SdkException ("HMAC value not found in url parameters. " );
244+ }
245+ //signature validation is deprecated
246+ if (isset ($ data ['signature ' ])) {
247+ unset($ data ['signature ' ]);
248+ }
249+ //Create data string for the remaining url parameters
250+ $ dataString = http_build_query ($ data );
251+
252+ $ realHmac = hash_hmac ('sha256 ' , $ dataString , $ sharedSecret );
253+
254+ //hash the values before comparing (to prevent time attack)
255+ if (md5 ($ realHmac ) === md5 ($ hmac )) {
256+ return true ;
257+ } else {
258+ return false ;
259+ }
260+ }
261+
262+ /**
263+ * Redirect the user to the authorization page to allow the app access to the shop
264+ *
265+ * @see https://help.shopify.com/api/guides/authentication/oauth#scopes For allowed scopes
266+ *
267+ * @param array $config
268+ * @param string|string[] $scopes Scopes required by app
269+ * @param string $redirectUrl
270+ *
271+ * @return void
272+ */
273+ public static function createAuthRequest ($ config , $ scopes , $ redirectUrl = null )
274+ {
275+ if (!$ redirectUrl ) {
276+ //If redirect url is the same as this url, then need to check for access token when redirected back from shopify
277+ if (isset ($ _GET ['code ' ])) {
278+ return self ::getAccessToken ($ config );
279+ } else {
280+ $ redirectUrl = self ::getCurrentUrl ();
281+ }
282+ }
283+
284+ if (is_array ($ scopes )) {
285+ $ scopes = join (', ' , $ scopes );
286+ }
287+ $ authUrl = self ::getAdminUrl ($ config ['ShopUrl ' ]) . 'oauth/authorize?client_id= ' . $ config ['ApiKey ' ] . '&redirect_uri= ' . $ redirectUrl . "&scope= $ scopes " ;
288+
289+ header ("Location: $ authUrl " );
290+ }
291+
292+ /**
293+ * Get Access token for the API
294+ * Call this when being redirected from shopify page ( to the $redirectUrl) after authentication
295+ *
296+ * @param array $config
297+ *
298+ * @return string
299+ */
300+ public static function getAccessToken ($ config )
301+ {
302+ if (self ::verifyShopifyRequest ($ config ['SharedSecret ' ])) {
303+ $ data = array (
304+ 'client_id ' => $ config ['ApiKey ' ],
305+ 'client_secret ' => $ config ['SharedSecret ' ],
306+ 'code ' => $ _GET ['code ' ],
307+ );
308+
309+ $ response = HttpRequestJson::post (self ::getAdminUrl ($ config ['ShopUrl ' ]) . 'oauth/access_token ' , $ data );
310+
311+ return isset ($ response ['access_token ' ]) ? $ response ['access_token ' ] : null ;
312+ }
313+ }
314+
315+ /**
316+ * Get the url of the current page
317+ *
318+ * @return string
319+ */
320+ public static function getCurrentUrl ()
321+ {
322+ if (isset ($ _SERVER ['HTTPS ' ]) &&
323+ ($ _SERVER ['HTTPS ' ] == 'on ' || $ _SERVER ['HTTPS ' ] == 1 ) ||
324+ isset ($ _SERVER ['HTTP_X_FORWARDED_PROTO ' ]) &&
325+ $ _SERVER ['HTTP_X_FORWARDED_PROTO ' ] == 'https ' ) {
326+ $ protocol = 'https ' ;
327+ }
328+ else {
329+ $ protocol = 'http ' ;
330+ }
331+
332+ return "$ protocol:// $ _SERVER [HTTP_HOST ]$ _SERVER [REQUEST_URI ]" ;
333+ }
208334}
0 commit comments