Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
75.00% covered (warning)
75.00%
6 / 8
CRAP
72.00% covered (warning)
72.00%
36 / 50
Shopify\PublicApp
0.00% covered (danger)
0.00%
0 / 1
75.00% covered (warning)
75.00%
6 / 8
26.92
72.00% covered (warning)
72.00%
36 / 50
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
8 / 8
 setAccessToken
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 getAccessToken
0.00% covered (danger)
0.00%
0 / 1
22.10
23.53% covered (danger)
23.53%
4 / 17
 prepareAuthorizeUrl
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
11 / 11
 setState
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getState
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 validateHmac
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
 validateState
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
<?php
namespace Shopify;
use Shopify\Common\AppInterface;
use Shopify\Exception\ApiException;
/**
 * Class PublicApp
 * @package Shopify
 */
class PublicApp extends Client implements AppInterface
{
    /**
     * define scope for api access
     */
    const SCOPE = 'read_products,read_orders';
    /**
     *
     * @var string
     */
    private $oauth_url = 'https://{shopify_domain}/admin/oauth/';
    /**
     * random unique value for each authorization request
     * @var state
     */
    private $state;
    /**
     * PublicApp constructor.
     * Shopify domain => testshop.myshopify.com
     * @param $shop
     * Shopify api key
     * @param $api_key
     * Shopify api secret key
     * @param $api_secret_key
     * ['version'=>'2020-01']
     * @param array $api_params
     * @throws ApiException
     */
    public function __construct($shop, $api_key, $api_secret_key, array $api_params = [])
    {
        $this->setShop($shop);
        $this->api_key = $api_key;
        $this->api_secret_key = $api_secret_key;
        $this->api_params = $api_params;
        $this->setApiVersion($this->api_params);
        $this->setGraphqlApiUrl($this->graphql_api_url);
        $this->setRestApiUrl($this->rest_api_url);
    }
    /**
     * assign access token for api call
     * @param $access_token
     */
    public function setAccessToken($access_token)
    {
        $this->access_token =  $access_token;
        $this->setRestApiHeaders($this->access_token);
        $this->setGraphqlApiHeaders($this->access_token);
    }
    /**
     * Once the User has authorized the app, call to get the access token
     * @param $get_params
     * @return mixed
     * @throws ApiException
     * @throws ClientException
     */
    public function getAccessToken($get_params)
    {
        if(isset($get_params['code'],$get_params['hmac']))
        {
            if(isset($get_params['state']) && !$this->validateState($get_params))
                throw new ApiException("Previous state value('".$this->getState()."') doesn't match with current value('".$get_params['state']."')",0);
            if(!$this->validateHmac($get_params,$get_params['hmac']))
                throw new ApiException("Hmac validation failed",0);
            $access_token_url = $this->oauth_url.'access_token';
            $access_token_url = strtr($access_token_url,['{shopify_domain}'=> $this->shop]);
            $params['client_id'] = $this->api_key;
            $params['client_secret'] = $this->api_secret_key;
            $params['code'] = $get_params['code'];
            $http_response = $this->request('POST', $access_token_url, ['query'=>$params]);
            $response = \GuzzleHttp\json_decode($http_response->getBody()->getContents(),true);
            if(isset($response['access_token']))
            {
                $this->setAccessToken($response['access_token']);
                return $response['access_token'];
            }
        }
        else {
            throw new ApiException('Unable to authorise app, check your credentials',0);
        }
    }
    /**
     * prepare url to authorize public app with Oauth for given shop domain
     * @param $scope
     * @param null $redirect_url
     * @param $state
     * @return false|string
     */
    public function prepareAuthorizeUrl($redirect_url='', $scope='', $state=false)
    {
        /*&redirect_uri={redirect_uri}&state={nonce}*/
        $authorise_url = $this->oauth_url.'authorize?client_id={api_key}&scope={scopes}';
        $authorise_url = strtr($authorise_url, [
                '{shopify_domain}'=> $this->shop,
                '{api_key}'=> $this->api_key,
                '{scopes}'=> !empty($scope)?$scope:self::SCOPE
            ]
        );
        if($redirect_url){
            $authorise_url.='&redirect_uri='.$redirect_url;
        }
        if($state){
            $this->setState($state);
            $authorise_url.='&state='.$this->getState();
        }
        return $authorise_url;
    }
    /**
     * set random unique value for authorization request
     * @param $state
     */
    public function setState($state)
    {
        $this->state = $state;
    }
    /**
     * get random unique value for authorization request
     * @return string
     */
    public function getState()
    {
        return $this->state;
    }
    /**
     * HMAC verification procedure for OAuth/webhooks
     * @param $data
     * @param $hmac
     * @return bool
     */
    public function validateHmac($data, $hmac)
    {
        if(isset($data['hmac'])) {
            unset($data['hmac']);
            array_values($data);
        }
        return ($hmac === hash_hmac('sha256', is_array($data) ? http_build_query($data) : $data, $this->api_secret_key));
    }
    /**
     * check random value same with previous value set for authorization request
     * @param $state
     * @return bool
     */
    public function validateState($params)
    {
        if($params['state'] === $this->getState())
            return true;
        return false;
    }
}