Skip to content
This repository has been archived by the owner on Aug 29, 2019. It is now read-only.

Commit

Permalink
Merge pull request #65 from geoffp-aweber/master
Browse files Browse the repository at this point in the history
Fixes nested objects in request
  • Loading branch information
aremm authored Sep 13, 2017
2 parents 8d85b61 + 0696879 commit 2ee27a5
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 70 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: php

sudo: required
php:
- 5.3
- 5.4

script: phpunit --configuration build/phpunit.xml
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ subscriber information.

Changelog:
----------
2017-09-02: v1.1.18
* Fixing issues on the formatting of nested objects on 'application/json' requests
* Fixing issue with extra data being sent in header.
* Fixing issue to allow the creation of custom fields to be sent as form encoded.

2017-08-21: v1.1.17
* Fixing UTF-8 issues on creates

Expand Down
3 changes: 2 additions & 1 deletion aweber_api/aweber_collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ protected function _type() {
public function create($kv_pairs) {
# Create Resource
$params = array_merge(array('ws.op' => 'create'), $kv_pairs);
$data = $this->adapter->request('POST', $this->url, $params, array('return' => 'headers'), array('Content-Type: application/json'));
$headers = $this->_type() == 'custom_fields' ? array() : array('Content-Type: application/json');
$data = $this->adapter->request('POST', $this->url, $params, array('return' => 'headers'), $headers);

# Return new Resource
$url = $data['Location'];
Expand Down
149 changes: 102 additions & 47 deletions aweber_api/oauth_application.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class OAuthApplication implements AWeberOAuthAdapter {

public $signatureMethod = 'HMAC-SHA1';

public $clientVersion = '1.1.17';
public $clientVersion = '1.1.18';

public $oAuthVersion = '1.0';

Expand All @@ -86,7 +86,8 @@ class OAuthApplication implements AWeberOAuthAdapter {
*
* Create a new OAuthApplication, based on an OAuthServiceProvider
* @access public
* @return void
* @param bool $parentApp
* @throws Exception
*/
public function __construct($parentApp = false) {
if ($parentApp) {
Expand Down Expand Up @@ -116,14 +117,6 @@ public function request($method, $uri, $data = array(), $options = array(), $hea
$uri = $this->app->removeBaseUri($uri);
$url = $this->app->getBaseUri() . $uri;

# WARNING: non-primative items in data must be json serialized in GET and POST.
if ($method == 'POST' or $method == 'GET') {
foreach ($data as $key => $value) {
if (is_array($value)) {
$data[$key] = json_encode($value);
}
}
}

$response = $this->makeRequest($method, $url, $data, $headers);
if (!empty($options['return'])) {
Expand Down Expand Up @@ -171,6 +164,7 @@ public function getRequestToken($callbackUrl=false) {
*
* @access public
* @return void
* @throws AWeberOAuthDataMissing
*/
public function getAccessToken() {
$resp = $this->makeRequest('POST', $this->app->getAccessTokenUrl(),
Expand Down Expand Up @@ -212,10 +206,11 @@ public function parseAsError($response) {
* Enforce that all the fields in requiredFields are present and not
* empty in data. If a required field is empty, throw an exception.
*
* @param mixed $data Array of data
* @param mixed $requiredFields Array of required field names.
* @access protected
* @param mixed $data Array of data
* @param mixed $requiredFields Array of required field names.
* @return void
* @throws AWeberOAuthDataMissing
* @access protected
*/
protected function requiredFromResponse($data, $requiredFields) {
foreach ($requiredFields as $field) {
Expand All @@ -229,14 +224,14 @@ protected function requiredFromResponse($data, $requiredFields) {
* get
*
* Make a get request. Used to exchange user tokens with serice provider.
* @param mixed $url URL to make a get request from.
* @param array $data Data for the request.
* @param mixed $headers Headers for the request
* @param mixed $url URL to make a get request from.
* @param String $url_params URL parameter string
* @param mixed $headers Headers for the request
* @access protected
* @return void
*/
protected function get($url, $data, $headers) {
$url = $this->_addParametersToUrl($url, $data);
protected function get($url, $url_params, $headers = array()) {
$url = $this->_addParametersToUrl($url, $url_params);
$handle = $this->curl->init($url);
$resp = $this->_sendRequest($handle, $headers);
return $resp;
Expand All @@ -248,17 +243,17 @@ protected function get($url, $data, $headers) {
* Adds the parameters in associative array $data to the
* given URL
* @param String $url URL
* @param array $data Parameters to be added as a query string to
* @param String $data Parameters to be added as a query string to
* the URL provided
* @access protected
* @return void
*/
protected function _addParametersToUrl($url, $data) {
if (!empty($data)) {
if (strpos($url, '?') === false) {
$url .= '?'.$this->buildData($data);
$url .= '?' . $data;
} else {
$url .= '&'.$this->buildData($data);
$url .= '&' . $data;
}
}
return $url;
Expand Down Expand Up @@ -441,32 +436,34 @@ public function signRequest($method, $url, $data) {
* @param mixed $method
* @param mixed $url - Reserved characters in query params MUST be escaped
* @param mixed $data - Reserved characters in values MUST NOT be escaped
* @param mixed $headers - Reserved characters in values MUST NOT be escaped
* @access public
* @return void
*
* @throws AWeberAPIException
*/
public function makeRequest($method, $url, $data=array(), $headers=array()) {

if ($this->debug) echo "\n** {$method}: $url\n";

list($urlParams, $requestBody) = $this->formatRequestData($method, $url, $data, $headers);

switch (strtoupper($method)) {
case 'POST':
$oauth = $this->prepareRequest($method, $url, $data);
$resp = $this->post($url, $oauth, $data, $headers);
$resp = $this->post($url, $urlParams, $requestBody, $headers);
break;

case 'GET':
$oauth = $this->prepareRequest($method, $url, $data);
$resp = $this->get($url, $oauth, $data, $headers);
$resp = $this->get($url, $urlParams, $headers);
break;

case 'DELETE':
$oauth = $this->prepareRequest($method, $url, $data);
$resp = $this->delete($url, $oauth, $headers);
$resp = $this->delete($url, $urlParams, $headers);
break;

case 'PATCH':
$oauth = $this->prepareRequest($method, $url, array());
$resp = $this->patch($url, $oauth, $data, $headers);
$headers = $this->_ensureContentType($headers, 'application/json');
$resp = $this->patch($url, $urlParams, $requestBody, $headers);
break;
}

Expand Down Expand Up @@ -495,21 +492,22 @@ public function makeRequest($method, $url, $data=array(), $headers=array()) {
}

/**
* put
* patch
*
* Prepare an OAuth put method.
* Prepare an OAuth patch method.
*
* @param mixed $url URL where we are making the request to
* @param mixed $data Data that is used to make the request
* @param mixed $headers Headers for the request
* @param mixed $url URL where we are making the request to
* @param mixed $url_params URL parameter string
* @param mixed $post_field Data that is used to make the request
* @param mixed $headers Headers for the request
* @access protected
* @return void
*/
protected function patch($url, $oauth, $data, $headers) {
$url = $this->_addParametersToUrl($url, $oauth);
protected function patch($url, $url_params, $post_field, $headers = array()) {
$url = $this->_addParametersToUrl($url, $url_params);
$handle = $this->curl->init($url);
$this->curl->setopt($handle, CURLOPT_CUSTOMREQUEST, 'PATCH');
$this->curl->setopt($handle, CURLOPT_POSTFIELDS, json_encode($data));
$this->curl->setopt($handle, CURLOPT_POSTFIELDS, $post_field);
$resp = $this->_sendRequest($handle, $headers);
return $resp;
}
Expand All @@ -519,18 +517,18 @@ protected function patch($url, $oauth, $data, $headers) {
*
* Prepare an OAuth post method.
*
* @param mixed $url URL where we are making the request to
* @param mixed $data Data that is used to make the request
* @param mixed $headers Headers for the request
* @param mixed $url URL where we are making the request to
* @param mixed $url_params URL parameter string
* @param mixed $post_field Data that is used to make the request
* @param mixed $headers Headers for the request
* @access protected
* @return void
*/
protected function post($url, $oauth, $data, $headers = array()) {
$url = $this->_addParametersToUrl($url, $oauth);
protected function post($url, $url_params, $post_field, $headers = array()) {
$url = $this->_addParametersToUrl($url, $url_params);
$handle = $this->curl->init($url);
$this->curl->setopt($handle, CURLOPT_POST, true);
$postData = in_array("Content-Type: application/json", $headers) ? json_encode($data) : $this->buildData($data);
$this->curl->setopt($handle, CURLOPT_POSTFIELDS, $postData);
$this->curl->setopt($handle, CURLOPT_POSTFIELDS, $post_field);
$resp = $this->_sendRequest($handle, $headers);
return $resp;
}
Expand All @@ -540,13 +538,13 @@ protected function post($url, $oauth, $data, $headers = array()) {
*
* Makes a DELETE request
* @param mixed $url URL where we are making the request to
* @param mixed $data Data that is used in the request
* @param mixed $url_params URL parameter string
* @param mixed $headers Headers for the request
* @access protected
* @return void
*/
protected function delete($url, $data, $headers = array()) {
$url = $this->_addParametersToUrl($url, $data);
protected function delete($url, $url_params, $headers = array()) {
$url = $this->_addParametersToUrl($url, $url_params);
$handle = $this->curl->init($url);
$this->curl->setopt($handle, CURLOPT_CUSTOMREQUEST, 'DELETE');
$resp = $this->_sendRequest($handle, $headers);
Expand Down Expand Up @@ -649,6 +647,63 @@ protected function userAgent() {
return $this->userAgentTitle . $this->clientVersion . ' PHP/' . PHP_VERSION . ' ' . php_uname('m') . '-' . strtolower(php_uname('s')) . '-'. php_uname('r');
}

/**
* @param $method
* @param $headers
* @return bool
*
* Return True if headers array does not contain 'Content-Type: application/json' and is a POST, GET, or DELETE request
*/
protected function needsUrlFormatting($method, $headers) {
return !in_array("Content-Type: application/json", $headers) && in_array($method, array('POST', 'GET', 'DELETE'));
}

/**
* @param $method
* @param $url
* @param $data
* @param $headers
* @return array
*/
protected function formatRequestData($method, $url, $data, $headers)
{
# WARNING: If not being sent as json, non-primitive items in data must be json serialized in GET and POST.
if ($this->needsUrlFormatting($method, $headers)) {
foreach ($data as $key => $value) {
if (is_array($value)) {
$data[$key] = json_encode($value);
}
}
$urlParams = $this->buildData($this->prepareRequest($method, $url, $data));
$requestBody = $this->buildData($data);
} else {
$urlParams = $this->buildData($this->prepareRequest($method, $url, array()));
$requestBody = json_encode($data);
}
return array($urlParams, $requestBody);
}

/**
* Checks the $headers array for content-type and adds the header if it doesn't exist and replaces it if isn't
* what is passed.
*
* @param $headers
* @param $expectedContentType
* @return array
*/
private function _ensureContentType($headers, $expectedContentType) {

foreach ($headers as $key => $value) {
if ( stripos($value, 'content-type:') !== false ) {
unset($headers[$key]);
}

}

$headers[] = 'Content-Type: ' . $expectedContentType;
return $headers;
}

}

/**
Expand Down
60 changes: 57 additions & 3 deletions tests/AWeberCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,60 @@ public function setUp() {
$this->found = $this->subscribers->find($this->params);
}

/**
* Test to ensure that the nested objects, such as "custom_fields", are formatted correctly for GET request. The
* nested objects should be a JSON encoded string.
*/
public function testFormatOfGetData() {
$findParams = array('custom_fields' => array('test' => 'test'));
$expectedUri = '/accounts/1/lists/303449/subscribers?custom_fields=%7B%22test%22%3A%22test%22%7D&ws.op=find';

$this->adapter->clearRequests();

$resp = $this->subscribers->find($findParams);

$req = $this->adapter->requestsMade[0];
$this->assertEquals($req['method'], 'GET');
$this->assertEquals($expectedUri, $req['uri']);
$this->assertEmpty($req['headers'], "Find request shouldn't have a Content-Type header");
}

/**
* Checks that the nested objects, such as "custom_fields", are formatted correctly. The "create" method
* is a POST with Content-Type of 'application/json'. The data should be formatted as JSON.
*/
public function testFormatOfPostData() {

$createParams = array(
'email' => '[email protected]',
'ip_address' => '127.0.0.1',
'name' => 'John Doe',
'custom_fields' => array(
'custom' => 'test'
)
);

$expectedCreateParams = array(
'ws.op' => 'create',
'email' => '[email protected]',
'ip_address' => '127.0.0.1',
'name' => 'John Doe',
'custom_fields' => array(
'custom' => 'test'
)
);

$this->adapter->clearRequests();

$resp = $this->subscribers->create($createParams);

$req = $this->adapter->requestsMade[0];
$this->assertEquals($req['method'], 'POST');
$this->assertEquals($req['data'], $expectedCreateParams);
$this->assertEquals(array('Content-Type: application/json'), $req['headers'], "Create request should have a Content-Type header");

}

/**
* The find method makes two requests, one for the collection, and the other to get total_size.
*/
Expand All @@ -30,15 +84,15 @@ public function testShouldInitiallyMake2APIRequests() {
public function testShouldRequestCollectionPageFirst() {
#$this->subscribers->find($this->params);
$uri = $this->adapter->requestsMade[0]['uri'];
$this->assertEquals($uri, '/accounts/1/lists/303449/subscribers?status=unsubscribed&ws.size=1&ws.start=0&ws.op=find');
$this->assertEquals($uri, '/accounts/1/lists/303449/subscribers?status=unsubscribed&ws.op=find&ws.size=1&ws.start=0');
}

/**
* The second of two requests, verify the url to get the total size.
*/
public function testShouldRequestTotalSizePageSecond() {
$uri = $this->adapter->requestsMade[1]['uri'];
$this->assertEquals($uri, '/accounts/1/lists/303449/subscribers?status=unsubscribed&ws.size=1&ws.start=0&ws.op=find&ws.show=total_size');
$this->assertEquals($uri, '/accounts/1/lists/303449/subscribers?status=unsubscribed&ws.op=find&ws.show=total_size&ws.size=1&ws.start=0');
}

/**
Expand All @@ -57,7 +111,7 @@ public function testShouldRequestCorrectCollectionPage() {
$this->adapter->clearRequests();
$subscriber = $this->found[1];
$uri = $this->adapter->requestsMade[0]['uri'];
$this->assertEquals($uri, '/accounts/1/lists/303449/subscribers?status=unsubscribed&ws.size=1&ws.start=1&ws.op=find');
$this->assertEquals($uri, '/accounts/1/lists/303449/subscribers?status=unsubscribed&ws.op=find&ws.size=1&ws.start=1');
}

/**
Expand Down
Loading

0 comments on commit 2ee27a5

Please sign in to comment.