Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ composer.lock
# PHPUnit Files
tests/phpunit.xml

/lib/Examples/settings.json
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The canoncial repository for this stream of development is

This API Client is still in a pre-1.0 state, so you can expect:
* some bugs (feel free to submit a pull request with bug fixes and test coverage)
* possibly some breaking API changes between v0.9 and v1.0
* possibly some breaking API changes between v0.10 and v1.0

## Requirements

Expand All @@ -29,7 +29,7 @@ root directory and require shopify-php:

{
"require": {
"offshoot/shopify-php": "0.9.x"
"offshoot/shopify-php": "0.10.x"
}
}

Expand All @@ -47,7 +47,7 @@ might look something like this:

{
"require": {
"offshoot/shopify-php": "0.9.x",
"offshoot/shopify-php": "0.10.x",
"haxx-se/curl": "1.0.0"
},
"repositories": [
Expand Down
9 changes: 5 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"name": "cadicvnn/shopify-php",
"name": "cdyweb/shopify-php",
"type": "library",
"description": "Simple Shopify API client in PHP",
"keywords": ["shopify","api"],
"homepage": "https://github.com/cadicvnn/shopify-php",
"homepage": "https://github.com/cdyweb/shopify-php",
"license": "MIT",
"authors": [
{
"name": "Cadic",
"role": "Developer"
"name": "Erwin Kooi",
"role": "Developer",
"homepage": "http://cdyweb.com"
},
{
"name": "Chris Woodford",
Expand Down
9 changes: 9 additions & 0 deletions lib/Examples/autoload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

spl_autoload_register(function ($class) {
if (
file_exists($fn = __DIR__.'/../'.str_replace('\\','/',$class).'.php')
) {
require $fn;
}
});
33 changes: 33 additions & 0 deletions lib/Examples/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

include 'autoload.php';

use \Shopify\Api\Client;
use \Shopify\Api\AuthenticationGateway;
use \Shopify\HttpClient\CurlHttpClient;
use \Shopify\Redirector\HeaderRedirector;

$settings=json_decode(file_get_contents('settings.json'));

if (!empty($settings->shopName) && !empty($_GET['code'])) {
Client::doValidateSignature($settings->clientSecret, $_GET) or die('Signature validation failed');
$auth = new AuthenticationGateway(new CurlHttpClient(null, false, false), new HeaderRedirector());
$token = $auth->forShopName($settings->shopName)
->usingClientId($settings->clientId)
->usingClientSecret($settings->clientSecret)
->toExchange($_GET['code']);
if ($token) {
$settings->accessToken = $token;
file_put_contents('settings.json', json_encode($settings, JSON_PRETTY_PRINT));
HeaderRedirector::go($settings->redirectUri);
} else {
die('toExchange failed');
}
}

if (empty($settings->accessToken)) die('not authenticated, use install.php first');

$client = new Client(new CurlHttpClient(null, false, false), $settings);
$result = $client->get('/admin/pages.json');
var_dump($result);

23 changes: 23 additions & 0 deletions lib/Examples/install.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

if (empty($_GET['shopName'])) die('shopName not provided');

include 'autoload.php';

use \Shopify\Api\AuthenticationGateway;
use \Shopify\HttpClient\CurlHttpClient;
use \Shopify\Redirector\HeaderRedirector;

if (!file_exists('settings.json')) copy('settings.json.dist','settings.json');
$settings=json_decode(file_get_contents('settings.json'));
$settings->shopName = $_GET['shopName'];
$settings->redirectUri = "http://{$_SERVER['HTTP_HOST']}".dirname($_SERVER['REQUEST_URI']).'/';

file_put_contents('settings.json', json_encode($settings, JSON_PRETTY_PRINT));

$auth = new AuthenticationGateway(new CurlHttpClient(null, false, false), new HeaderRedirector());
$auth->forShopName($settings->shopName)
->usingClientId($settings->clientId)
->withScope($settings->permissions)
->andReturningTo($settings->redirectUri)
->initiateLogin();
22 changes: 22 additions & 0 deletions lib/Examples/settings.json.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"clientId": "YOUR APP API KEY",
"clientSecret": "YOUR APP API SECRET",
"permissions": [
"read_content",
"write_content",
"read_themes",
"write_themes",
"read_products",
"write_products",
"read_customers",
"write_customers",
"read_orders",
"write_orders",
"read_script_tags",
"write_script_tags",
"read_fulfillments",
"write_fulfillments",
"read_shipping",
"write_shipping"
]
}
9 changes: 5 additions & 4 deletions lib/Shopify/Api/AuthenticationGateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,17 @@ public function toExchange($temporaryToken)
'code' => $temporaryToken,
);

$response = json_decode($this->httpClient->post(
$response = $this->httpClient->post(
$this->getAccessUri(),
$request
));
);

$response_obj = json_decode($response);
if (isset($response->error)) {
throw new \RuntimeException($response->error);
throw new \RuntimeException($response_obj->error);
}

return isset($response->access_token) ? $response->access_token : null;
return isset($response_obj->access_token) ? $response_obj->access_token : null;

}

Expand Down
120 changes: 63 additions & 57 deletions lib/Shopify/Api/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Client
* the shared secret created by Shopify
* @var string
*/
protected $sharedSecret;
protected $clientSecret;

/**
* the http client used to make requests to the shopify api
Expand All @@ -34,10 +34,14 @@ class Client
/**
* initialize the API client
* @param HttpClient $client
* @param $options
*/
public function __construct(HttpClient $client)
public function __construct(HttpClient $client, $options=null)
{
$this->httpClient = $client;
if (is_object($options)) foreach (get_object_vars($this) as $key=>$value) {
if (isset($options->$key)) $this->$key = $options->$key;
}
}

/**
Expand All @@ -64,7 +68,7 @@ public function setAccessToken($token)
*/
public function setClientSecret($secret)
{
$this->sharedSecret = $secret;
$this->clientSecret = $secret;
}

/**
Expand Down Expand Up @@ -97,77 +101,78 @@ public function post($resource, array $data = array())
{
return $this->makeApiRequest($resource, $data, HttpClient::POST);
}

/**
* make a PUT request to the Shopify API
* @param string $resource
* @param array $data
* @return \stdClass
*/
public function put($resource, array $data = array())
{
return $this->makeApiRequest($resource, $data, HttpClient::PUT);
}


/**
* make a DELETE request to the Shopify API
* @param string $resource
* @param array $data
* @return \stdClass
* generate the signature as required by shopify
* @param array $request
* @see https://docs.shopify.com/api/authentication/oauth#confirming-installation
* @return string
*/
public function delete($resource, array $data = array())
public function generateSignature(array $request)
{
return $this->makeApiRequest($resource, $data, HttpClient::DELETE);
return self::doGenerateSignature($this->getClientSecret(), $request);
}

/**
* generate the signature as required by shopify
* @param array $params
* @param $secret
* @param array $request
* @param string $implode_with (the proxy validation uses no separator)
* @return string
*/
public function generateSignature(array $params)
public static function doGenerateSignature($clientSecret, array $request, $implode_with='&')
{
$params = $request;

// Collect the URL parameters into an array of elements of the format
// "$parameter_name=$parameter_value"

$calculated = array();

foreach ($params as $key => $value) {
$calculated[] = $key . "=" . $value;
}
// The signature and hmac entries are removed from the map, leaving the
// remaining parameters.
unset($params['signature']);
unset($params['hmac']);

// Sort the key/value pairs in the array
sort($calculated);
// Each key is concatenated with its value, seperated by an = character,
// to create a list of strings
$collected = array_map(function($key, $value) {
return $key . "=" . $value;
}, array_keys($params), $params);

// Join the array elements into a string
$calculated = implode('', $calculated);
// The list of key-value pairs is sorted lexicographically
sort($collected);

// Final calculated_signature to compare against
return md5($this->getClientSecret() . $calculated);
// and concatenated together with & to create a single string
$collected = implode($implode_with, $collected);

// this string processed through an HMAC-SHA256 using the Shared Secret
// as the key
return hash_hmac('sha256', $collected, $clientSecret);
}

/**
* validate the signature on the supplied query parameters
* @param array $request
* @return boolean
*/
public function validateSignature(array $params)
public function validateSignature(array $request)
{

$this->assertRequestParamIsNotNull(
$params, 'signature', 'Expected signature in query params'
$request, 'hmac', 'Expected signature in query params'
);
return self::doValidateSignature($this->getClientSecret(), $request);
}

$signature = $params['signature'];
unset($params['signature']);

return $this->generateSignature($params) === $signature;

/**
* @param string $secret
* @param array $request
* @return bool
* @throws RequestException
*/
public static function doValidateSignature($clientSecret, array $request)
{
$hmac = $request['hmac'];
return self::doGenerateSignature($clientSecret, $request) === $hmac;
}

/**
* returns true if the supplied request params are valid
* @param array $params
* @return boolean
*/
public function isValidRequest(array $params)
Expand All @@ -187,6 +192,7 @@ public function isValidRequest(array $params)

/**
* get the number of calls made to the shopify api
* @param array $headers
* @return integer
*/
public function getNumberOfCallsMade(array $headers)
Expand All @@ -196,6 +202,7 @@ public function getNumberOfCallsMade(array $headers)

/**
* get the total number of calls that can be made to the shopify api
* @param array $headers
* @return integer
*/
public function getCallLimit(array $headers)
Expand Down Expand Up @@ -256,22 +263,21 @@ protected function makeApiRequest(
$data = json_encode($params);
$response = $this->getHttpClient()->post($uri, $data);
break;
case HttpClient::PUT:
$data = json_encode($params);
$response = $this->getHttpClient()->put($uri, $data);
break;
case HttpClient::DELETE:
$data = json_encode($params);
$response = $this->getHttpClient()->delete($uri, $data);
break;
case 'PUT':
case 'DELETE':
default:
throw new \RuntimeException(
'Currently only "GET", "POST", "PUT" and "DELETE" are supported'
'Currently only "GET" and "POST" are supported. "PUT" and '
. '"DELETE" functionality is currently under development'
);
}

$response = json_decode($response);

if (isset($response->errors)) {
throw new \RuntimeException($response->errors);
}

return $response;

}
Expand All @@ -280,7 +286,7 @@ protected function makeApiRequest(
* get the HTTP Client
* @return HttpClient
*/
public function getHttpClient()
protected function getHttpClient()
{
return $this->httpClient;
}
Expand Down Expand Up @@ -309,7 +315,7 @@ protected function getShopName()
*/
protected function getClientSecret()
{
return $this->sharedSecret;
return $this->clientSecret;
}

/**
Expand Down
Loading