├── VERSION ├── tests ├── bootstrap.php ├── bootstrap.no_autoload.php ├── GenreTest.php ├── ProductTest.php ├── PurchaseTest.php ├── UtilTest.php ├── TestCase.php └── AdsObjectTest.php ├── lib ├── Error │ ├── Api.php │ ├── ApiConnection.php │ ├── Authentication.php │ ├── RateLimit.php │ ├── InvalidRequest.php │ └── Base.php ├── Genre.php ├── JsonSerializable.php ├── ApiResponse.php ├── Product.php ├── AttachedObject.php ├── HttpClient │ ├── ClientInterface.php │ └── CurlClient.php ├── SingletonApiResource.php ├── Util │ ├── Set.php │ ├── AutoPagingIterator.php │ ├── RequestOptions.php │ └── Util.php ├── GameKey.php ├── Purchase.php ├── Collection.php ├── ExternalAccount.php ├── Ads.php ├── ApiResource.php ├── AdsObject.php └── ApiRequestor.php ├── .gitignore ├── phpunit.xml ├── phpunit.no_autoload.xml ├── composer.json ├── init.php └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.1 -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests 5 | 6 | 7 | 8 | 9 | lib 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /lib/JsonSerializable.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests 5 | 6 | 7 | 8 | 9 | lib 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | {"name":"atgames\/ads-php","type":"library","description":"Atgames Digital Service PHP Library","keywords":["atgames","ads-php","digital service","api"],"homepage":"https:\/\/atgames.net\/","license":"MIT","authors":[{"name":"AtGames and contributors","homepage":"https:\/\/gitlab.direct2drive.com\/atgames\/ads-php"}],"require":{"php":">=5.3.3","ext-curl":"*","ext-json":"*","ext-mbstring":"*"},"require-dev":{"phpunit\/phpunit":"~4.0","satooshi\/php-coveralls":"~0.6.1"},"extra":{"branch-alias":{"dev-master":"1.0-dev"}}} -------------------------------------------------------------------------------- /lib/Error/InvalidRequest.php: -------------------------------------------------------------------------------- 1 | adsParam = $adsParam; 15 | } 16 | public function getAdsParam() 17 | { 18 | return $this->adsParam; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/ApiResponse.php: -------------------------------------------------------------------------------- 1 | body = $body; 20 | $this->code = $code; 21 | $this->headers = $headers; 22 | $this->json = $json; 23 | } 24 | } -------------------------------------------------------------------------------- /lib/Product.php: -------------------------------------------------------------------------------- 1 | _values), array_keys($properties)); 14 | // Don't unset, but rather set to null so we send up '' for deletion. 15 | foreach ($removed as $k) { 16 | $this->$k = null; 17 | } 18 | foreach ($properties as $k => $v) { 19 | $this->$k = $v; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /tests/PurchaseTest.php: -------------------------------------------------------------------------------- 1 | 12, 11 | // 'product_ids' => array('151480', '152070') 12 | // )); 13 | } 14 | 15 | public function testRefund() 16 | { 17 | self::authorizeFromEnv(); 18 | 19 | $p = Purchase::create(array( 20 | 'transaction_id' => 12, 21 | 'product_ids' => array('151480', '152070') 22 | )); 23 | 24 | 25 | // Purchase::refund(array( 26 | // 'transaction_id' => $p->id(); 27 | // )); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /lib/HttpClient/ClientInterface.php: -------------------------------------------------------------------------------- 1 | refresh(); 10 | return $instance; 11 | } 12 | /** 13 | * @return string The endpoint associated with this singleton class. 14 | */ 15 | public static function classUrl() 16 | { 17 | $base = static::className(); 18 | return "/${base}"; 19 | } 20 | /** 21 | * @return string The endpoint associated with this singleton API resource. 22 | */ 23 | public function instanceUrl() 24 | { 25 | return static::classUrl(); 26 | } 27 | } -------------------------------------------------------------------------------- /lib/Util/Set.php: -------------------------------------------------------------------------------- 1 | _elts = array(); 11 | foreach ($members as $item) { 12 | $this->_elts[$item] = true; 13 | } 14 | } 15 | public function includes($elt) 16 | { 17 | return isset($this->_elts[$elt]); 18 | } 19 | public function add($elt) 20 | { 21 | $this->_elts[$elt] = true; 22 | } 23 | public function discard($elt) 24 | { 25 | unset($this->_elts[$elt]); 26 | } 27 | public function toArray() 28 | { 29 | return array_keys($this->_elts); 30 | } 31 | public function getIterator() 32 | { 33 | return new ArrayIterator($this->toArray()); 34 | } 35 | } -------------------------------------------------------------------------------- /lib/GameKey.php: -------------------------------------------------------------------------------- 1 | assertTrue(Util\Util::isList($list)); 11 | 12 | $notlist = array(5, 'nstaoush', array(), 'bar' => 'baz'); 13 | $this->assertFalse(Util\Util::isList($notlist)); 14 | } 15 | 16 | public function testThatPHPHasValueSemanticsForArrays() 17 | { 18 | $original = array('php-arrays' => 'value-semantics'); 19 | $derived = $original; 20 | $derived['php-arrays'] = 'reference-semantics'; 21 | 22 | $this->assertSame('value-semantics', $original['php-arrays']); 23 | } 24 | 25 | public function testUtf8() 26 | { 27 | // UTF-8 string 28 | $x = "\xc3\xa9"; 29 | $this->assertSame(Util\Util::utf8($x), $x); 30 | 31 | // Latin-1 string 32 | $x = "\xe9"; 33 | $this->assertSame(Util\Util::utf8($x), "\xc3\xa9"); 34 | 35 | // Not a string 36 | $x = true; 37 | $this->assertSame(Util\Util::utf8($x), $x); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/Error/Base.php: -------------------------------------------------------------------------------- 1 | httpStatus = $httpStatus; 15 | $this->httpBody = $httpBody; 16 | $this->jsonBody = $jsonBody; 17 | $this->httpHeaders = $httpHeaders; 18 | $this->requestId = null; 19 | if ($httpHeaders && isset($httpHeaders['Request-Id'])) { 20 | $this->requestId = $httpHeaders['Request-Id']; 21 | } 22 | } 23 | public function getHttpStatus() 24 | { 25 | return $this->httpStatus; 26 | } 27 | public function getHttpBody() 28 | { 29 | return $this->httpBody; 30 | } 31 | public function getJsonBody() 32 | { 33 | return $this->jsonBody; 34 | } 35 | public function getHttpHeaders() 36 | { 37 | return $this->httpHeaders; 38 | } 39 | public function getRequestId() 40 | { 41 | return $this->requestId; 42 | } 43 | public function __toString() 44 | { 45 | $id = $this->requestId ? " from API request '{$this->requestId}'": ""; 46 | $message = explode("\n", parent::__toString()); 47 | $message[0] .= $id; 48 | return implode("\n", $message); 49 | } 50 | } -------------------------------------------------------------------------------- /lib/Util/AutoPagingIterator.php: -------------------------------------------------------------------------------- 1 | page = $collection; 11 | $this->params = $params; 12 | } 13 | public function rewind() 14 | { 15 | // Actually rewinding would require making a copy of the original page. 16 | } 17 | public function current() 18 | { 19 | $item = current($this->page->data); 20 | $this->lastId = $item !== false ? $item['id'] : null; 21 | return $item; 22 | } 23 | public function key() 24 | { 25 | return key($this->page->data); 26 | } 27 | public function next() 28 | { 29 | $item = next($this->page->data); 30 | if ($item === false) { 31 | // If we've run out of data on the current page, try to fetch another one 32 | if ($this->page['has_more']) { 33 | $this->params = array_merge( 34 | $this->params ? $this->params : array(), 35 | array('starting_after' => $this->lastId) 36 | ); 37 | $this->page = $this->page->all($this->params); 38 | } else { 39 | return false; 40 | } 41 | } 42 | } 43 | public function valid() 44 | { 45 | $key = key($this->page->data); 46 | $valid = ($key !== null && $key !== false); 47 | return $valid; 48 | } 49 | } -------------------------------------------------------------------------------- /lib/Purchase.php: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ADS PHP bindings 2 | 3 | 4 | ## Requirements 5 | 6 | PHP 5.3.3 and later. 7 | 8 | ## Composer 9 | 10 | You can install the bindings via [Composer](http://getcomposer.org/). Run the following command: 11 | 12 | ```bash 13 | composer require atgames/ads-php 14 | ``` 15 | 16 | To use the bindings, use Composer's [autoload](https://getcomposer.org/doc/00-intro.md#autoloading): 17 | 18 | ```php 19 | require_once('vendor/autoload.php'); 20 | ``` 21 | 22 | ## Manual Installation 23 | 24 | If you do not wish to use Composer, you can download the [latest release](https://gitlab.direct2drive.com/atgames/ads-php releases). Then, to use the bindings, include the `init.php` file. 25 | 26 | ```php 27 | require_once('/path/to/ads-php/init.php'); 28 | ``` 29 | 30 | ## Getting Started 31 | 32 | Simple usage looks like: 33 | 34 | ```php 35 | \Ads\Ads::setApiKey('OTEwMDA6a1I2UkFTelJhUS1lcWNZSkw1blE='); 36 | $data = \Ads\Product::all(); 37 | echo $data; 38 | ``` 39 | 40 | ## Documentation 41 | 42 | Please see https://api.atgames.net/docs/api for up-to-date documentation. 43 | 44 | ## Custom Request Timeouts 45 | 46 | To modify request timeouts (connect or total, in seconds) you'll need to tell the API client to use a CurlClient other than its default. You'll set the timeouts in that CurlClient. 47 | 48 | ```php 49 | // set up your tweaked Curl client 50 | $curl = new \Ads\HttpClient\CurlClient(); 51 | $curl->setTimeout(10); // default is \Ads\HttpClient\CurlClient::DEFAULT_TIMEOUT 52 | $curl->setConnectTimeout(5); // default is \Ads\HttpClient\CurlClient::DEFAULT_CONNECT_TIMEOUT 53 | 54 | echo $curl->getTimeout(); // 10 55 | echo $curl->getConnectTimeout(); // 5 56 | 57 | // tell Ads to use the tweaked client 58 | \Ads\ApiRequestor::setHttpClient($curl); 59 | 60 | // use the Ads API client as you normally would 61 | ``` 62 | 63 | ## Development 64 | 65 | Install dependencies: 66 | 67 | ``` bash 68 | composer install 69 | ``` 70 | 71 | ## Tests 72 | 73 | Install dependencies as mentioned above (which will resolve [PHPUnit](http://packagist.org/packages/phpunit/phpunit)), then you can run the test suite: 74 | 75 | ```bash 76 | ./vendor/bin/phpunit 77 | ``` 78 | 79 | Or to run an individual test file: 80 | 81 | ```bash 82 | ./vendor/bin/phpunit tests/UtilTest.php 83 | ``` -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | mock = null; 26 | $this->call = 0; 27 | } 28 | protected function mockRequest($method, $path, $params = array(), $return = array('id' => 'myId'), $rcode = 200) 29 | { 30 | $mock = $this->setUpMockRequest(); 31 | $mock->expects($this->at($this->call++)) 32 | ->method('request') 33 | ->with(strtolower($method), 'https://api.atgames.net' . $path, $this->anything(), $params, false) 34 | ->willReturn(array(json_encode($return), $rcode, array())); 35 | } 36 | private function setUpMockRequest() 37 | { 38 | if (!$this->mock) { 39 | self::authorizeFromEnv(); 40 | $this->mock = $this->getMock('\Ads\HttpClient\ClientInterface'); 41 | ApiRequestor::setHttpClient($this->mock); 42 | } 43 | return $this->mock; 44 | } 45 | 46 | /** 47 | * Genereate a semi-random string 48 | */ 49 | protected static function generateRandomString($length = 24) 50 | { 51 | $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU'; 52 | $charactersLength = strlen($characters); 53 | $randomString = ''; 54 | for ($i = 0; $i < $length; $i++) { 55 | $randomString .= $characters[rand(0, $charactersLength - 1)]; 56 | } 57 | return $randomString; 58 | } 59 | /** 60 | * Generate a semi-random email. 61 | */ 62 | protected static function generateRandomEmail($domain = 'bar.com') 63 | { 64 | return self::generateRandomString().'@'.$domain; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /lib/Collection.php: -------------------------------------------------------------------------------- 1 | _requestParams = $params; 9 | } 10 | public function all($params = null, $opts = null) 11 | { 12 | list($url, $params) = $this->extractPathAndUpdateParams($params); 13 | list($response, $opts) = $this->_request('get', $url, $params, $opts); 14 | $this->_requestParams = $params; 15 | return Util\Util::convertToAdsObject($response, $opts); 16 | } 17 | public function create($params = null, $opts = null) 18 | { 19 | list($url, $params) = $this->extractPathAndUpdateParams($params); 20 | list($response, $opts) = $this->_request('post', $url, $params, $opts); 21 | $this->_requestParams = $params; 22 | return Util\Util::convertToAdsObject($response, $opts); 23 | } 24 | public function retrieve($id, $params = null, $opts = null) 25 | { 26 | list($url, $params) = $this->extractPathAndUpdateParams($params); 27 | $id = Util\Util::utf8($id); 28 | $extn = urlencode($id); 29 | list($response, $opts) = $this->_request( 30 | 'get', 31 | "$url/$extn", 32 | $params, 33 | $opts 34 | ); 35 | $this->_requestParams = $params; 36 | return Util\Util::convertToAdsObject($response, $opts); 37 | } 38 | /** 39 | * @return AutoPagingIterator An iterator that can be used to iterate 40 | * across all objects across all pages. As page boundaries are 41 | * encountered, the next page will be fetched automatically for 42 | * continued iteration. 43 | */ 44 | public function autoPagingIterator() 45 | { 46 | return new Util\AutoPagingIterator($this, $this->_requestParams); 47 | } 48 | private function extractPathAndUpdateParams($params) 49 | { 50 | $url = parse_url($this->url); 51 | if (!isset($url['path'])) { 52 | throw new Error\Api("Could not parse list url into parts: $url"); 53 | } 54 | if (isset($url['query'])) { 55 | // If the URL contains a query param, parse it out into $params so they 56 | // don't interact weirdly with each other. 57 | $query = array(); 58 | parse_str($url['query'], $query); 59 | // PHP 5.2 doesn't support the ?: operator :( 60 | $params = array_merge($params ? $params : array(), $query); 61 | } 62 | return array($url['path'], $params); 63 | } 64 | } -------------------------------------------------------------------------------- /lib/Util/RequestOptions.php: -------------------------------------------------------------------------------- 1 | apiKey = $key; 15 | $this->headers = $headers; 16 | } 17 | 18 | /** 19 | * Unpacks an options array and merges it into the existing RequestOptions 20 | * object. 21 | * @param array|string|null $options a key => value array 22 | * 23 | * @return RequestOptions 24 | */ 25 | public function merge($options) 26 | { 27 | $other_options = self::parse($options); 28 | if ($other_options->apiKey === null) { 29 | $other_options->apiKey = $this->apiKey; 30 | } 31 | $other_options->headers = array_merge($this->headers, $other_options->headers); 32 | return $other_options; 33 | } 34 | 35 | /** 36 | * Unpacks an options array into an RequestOptions object 37 | * @param array|string|null $options a key => value array 38 | * 39 | * @return RequestOptions 40 | */ 41 | public static function parse($options) 42 | { 43 | if ($options instanceof self) { 44 | return $options; 45 | } 46 | 47 | if (is_null($options)) { 48 | return new RequestOptions(null, array()); 49 | } 50 | 51 | if (is_string($options)) { 52 | return new RequestOptions($options, array()); 53 | } 54 | 55 | if (is_array($options)) { 56 | $headers = array(); 57 | $key = null; 58 | if (array_key_exists('api_key', $options)) { 59 | $key = $options['api_key']; 60 | } 61 | if (array_key_exists('idempotency_key', $options)) { 62 | $headers['Idempotency-Key'] = $options['idempotency_key']; 63 | } 64 | if (array_key_exists('ads_account', $options)) { 65 | $headers['Ads-Account'] = $options['ads_account']; 66 | } 67 | if (array_key_exists('ads_version', $options)) { 68 | $headers['Ads-Version'] = $options['ads_version']; 69 | } 70 | return new RequestOptions($key, $headers); 71 | } 72 | 73 | $message = 'The second argument to Ads API method calls is an ' 74 | . 'optional per-request apiKey, which must be a string, or ' 75 | . 'per-request options, which must be an array. (HINT: you can set ' 76 | . 'a global apiKey by "Ads::setApiKey()")'; 77 | throw new Error\Api($message); 78 | } 79 | } -------------------------------------------------------------------------------- /lib/ExternalAccount.php: -------------------------------------------------------------------------------- 1 | _delete($params, $opts); 48 | } 49 | /** 50 | * @param array|string|null $opts 51 | * 52 | * @return ExternalAccount The saved external account. 53 | */ 54 | public function save($opts = null) 55 | { 56 | return $this->_save($opts); 57 | } 58 | /** 59 | * @param array|null $params 60 | * @param array|string|null $opts 61 | * 62 | * @return ExternalAccount The verified (or not) external account. 63 | */ 64 | public function verify($params = null, $opts = null) 65 | { 66 | if ($this['customer']) { 67 | $url = $this->instanceUrl() . '/verify'; 68 | list($response, $options) = $this->_request('post', $url, $params, $opts); 69 | $this->refreshFrom($response, $options); 70 | return $this; 71 | } else { 72 | $message = 'Only customer external accounts can be verified in this manner.'; 73 | throw new Error\Api($message); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /lib/Ads.php: -------------------------------------------------------------------------------- 1 | assertSame($s['foo'], 'a'); 10 | $this->assertTrue(isset($s['foo'])); 11 | unset($s['foo']); 12 | $this->assertFalse(isset($s['foo'])); 13 | } 14 | public function testNormalAccessorsSemantics() 15 | { 16 | $s = new AdsObject(); 17 | $s->foo = 'a'; 18 | $this->assertSame($s->foo, 'a'); 19 | $this->assertTrue(isset($s->foo)); 20 | unset($s->foo); 21 | $this->assertFalse(isset($s->foo)); 22 | } 23 | public function testArrayAccessorsMatchNormalAccessors() 24 | { 25 | $s = new AdsObject(); 26 | $s->foo = 'a'; 27 | $this->assertSame($s['foo'], 'a'); 28 | $s['bar'] = 'b'; 29 | $this->assertSame($s->bar, 'b'); 30 | } 31 | public function testKeys() 32 | { 33 | $s = new AdsObject(); 34 | $s->foo = 'a'; 35 | $this->assertSame($s->keys(), array('foo')); 36 | } 37 | public function testToArray() 38 | { 39 | $s = new AdsObject(); 40 | $s->foo = 'a'; 41 | $converted = $s->__toArray(); 42 | $this->assertInternalType('array', $converted); 43 | $this->assertArrayHasKey('foo', $converted); 44 | $this->assertEquals('a', $converted['foo']); 45 | } 46 | public function testRecursiveToArray() 47 | { 48 | $s = new AdsObject(); 49 | $z = new AdsObject(); 50 | $s->child = $z; 51 | $z->foo = 'a'; 52 | $converted = $s->__toArray(true); 53 | $this->assertInternalType('array', $converted); 54 | $this->assertArrayHasKey('child', $converted); 55 | $this->assertInternalType('array', $converted['child']); 56 | $this->assertArrayHasKey('foo', $converted['child']); 57 | $this->assertEquals('a', $converted['child']['foo']); 58 | } 59 | public function testNonexistentProperty() 60 | { 61 | $s = new AdsObject(); 62 | $this->assertNull($s->nonexistent); 63 | } 64 | public function testPropertyDoesNotExists() 65 | { 66 | $s = new AdsObject(); 67 | $this->assertNull($s['nonexistent']); 68 | } 69 | public function testJsonEncode() 70 | { 71 | // We can only JSON encode our objects in PHP 5.4+. 5.3 must use ->__toJSON() 72 | if (version_compare(phpversion(), '5.4.0', '<')) { 73 | return; 74 | } 75 | $s = new AdsObject(); 76 | $s->foo = 'a'; 77 | $this->assertEquals('{"foo":"a"}', json_encode($s->__toArray())); 78 | } 79 | } -------------------------------------------------------------------------------- /lib/Util/Util.php: -------------------------------------------------------------------------------- 1 | $v) { 35 | // FIXME: this is an encapsulation violation 36 | if ($k[0] == '_') { 37 | continue; 38 | } 39 | if ($v instanceof AdsObject) { 40 | $results[$k] = $v->__toArray(true); 41 | } elseif (is_array($v)) { 42 | $results[$k] = self::convertAdsObjectToArray($v); 43 | } else { 44 | $results[$k] = $v; 45 | } 46 | } 47 | return $results; 48 | } 49 | /** 50 | * Converts a response from the Ads API to the corresponding PHP object. 51 | * 52 | * @param array $resp The response from the Ads API. 53 | * @param array $opts 54 | * @return AdsObject|array 55 | */ 56 | public static function convertToAdsObject($resp, $opts) 57 | { 58 | $types = array( 59 | 'list' => 'Ads\\Collection', 60 | 'product' => 'Ads\\Product', 61 | ); 62 | if (self::isList($resp)) { 63 | $mapped = array(); 64 | foreach ($resp as $i) { 65 | array_push($mapped, self::convertToAdsObject($i, $opts)); 66 | } 67 | return $mapped; 68 | } elseif (is_array($resp)) { 69 | $class = 'Ads\\Collection'; 70 | return $class::constructFrom($resp, $opts); 71 | } else { 72 | return $resp; 73 | } 74 | } 75 | 76 | public static function arraySortByKey(&$arr, $sort_flags = SORT_REGULAR) 77 | { 78 | foreach ($arr as $key=> $val) { 79 | if (is_array($val)) { 80 | self::arraySortByKey($val, $sort_flags); 81 | $arr[$key] = $val; 82 | } 83 | } 84 | ksort($arr); 85 | } 86 | 87 | /** 88 | * concat array key value with '|' 89 | * @param $array|prepare to concat key value 90 | * @return string 91 | */ 92 | public static function flattenArray($array) 93 | { 94 | $output = implode('|', array_map( 95 | function ($v, $k) { 96 | if (is_array($v)) { 97 | return sprintf("%s|%s", $k, self::flattenArray($v)); 98 | } else { 99 | return sprintf("%s|%s", $k, $v); 100 | } 101 | }, 102 | $array, 103 | array_keys($array) 104 | )); 105 | return $output; 106 | } 107 | 108 | /** 109 | * @param string|mixed $value A string to UTF8-encode. 110 | * 111 | * @return string|mixed The UTF8-encoded string, or the object passed in if 112 | * it wasn't a string. 113 | */ 114 | public static function utf8($value) 115 | { 116 | if (is_string($value) && mb_detect_encoding($value, "UTF-8", true) != "UTF-8") { 117 | return utf8_encode($value); 118 | } else { 119 | return $value; 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /lib/HttpClient/CurlClient.php: -------------------------------------------------------------------------------- 1 | timeout = (int) max($seconds, 0); 24 | return $this; 25 | } 26 | public function setConnectTimeout($seconds) 27 | { 28 | $this->connectTimeout = (int) max($seconds, 0); 29 | return $this; 30 | } 31 | public function getTimeout() 32 | { 33 | return $this->timeout; 34 | } 35 | public function getConnectTimeout() 36 | { 37 | return $this->connectTimeout; 38 | } 39 | // END OF USER DEFINED TIMEOUTS 40 | public function request($method, $absUrl, $headers, $params, $hasFile) 41 | { 42 | $curl = curl_init(); 43 | $method = strtolower($method); 44 | $opts = array(); 45 | if ($method == 'get') { 46 | if ($hasFile) { 47 | throw new Error\Api( 48 | "Issuing a GET request with a file parameter" 49 | ); 50 | } 51 | $opts[CURLOPT_HTTPGET] = 1; 52 | if (count($params) > 0) { 53 | $encoded = self::encode($params); 54 | $absUrl = "$absUrl?$encoded"; 55 | } 56 | } elseif ($method == 'post') { 57 | $opts[CURLOPT_POST] = 1; 58 | $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : self::encode($params); 59 | } elseif ($method == 'delete') { 60 | $opts[CURLOPT_CUSTOMREQUEST] = 'DELETE'; 61 | if (count($params) > 0) { 62 | $encoded = self::encode($params); 63 | $absUrl = "$absUrl?$encoded"; 64 | } 65 | } else { 66 | throw new Error\Api("Unrecognized method $method"); 67 | } 68 | // Create a callback to capture HTTP headers for the response 69 | $rheaders = array(); 70 | $headerCallback = function ($curl, $header_line) use (&$rheaders) { 71 | // Ignore the HTTP request line (HTTP/1.1 200 OK) 72 | if (strpos($header_line, ":") === false) { 73 | return strlen($header_line); 74 | } 75 | list($key, $value) = explode(":", trim($header_line), 2); 76 | $rheaders[trim($key)] = trim($value); 77 | return strlen($header_line); 78 | }; 79 | $absUrl = Util\Util::utf8($absUrl); 80 | $opts[CURLOPT_URL] = $absUrl; 81 | $opts[CURLOPT_RETURNTRANSFER] = true; 82 | $opts[CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout; 83 | $opts[CURLOPT_TIMEOUT] = $this->timeout; 84 | $opts[CURLOPT_RETURNTRANSFER] = true; 85 | $opts[CURLOPT_HEADERFUNCTION] = $headerCallback; 86 | $opts[CURLOPT_HTTPHEADER] = $headers; 87 | if (!Ads::$verifySslCerts) { 88 | $opts[CURLOPT_SSL_VERIFYPEER] = false; 89 | } 90 | // @codingStandardsIgnoreStart 91 | // PSR2 requires all constants be upper case. Sadly, the CURL_SSLVERSION 92 | // constants to not abide by those rules. 93 | // 94 | // Opt into TLS 1.x support on older versions of curl. This causes some 95 | // curl versions, notably on RedHat, to upgrade the connection to TLS 96 | // 1.2, from the default TLS 1.0. 97 | if (!defined('CURL_SSLVERSION_TLSv1')) { 98 | define('CURL_SSLVERSION_TLSv1', 1); // constant not defined in PHP < 5.5 99 | } 100 | $opts[CURLOPT_SSLVERSION] = CURL_SSLVERSION_TLSv1; 101 | // @codingStandardsIgnoreEnd 102 | curl_setopt_array($curl, $opts); 103 | $rbody = curl_exec($curl); 104 | if (!defined('CURLE_SSL_CACERT_BADFILE')) { 105 | define('CURLE_SSL_CACERT_BADFILE', 77); // constant not defined in PHP 106 | } 107 | $errno = curl_errno($curl); 108 | if ($errno == CURLE_SSL_CACERT || 109 | $errno == CURLE_SSL_PEER_CERTIFICATE || 110 | $errno == CURLE_SSL_CACERT_BADFILE 111 | ) { 112 | array_push( 113 | $headers, 114 | 'X-Ads-Client-Info: {"ca":"using Ads-supplied CA bundle"}' 115 | ); 116 | $cert = self::caBundle(); 117 | curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); 118 | curl_setopt($curl, CURLOPT_CAINFO, $cert); 119 | $rbody = curl_exec($curl); 120 | } 121 | if ($rbody === false) { 122 | $errno = curl_errno($curl); 123 | $message = curl_error($curl); 124 | curl_close($curl); 125 | $this->handleCurlError($absUrl, $errno, $message); 126 | } 127 | $rcode = curl_getinfo($curl, CURLINFO_HTTP_CODE); 128 | curl_close($curl); 129 | return array($rbody, $rcode, $rheaders); 130 | } 131 | /** 132 | * @param number $errno 133 | * @param string $message 134 | * @throws Error\ApiConnection 135 | */ 136 | private function handleCurlError($url, $errno, $message) 137 | { 138 | switch ($errno) { 139 | case CURLE_COULDNT_CONNECT: 140 | case CURLE_COULDNT_RESOLVE_HOST: 141 | case CURLE_OPERATION_TIMEOUTED: 142 | $msg = "Could not connect to Ads ($url). Please check your " 143 | . "internet connection and try again. If this problem persists, " 144 | . "you should check Ads's service status at " 145 | . "https://twitter.com/atgames, or"; 146 | break; 147 | case CURLE_SSL_CACERT: 148 | case CURLE_SSL_PEER_CERTIFICATE: 149 | $msg = "Could not verify Ads's SSL certificate. Please make sure " 150 | . "that your network is not intercepting certificates. " 151 | . "(Try going to $url in your browser.) " 152 | . "If this problem persists,"; 153 | break; 154 | default: 155 | $msg = "Unexpected error communicating with Ads. " 156 | . "If this problem persists,"; 157 | } 158 | $msg .= " let us know at support@atgames.net."; 159 | $msg .= "\n\n(Network error [errno $errno]: $message)"; 160 | throw new Error\ApiConnection($msg); 161 | } 162 | private static function caBundle() 163 | { 164 | return dirname(__FILE__) . '/../../data/ca-certificates.crt'; 165 | } 166 | /** 167 | * @param array $arr An map of param keys to values. 168 | * @param string|null $prefix 169 | * 170 | * Only public for testability, should not be called outside of CurlClient 171 | * 172 | * @return string A querystring, essentially. 173 | */ 174 | public static function encode($arr, $prefix = null) 175 | { 176 | if (!is_array($arr)) { 177 | return $arr; 178 | } 179 | $r = array(); 180 | foreach ($arr as $k => $v) { 181 | if (is_null($v)) { 182 | continue; 183 | } 184 | if ($prefix && $k && !is_int($k)) { 185 | $k = $prefix."[".$k."]"; 186 | } elseif ($prefix) { 187 | $k = $prefix."[]"; 188 | } 189 | if (is_array($v)) { 190 | $enc = self::encode($v, $k); 191 | if ($enc) { 192 | $r[] = $enc; 193 | } 194 | } else { 195 | $r[] = urlencode($k)."=".urlencode($v); 196 | } 197 | } 198 | return implode("&", $r); 199 | } 200 | } -------------------------------------------------------------------------------- /lib/ApiResource.php: -------------------------------------------------------------------------------- 1 | true, 'Ads-Version' => true); 6 | public static function baseUrl() 7 | { 8 | return Ads::$apiBase; 9 | } 10 | /** 11 | * @return ApiResource The refreshed resource. 12 | */ 13 | public function refresh() 14 | { 15 | $requestor = new ApiRequestor($this->_opts->apiKey, static::baseUrl()); 16 | $url = $this->instanceUrl(); 17 | list($response, $this->_opts->apiKey) = $requestor->request( 18 | 'get', 19 | $url, 20 | $this->_retrieveOptions, 21 | $this->_opts->headers 22 | ); 23 | $this->setLastResponse($response); 24 | $this->refreshFrom($response->json, $this->_opts); 25 | return $this; 26 | } 27 | /** 28 | * @return string The name of the class, with namespacing and underscores 29 | * stripped. 30 | */ 31 | public static function className() 32 | { 33 | $class = get_called_class(); 34 | // Useful for namespaces: Foo\Charge 35 | if ($postfixNamespaces = strrchr($class, '\\')) { 36 | $class = substr($postfixNamespaces, 1); 37 | } 38 | // Useful for underscored 'namespaces': Foo_Charge 39 | if ($postfixFakeNamespaces = strrchr($class, '')) { 40 | $class = $postfixFakeNamespaces; 41 | } 42 | if (substr($class, 0, strlen('Ads')) == 'Ads') { 43 | $class = substr($class, strlen('Ads')); 44 | } 45 | $class = str_replace('_', '', $class); 46 | $name = urlencode($class); 47 | $name = static::_decamelize($name); 48 | $name = strtolower($name); 49 | return $name; 50 | } 51 | /** 52 | * @return string The endpoint URL for the given class. 53 | */ 54 | public static function classUrl() 55 | { 56 | $base = static::className(); 57 | return "/${base}s"; 58 | } 59 | /** 60 | * @return string The full API URL for this API resource. 61 | */ 62 | public function instanceUrl() 63 | { 64 | $id = $this['id']; 65 | if ($id === null) { 66 | $class = get_called_class(); 67 | $message = "Could not determine which URL to request: " 68 | . "$class instance has invalid ID: $id"; 69 | throw new Error\InvalidRequest($message, null); 70 | } 71 | $id = Util\Util::utf8($id); 72 | $base = static::classUrl(); 73 | $extn = urlencode($id); 74 | return "$base/$extn"; 75 | } 76 | public static function _decamelize($string) { 77 | return strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $string)); 78 | } 79 | private static function _validateParams($params = null) 80 | { 81 | if ($params && !is_array($params)) { 82 | $message = "You must pass an array as the first argument to Ads API " 83 | . "method calls. (HINT: an example call to reverse a game key " 84 | . "would be: \"Ads\\GameKey::reserve(array('product_id' => 8288828, " 85 | . "'currency' => 'usd'))\")"; 86 | throw new Error\Api($message); 87 | } 88 | } 89 | protected function _request($method, $url, $params = array(), $options = null) 90 | { 91 | $opts = $this->_opts->merge($options); 92 | list($resp, $options) = static::_staticRequest($method, $url, $params, $opts); 93 | $this->setLastResponse($resp); 94 | return array($resp->json, $options); 95 | } 96 | protected static function _staticRequest($method, $url, $params, $options) 97 | { 98 | $opts = Util\RequestOptions::parse($options); 99 | $requestor = new ApiRequestor($opts->apiKey, static::baseUrl()); 100 | list($response, $opts->apiKey) = $requestor->request($method, $url, $params, $opts->headers); 101 | foreach ($opts->headers as $k => $v) { 102 | if (!array_key_exists($k, self::$HEADERS_TO_PERSIST)) { 103 | unset($opts->headers[$k]); 104 | } 105 | } 106 | return array($response, $opts); 107 | } 108 | protected static function _retrieve($id, $options = null) 109 | { 110 | $opts = Util\RequestOptions::parse($options); 111 | $instance = new static($id, $opts); 112 | $instance->refresh(); 113 | return $instance; 114 | } 115 | protected static function _all($params = null, $options = null) 116 | { 117 | self::_validateParams($params); 118 | $url = static::classUrl(); 119 | list($response, $opts) = static::_staticRequest('get', $url, $params, $options); 120 | $obj = Util\Util::convertToAdsObject($response->json, $opts); 121 | if (!is_a($obj, 'Ads\\Collection')) { 122 | $class = get_class($obj); 123 | $message = "Expected type \"Ads\\Collection\", got \"$class\" instead"; 124 | throw new Error\Api($message); 125 | } 126 | $obj->setLastResponse($response); 127 | $obj->setRequestParams($params); 128 | return $obj; 129 | } 130 | protected static function _create($params = null, $options = null) 131 | { 132 | self::_validateParams($params); 133 | $url = static::classUrl(); 134 | list($response, $opts) = static::_staticRequest('post', $url, $params, $options); 135 | $obj = Util\Util::convertToAdsObject($response->json, $opts); 136 | $obj->setLastResponse($response); 137 | return $obj; 138 | } 139 | protected static function _reserve($params = null, $options = null) 140 | { 141 | self::_validateParams($params); 142 | $url = static::classUrl() . '/reserve'; 143 | list($response, $opts) = static::_staticRequest('post', $url, $params, $options); 144 | $obj = Util\Util::convertToAdsObject($response->json, $opts); 145 | $obj->setLastResponse($response); 146 | return $obj; 147 | } 148 | protected static function _post($subPath, $params = null, $options = null) 149 | { 150 | self::_validateParams($params); 151 | $url = static::classUrl() . $subPath; 152 | list($response, $opts) = static::_staticRequest('post', $url, $params, $options); 153 | $obj = Util\Util::convertToAdsObject($response->json, $opts); 154 | $obj->setLastResponse($response); 155 | return $obj; 156 | } 157 | protected function _save($options = null) 158 | { 159 | $params = $this->serializeParameters(); 160 | if (count($params) > 0) { 161 | $url = $this->instanceUrl(); 162 | list($response, $opts) = $this->_request('post', $url, $params, $options); 163 | $this->refreshFrom($response, $opts); 164 | } 165 | return $this; 166 | } 167 | protected function _delete($params = null, $options = null) 168 | { 169 | self::_validateParams($params); 170 | $url = $this->instanceUrl(); 171 | list($response, $opts) = $this->_request('delete', $url, $params, $options); 172 | $this->refreshFrom($response, $opts); 173 | return $this; 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /lib/AdsObject.php: -------------------------------------------------------------------------------- 1 | _lastResponse; 33 | } 34 | /** 35 | * @param ApiResponse 36 | * 37 | * @return void Set the last response from the Ads API 38 | */ 39 | public function setLastResponse($resp) 40 | { 41 | $this->_lastResponse = $resp; 42 | } 43 | protected $_opts; 44 | protected $_values; 45 | protected $_unsavedValues; 46 | protected $_transientValues; 47 | protected $_retrieveOptions; 48 | protected $_lastResponse; 49 | public function __construct($id = null, $opts = null) 50 | { 51 | $this->_opts = $opts ? $opts : new Util\RequestOptions(); 52 | $this->_values = array(); 53 | $this->_unsavedValues = new Util\Set(); 54 | $this->_transientValues = new Util\Set(); 55 | $this->_retrieveOptions = array(); 56 | if (is_array($id)) { 57 | foreach ($id as $key => $value) { 58 | if ($key != 'id') { 59 | $this->_retrieveOptions[$key] = $value; 60 | } 61 | } 62 | $id = $id['id']; 63 | } 64 | if ($id !== null) { 65 | $this->id = $id; 66 | } 67 | } 68 | // Standard accessor magic methods 69 | public function __set($k, $v) 70 | { 71 | if ($v === "") { 72 | throw new InvalidArgumentException( 73 | 'You cannot set \''.$k.'\'to an empty string. ' 74 | .'We interpret empty strings as NULL in requests. ' 75 | .'You may set obj->'.$k.' = NULL to delete the property' 76 | ); 77 | } 78 | if (self::$nestedUpdatableAttributes->includes($k) 79 | && isset($this->$k) && is_array($v)) { 80 | $this->$k->replaceWith($v); 81 | } else { 82 | // TODO: may want to clear from $_transientValues (Won't be user-visible). 83 | $this->_values[$k] = $v; 84 | } 85 | if (!self::$permanentAttributes->includes($k)) { 86 | $this->_unsavedValues->add($k); 87 | } 88 | } 89 | public function __isset($k) 90 | { 91 | return isset($this->_values[$k]); 92 | } 93 | public function __unset($k) 94 | { 95 | unset($this->_values[$k]); 96 | $this->_transientValues->add($k); 97 | $this->_unsavedValues->discard($k); 98 | } 99 | public function &__get($k) 100 | { 101 | // function should return a reference, using $nullval to return a reference to null 102 | $nullval = null; 103 | if (array_key_exists($k, $this->_values)) { 104 | return $this->_values[$k]; 105 | } else if ($this->_transientValues->includes($k)) { 106 | $class = get_class($this); 107 | $attrs = join(', ', array_keys($this->_values)); 108 | $message = "Ads Notice: Undefined property of $class instance: $k. " 109 | . "HINT: The $k attribute was set in the past, however. " 110 | . "It was then wiped when refreshing the object " 111 | . "with the result returned by Ads's API, " 112 | . "probably as a result of a save(). The attributes currently " 113 | . "available on this object are: $attrs"; 114 | error_log($message); 115 | return $nullval; 116 | } else { 117 | $class = get_class($this); 118 | error_log("Ads Notice: Undefined property of $class instance: $k"); 119 | return $nullval; 120 | } 121 | } 122 | // ArrayAccess methods 123 | public function offsetSet($k, $v) 124 | { 125 | $this->$k = $v; 126 | } 127 | public function offsetExists($k) 128 | { 129 | return array_key_exists($k, $this->_values); 130 | } 131 | public function offsetUnset($k) 132 | { 133 | unset($this->$k); 134 | } 135 | public function offsetGet($k) 136 | { 137 | return array_key_exists($k, $this->_values) ? $this->_values[$k] : null; 138 | } 139 | public function keys() 140 | { 141 | return array_keys($this->_values); 142 | } 143 | /** 144 | * This unfortunately needs to be public to be used in Util\Util 145 | * 146 | * @param array $values 147 | * @param array $opts 148 | * 149 | * @return AdsObject The object constructed from the given values. 150 | */ 151 | public static function constructFrom($values, $opts) 152 | { 153 | $obj = new static(isset($values['id']) ? $values['id'] : null); 154 | $obj->refreshFrom($values, $opts); 155 | return $obj; 156 | } 157 | /** 158 | * Refreshes this object using the provided values. 159 | * 160 | * @param array $values 161 | * @param array $opts 162 | * @param boolean $partial Defaults to false. 163 | */ 164 | public function refreshFrom($values, $opts, $partial = false) 165 | { 166 | $this->_opts = $opts; 167 | // Wipe old state before setting new. This is useful for e.g. updating a 168 | // customer, where there is no persistent card parameter. Mark those values 169 | // which don't persist as transient 170 | if ($partial) { 171 | $removed = new Util\Set(); 172 | } else { 173 | $removed = array_diff(array_keys($this->_values), array_keys($values)); 174 | } 175 | foreach ($removed as $k) { 176 | if (self::$permanentAttributes->includes($k)) { 177 | continue; 178 | } 179 | unset($this->$k); 180 | } 181 | foreach ($values as $k => $v) { 182 | if (self::$permanentAttributes->includes($k) && isset($this[$k])) { 183 | continue; 184 | } 185 | if (self::$nestedUpdatableAttributes->includes($k) && is_array($v)) { 186 | $this->_values[$k] = AttachedObject::constructFrom($v, $opts); 187 | } else { 188 | $this->_values[$k] = Util\Util::convertToAdsObject($v, $opts); 189 | } 190 | $this->_transientValues->discard($k); 191 | $this->_unsavedValues->discard($k); 192 | } 193 | } 194 | /** 195 | * @return array A recursive mapping of attributes to values for this object, 196 | * including the proper value for deleted attributes. 197 | */ 198 | public function serializeParameters() 199 | { 200 | $params = array(); 201 | if ($this->_unsavedValues) { 202 | foreach ($this->_unsavedValues->toArray() as $k) { 203 | $v = $this->$k; 204 | if ($v === null) { 205 | $v = ''; 206 | } 207 | $params[$k] = $v; 208 | } 209 | } 210 | // Get nested updates. 211 | foreach (self::$nestedUpdatableAttributes->toArray() as $property) { 212 | if (isset($this->$property)) { 213 | if ($this->$property instanceof AdsObject) { 214 | $serialized = $this->$property->serializeParameters(); 215 | if ($serialized) { 216 | $params[$property] = $serialized; 217 | } 218 | } 219 | } 220 | } 221 | return $params; 222 | } 223 | public function jsonSerialize() 224 | { 225 | return $this->__toArray(true); 226 | } 227 | public function __toJSON() 228 | { 229 | if (defined('JSON_PRETTY_PRINT')) { 230 | return json_encode($this->__toArray(true), JSON_PRETTY_PRINT); 231 | } else { 232 | return json_encode($this->__toArray(true)); 233 | } 234 | } 235 | public function __toString() 236 | { 237 | $class = get_class($this); 238 | return $class . ' JSON: ' . $this->__toJSON(); 239 | } 240 | public function __toArray($recursive = false) 241 | { 242 | if ($recursive) { 243 | return Util\Util::convertAdsObjectToArray($this->_values); 244 | } else { 245 | return $this->_values; 246 | } 247 | } 248 | } 249 | AdsObject::init(); -------------------------------------------------------------------------------- /lib/ApiRequestor.php: -------------------------------------------------------------------------------- 1 | _apiKey = $apiKey; 16 | if (!$apiBase) { 17 | $apiBase = Ads::$apiBase; 18 | } 19 | $this->_apiBase = $apiBase; 20 | } 21 | 22 | private static function _encodeObjects($d) 23 | { 24 | if ($d instanceof ApiResource) { 25 | return Util\Util::utf8($d->id); 26 | } elseif ($d === true) { 27 | return 'true'; 28 | } elseif ($d === false) { 29 | return 'false'; 30 | } elseif (is_array($d)) { 31 | $res = array(); 32 | foreach ($d as $k => $v) { 33 | $res[$k] = self::_encodeObjects($v); 34 | } 35 | return $res; 36 | } else { 37 | return Util\Util::utf8($d); 38 | } 39 | } 40 | 41 | /** 42 | * @param string $method 43 | * @param string $url 44 | * @param array|null $params 45 | * @param array|null $headers 46 | * 47 | * @return array An array whose first element is an API response and second 48 | * element is the API key used to make the request. 49 | */ 50 | public function request($method, $url, $params = null, $headers = null) 51 | { 52 | if (!$params) { 53 | $params = array(); 54 | } 55 | if (!$headers) { 56 | $headers = array(); 57 | } 58 | list($rbody, $rcode, $rheaders, $myApiKey) = 59 | $this->_requestRaw($method, $url, $params, $headers); 60 | $json = $this->_interpretResponse($rbody, $rcode, $rheaders); 61 | $resp = new ApiResponse($rbody, $rcode, $rheaders, $json); 62 | return array($resp, $myApiKey); 63 | } 64 | 65 | /** 66 | * @param string $rbody A JSON string. 67 | * @param int $rcode 68 | * @param array $rheaders 69 | * @param array $resp 70 | * 71 | * @throws Error\InvalidRequest if the error is caused by the user. 72 | * @throws Error\Authentication if the error is caused by a lack of 73 | * permissions. 74 | * @throws Error\Card if the error is the error code is 402 (payment 75 | * required) 76 | * @throws Error\RateLimit if the error is caused by too many requests 77 | * hitting the API. 78 | * @throws Error\Api otherwise. 79 | */ 80 | public function handleApiError($rbody, $rcode, $rheaders, $resp) 81 | { 82 | if (!is_array($resp) || !isset($resp['error'])) { 83 | $msg = "Invalid response object from API: $rbody " 84 | . "(HTTP response code was $rcode)"; 85 | throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); 86 | } 87 | 88 | $error = $resp['error']; 89 | $msg = isset($resp['error']) ? $resp['error'] : null; 90 | $param = isset($error['param']) ? $error['param'] : null; 91 | $code = isset($resp['code']) ? $resp['code'] : null; 92 | 93 | switch ($rcode) { 94 | case 400: 95 | // 'rate_limit' code is deprecated, but left here for backwards compatibility 96 | // for API versions earlier than 2015-09-08 97 | if ($code == 'rate_limit') { 98 | throw new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); 99 | } 100 | 101 | // intentional fall-through 102 | case 404: 103 | throw new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders); 104 | case 401: 105 | throw new Error\Authentication($msg, $rcode, $rbody, $resp, $rheaders); 106 | case 402: 107 | throw new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders); 108 | case 429: 109 | throw new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); 110 | default: 111 | throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); 112 | } 113 | } 114 | 115 | private function _requestRaw($method, $url, $params, $headers) 116 | { 117 | $myApiKey = $this->_apiKey; 118 | if (!$myApiKey) { 119 | $myApiKey = Ads::$apiKey; 120 | } 121 | 122 | if (!$myApiKey) { 123 | $msg = 'No API key provided. (HINT: set your API key using ' 124 | . '"Ads::setApiKey()". You can generate API keys from ' 125 | . 'the Ads web interface. See https://api.atgames.net/doc for ' 126 | . 'details, or email support@atgames.net if you have any questions.'; 127 | throw new Error\Authentication($msg); 128 | } 129 | //Signature params 130 | if (count($params) > 0) { 131 | $myApiKeys = explode(":", base64_decode($myApiKey)); 132 | Util\Util::arraySortByKey($params); 133 | $signed_data = Util\Util::flattenArray($params) . "|" . $myApiKeys[1]; 134 | $params['sig'] = hash('sha256', $signed_data); 135 | } 136 | 137 | $absUrl = $this->_apiBase.$url; 138 | $params = self::_encodeObjects($params); 139 | $langVersion = phpversion(); 140 | $uname = php_uname(); 141 | $ua = array( 142 | 'bindings_version' => Ads::VERSION, 143 | 'lang' => 'php', 144 | 'lang_version' => $langVersion, 145 | 'publisher' => 'atgames', 146 | 'uname' => $uname, 147 | ); 148 | $defaultHeaders = array( 149 | 'X-Ads-Client-User-Agent' => json_encode($ua), 150 | 'User-Agent' => 'Ads/v1 PhpBindings/' . Ads::VERSION, 151 | 'Authorization' => 'Bearer ' . $myApiKey, 152 | ); 153 | if (Ads::$apiVersion) { 154 | $defaultHeaders['Ads-Version'] = Ads::$apiVersion; 155 | } 156 | 157 | if (Ads::$accountId) { 158 | $defaultHeaders['Ads-Account'] = Ads::$accountId; 159 | } 160 | 161 | $hasFile = false; 162 | $hasCurlFile = class_exists('\CURLFile', false); 163 | foreach ($params as $k => $v) { 164 | if (is_resource($v)) { 165 | $hasFile = true; 166 | $params[$k] = self::_processResourceParam($v, $hasCurlFile); 167 | } elseif ($hasCurlFile && $v instanceof \CURLFile) { 168 | $hasFile = true; 169 | } 170 | } 171 | 172 | if ($hasFile) { 173 | $defaultHeaders['Content-Type'] = 'multipart/form-data'; 174 | } else { 175 | $defaultHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; 176 | } 177 | 178 | $combinedHeaders = array_merge($defaultHeaders, $headers); 179 | $rawHeaders = array(); 180 | 181 | foreach ($combinedHeaders as $header => $value) { 182 | $rawHeaders[] = $header . ': ' . $value; 183 | } 184 | 185 | list($rbody, $rcode, $rheaders) = $this->httpClient()->request( 186 | $method, 187 | $absUrl, 188 | $rawHeaders, 189 | $params, 190 | $hasFile 191 | ); 192 | return array($rbody, $rcode, $rheaders, $myApiKey); 193 | } 194 | 195 | private function _processResourceParam($resource, $hasCurlFile) 196 | { 197 | if (get_resource_type($resource) !== 'stream') { 198 | throw new Error\Api( 199 | 'Attempted to upload a resource that is not a stream' 200 | ); 201 | } 202 | 203 | $metaData = stream_get_meta_data($resource); 204 | if ($metaData['wrapper_type'] !== 'plainfile') { 205 | throw new Error\Api( 206 | 'Only plainfile resource streams are supported' 207 | ); 208 | } 209 | 210 | if ($hasCurlFile) { 211 | // We don't have the filename or mimetype, but the API doesn't care 212 | return new \CURLFile($metaData['uri']); 213 | } else { 214 | return '@'.$metaData['uri']; 215 | } 216 | } 217 | 218 | private function _interpretResponse($rbody, $rcode, $rheaders) 219 | { 220 | try { 221 | $resp = json_decode($rbody, true); 222 | } catch (Exception $e) { 223 | $msg = "Invalid response body from API: $rbody " 224 | . "(HTTP response code was $rcode)"; 225 | throw new Error\Api($msg, $rcode, $rbody); 226 | } 227 | 228 | if ($rcode < 200 || $rcode >= 300) { 229 | $this->handleApiError($rbody, $rcode, $rheaders, $resp); 230 | } 231 | return $resp; 232 | } 233 | 234 | public static function setHttpClient($client) 235 | { 236 | self::$_httpClient = $client; 237 | } 238 | 239 | private function httpClient() 240 | { 241 | if (!self::$_httpClient) { 242 | self::$_httpClient = HttpClient\CurlClient::instance(); 243 | } 244 | return self::$_httpClient; 245 | } 246 | } --------------------------------------------------------------------------------