├── 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 | }
--------------------------------------------------------------------------------