From 6066b1171b99eb0c81455c1883f6be51af60dd8d Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 6 Jan 2016 20:22:07 +0100 Subject: [PATCH 01/18] Set version to 3.1.0-dev --- CHANGELOG | 2 ++ README.md | 4 ++-- bower.json | 2 +- src/codebird.php | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d1af2ce..a33af46 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ codebird-php - changelog ======================== +3.1.0 (not yet released) + 3.0.0 (2016-01-01) + Add unit testing suite + #32 Support Twitter Streaming API diff --git a/README.md b/README.md index 0c17980..ed53f49 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -[![Coverage Status](https://img.shields.io/coveralls/jublonet/codebird-php/master.svg)](https://coveralls.io/github/jublonet/codebird-php?branch=master) -[![Travis Status](https://img.shields.io/travis/jublonet/codebird-php/master.svg)](https://travis-ci.org/jublonet/codebird-php/branches) +[![Coverage Status](https://img.shields.io/coveralls/jublonet/codebird-php/develop.svg)](https://coveralls.io/github/jublonet/codebird-php?branch=develop) +[![Travis Status](https://img.shields.io/travis/jublonet/codebird-php/develop.svg)](https://travis-ci.org/jublonet/codebird-php/branches) ### Requirements diff --git a/bower.json b/bower.json index 30a368a..bc1bdd2 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "codebird-php", - "version": "3.0.0", + "version": "3.1.0-dev", "homepage": "https://www.jublo.net/projects/codebird/php", "authors": [ "Joshua Atkins ", diff --git a/src/codebird.php b/src/codebird.php index abfa0b0..0756bea 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -6,7 +6,7 @@ * A Twitter library in PHP. * * @package codebird - * @version 3.0.0 + * @version 3.1.0-dev * @author Jublo Solutions * @copyright 2010-2016 Jublo Solutions * @license https://opensource.org/licenses/GPL-3.0 GNU General Public License 3.0 @@ -505,7 +505,7 @@ class Codebird /** * The current Codebird version */ - protected static $_version = '3.0.0'; + protected static $_version = '3.1.0-dev'; /** * The Request or access token. Used to sign requests From 5c41f3fc90188bf210096ed3822c575483c672dc Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 6 Jan 2016 20:22:51 +0100 Subject: [PATCH 02/18] Add support for proxy types See #143. --- CHANGELOG | 1 + README.md | 7 +++++++ src/codebird.php | 28 ++++++++++++++++++++++++---- test/setter_tests.php | 17 +++++++++++++++++ 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a33af46..85f929e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ codebird-php - changelog ======================== 3.1.0 (not yet released) ++ #143 Add support for proxy types 3.0.0 (2016-01-01) + Add unit testing suite diff --git a/README.md b/README.md index ed53f49..f5ea038 100644 --- a/README.md +++ b/README.md @@ -840,3 +840,10 @@ You may also use an authenticated proxy. Use the following call: $cb->setProxy('', ''); $cb->setProxyAuthentication(':'); ``` + +By default, a HTTP proxy is assumed. To use a different proxy type, +use the corresponding [`CURLPROXY_*` constants](http://php.net/curl_setopt), like this: + +```php +$cb->setProxy('', '', CURLPROXY_SOCKS5); +``` diff --git a/src/codebird.php b/src/codebird.php index 0756bea..23fdec7 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -713,15 +713,25 @@ public function setReturnFormat($return_format) /** * Sets the proxy * - * @param string $host Proxy host - * @param int $port Proxy port + * @param string $host Proxy host + * @param int $port Proxy port + * @param int optional $type Proxy type, defaults to HTTP * * @return void */ - public function setProxy($host, $port) + public function setProxy($host, $port, $type = CURLPROXY_HTTP) { $this->_proxy['host'] = $host; $this->_proxy['port'] = (int) $port; + + static $types = [ + CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS5, + CURLPROXY_SOCKS4A, CURLPROXY_SOCKS5_HOSTNAME + ]; + if (! in_array($type, $types)) { + throw new \Exception('Invalid proxy type specified.'); + } + $this->_proxy['type'] = $type; } /** @@ -1053,7 +1063,7 @@ protected function _getCurlInitialization($url) ); if ($this->_hasProxy()) { - $this->_curl_setopt($connection, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + $this->_curl_setopt($connection, CURLOPT_PROXYTYPE, $this->_getProxyType()); $this->_curl_setopt($connection, CURLOPT_PROXY, $this->_getProxyHost()); $this->_curl_setopt($connection, CURLOPT_PROXYPORT, $this->_getProxyPort()); @@ -1159,6 +1169,16 @@ protected function _getProxyAuthentication() return $this->_getProxyData('authentication'); } + /** + * Gets the proxy type + * + * @return string The proxy type + */ + protected function _getProxyType() + { + return $this->_getProxyData('type'); + } + /** * Gets data from the proxy configuration * diff --git a/test/setter_tests.php b/test/setter_tests.php index 80be154..ae810d8 100644 --- a/test/setter_tests.php +++ b/test/setter_tests.php @@ -150,6 +150,23 @@ public function testSetProxy() $cb->setProxy('127.0.0.1', '8888'); $this->assertEquals('127.0.0.1', $cb->get('_proxy')['host']); $this->assertEquals('8888', $cb->get('_proxy')['port']); + $this->assertEquals(CURLPROXY_HTTP, $cb->get('_proxy')['type']); + + $cb->setProxy('127.0.0.1', '8888', CURLPROXY_SOCKS5); + $this->assertEquals('127.0.0.1', $cb->get('_proxy')['host']); + $this->assertEquals('8888', $cb->get('_proxy')['port']); + $this->assertEquals(CURLPROXY_SOCKS5, $cb->get('_proxy')['type']); + } + + /** + * Tests setProxy + * @expectedException \Exception + * @expectedExceptionMessage Invalid proxy type specified. + */ + public function testSetProxy2() + { + $cb = new CodebirdT(); + $cb->setProxy('127.0.0.1', '8888', 1); } /** From 481f74f0ab008633bfe02e5b27988d646f4d27bc Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 6 Jan 2016 20:31:36 +0100 Subject: [PATCH 03/18] Fix version test regex --- test/setter_tests.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/setter_tests.php b/test/setter_tests.php index ae810d8..982a1b8 100644 --- a/test/setter_tests.php +++ b/test/setter_tests.php @@ -49,7 +49,7 @@ public function testGetVersion() $cb = new CodebirdT(); $version = $cb->getVersion(); $this->assertEquals($version, $cb->getStatic('_version')); - $this->assertRegexp('/^[1-9]\d*\.\d+\.\d+(-(([a-z]+\.[1-9]\d*))|dev)?$/', $version); + $this->assertRegexp('/^[1-9]\d*\.\d+\.\d+(-([a-z]+\.[1-9]\d*|dev))?$/', $version); } /** From 5b0f9994a5ed30332576173f40becfc5042d8b85 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 6 Jan 2016 20:53:00 +0100 Subject: [PATCH 04/18] Check that CURLPROXY_SOCKS4A is available --- src/codebird.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index 23fdec7..522539e 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -721,16 +721,19 @@ public function setReturnFormat($return_format) */ public function setProxy($host, $port, $type = CURLPROXY_HTTP) { - $this->_proxy['host'] = $host; - $this->_proxy['port'] = (int) $port; - - static $types = [ + $types = [ CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS5, - CURLPROXY_SOCKS4A, CURLPROXY_SOCKS5_HOSTNAME + CURLPROXY_SOCKS5_HOSTNAME ]; + if (defined('CURLPROXY_SOCKS4A')) { + $types[] = CURLPROXY_SOCKS4A; + } if (! in_array($type, $types)) { throw new \Exception('Invalid proxy type specified.'); } + + $this->_proxy['host'] = $host; + $this->_proxy['port'] = (int) $port; $this->_proxy['type'] = $type; } From eb713dfcfaf6118d536cf465ef28eda7dbc1a3c8 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 6 Jan 2016 20:55:59 +0100 Subject: [PATCH 05/18] Check that CURLPROXY_SOCKS5_HOSTNAME is available --- src/codebird.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/codebird.php b/src/codebird.php index 522539e..3d1277a 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -725,9 +725,14 @@ public function setProxy($host, $port, $type = CURLPROXY_HTTP) CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS5, CURLPROXY_SOCKS5_HOSTNAME ]; + // PHP 7.0+, keep this as long as PHP 5.6 is supported if (defined('CURLPROXY_SOCKS4A')) { $types[] = CURLPROXY_SOCKS4A; } + // PHP 7.0+, keep this as long as PHP 5.6 is supported + if (defined('CURLPROXY_SOCKS5_HOSTNAME')) { + $types[] = CURLPROXY_SOCKS5_HOSTNAME; + } if (! in_array($type, $types)) { throw new \Exception('Invalid proxy type specified.'); } From 3a75095f6ca184e17316f1ecabe34cd35e89e95d Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 6 Jan 2016 20:57:51 +0100 Subject: [PATCH 06/18] Amend CURLPROXY_SOCKS5_HOSTNAME check --- src/codebird.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index 3d1277a..ac5e20e 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -722,8 +722,7 @@ public function setReturnFormat($return_format) public function setProxy($host, $port, $type = CURLPROXY_HTTP) { $types = [ - CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS5, - CURLPROXY_SOCKS5_HOSTNAME + CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS5 ]; // PHP 7.0+, keep this as long as PHP 5.6 is supported if (defined('CURLPROXY_SOCKS4A')) { From 300973d176ac3739b21853707fc43ceb3e457d07 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 6 Jan 2016 21:03:37 +0100 Subject: [PATCH 07/18] Dynamically populate the proxy types list This also fixes the HHVM build. --- src/codebird.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index ac5e20e..c37c0fe 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -721,16 +721,14 @@ public function setReturnFormat($return_format) */ public function setProxy($host, $port, $type = CURLPROXY_HTTP) { - $types = [ - CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS5 + static $types_str = [ + 'HTTP', 'SOCKS4', 'SOCKS5', 'SOCKS4A', 'SOCKS5_HOSTNAME' ]; - // PHP 7.0+, keep this as long as PHP 5.6 is supported - if (defined('CURLPROXY_SOCKS4A')) { - $types[] = CURLPROXY_SOCKS4A; - } - // PHP 7.0+, keep this as long as PHP 5.6 is supported - if (defined('CURLPROXY_SOCKS5_HOSTNAME')) { - $types[] = CURLPROXY_SOCKS5_HOSTNAME; + $types = []; + foreach ($types_str as $type_str) { + if (defined('CURLPROXY_' . $type_str)) { + $types[] = constant('CURLPROXY_' . $type_str); + } } if (! in_array($type, $types)) { throw new \Exception('Invalid proxy type specified.'); From 9c9a5206d9f32d1e6d5b4e94f6698e8799af2338 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 6 Jan 2016 21:20:33 +0100 Subject: [PATCH 08/18] Throw Exception on failed remote media download Fix #152 --- CHANGELOG | 1 + src/codebird.php | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 85f929e..909aa19 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ codebird-php - changelog 3.1.0 (not yet released) + #143 Add support for proxy types ++ #152 Throw Exception on failed remote media download 3.0.0 (2016-01-01) + Add unit testing suite diff --git a/src/codebird.php b/src/codebird.php index c37c0fe..3dd837a 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -1826,6 +1826,7 @@ protected function _fetchRemoteFile($url) if ($result !== false) { return $result; } + throw new \Exception('Downloading a remote media file failed.'); return false; } // no cURL From 6b6ad74078dac7880ceb49705f00205ea9360e9c Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 6 Jan 2016 21:20:51 +0100 Subject: [PATCH 09/18] Add PHPDoc for methods that throw an Exception --- src/codebird.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/codebird.php b/src/codebird.php index 3dd837a..ee6cf6a 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -640,6 +640,7 @@ public function logout() * @param bool $use_curl Request uses cURL or not * * @return void + * @throws \Exception */ public function setUseCurl($use_curl) { @@ -718,6 +719,7 @@ public function setReturnFormat($return_format) * @param int optional $type Proxy type, defaults to HTTP * * @return void + * @throws \Exception */ public function setProxy($host, $port, $type = CURLPROXY_HTTP) { @@ -757,6 +759,7 @@ public function setProxyAuthentication($authentication) * @param callable $callback The streaming callback * * @return void + * @throws \Exception */ public function setStreamingCallback($callback) { @@ -960,6 +963,7 @@ protected function _mapFnRestoreParamUnderscores($method) * @param array byref $apiparams The parameters to send along * * @return string[] (string method, string method_template) + * @throws \Exception */ protected function _mapFnInlineParams($method, &$apiparams) { @@ -1002,6 +1006,7 @@ protected function _mapFnInlineParams($method, &$apiparams) * @param optional string $type 'authenticate' or 'authorize', to avoid duplicate code * * @return string The OAuth authenticate/authorize URL + * @throws \Exception */ public function oauth_authenticate($force_login = NULL, $screen_name = NULL, $type = 'authenticate') { @@ -1198,6 +1203,7 @@ private function _getProxyData($name) * Gets the OAuth bearer token, using cURL * * @return string The OAuth bearer token + * @throws \Exception */ protected function _oauth2TokenCurl() @@ -1237,6 +1243,7 @@ protected function _oauth2TokenCurl() * Gets the OAuth bearer token, without cURL * * @return string The OAuth bearer token + * @throws \Exception */ protected function _oauth2TokenNoCurl() @@ -1370,6 +1377,7 @@ protected function _getRateLimitInfo($headers) * @param int $validation_result The curl error number * * @return void + * @throws \Exception */ protected function _validateSslCertificate($validation_result) { @@ -1435,6 +1443,7 @@ protected function _url($data) * @param string $data The data to calculate the hash from * * @return string The hash + * @throws \Exception */ protected function _sha1($data) { @@ -1463,6 +1472,7 @@ protected function _sha1($data) * @param int optional $length The length of the string to generate * * @return string The random string + * @throws \Exception */ protected function _nonce($length = 8) { @@ -1508,6 +1518,7 @@ protected function _getSignature($httpmethod, $method, $base_params) * @param array optional $params The API call parameters, associative * * @return string Authorization HTTP header + * @throws \Exception */ protected function _sign($httpmethod, $method, $params = []) { @@ -1691,6 +1702,7 @@ protected function _detectMultipart($method) * @param array $params The parameters to send along * * @return string request + * @throws \Exception */ protected function _getMultipartRequestFromParams($method_template, $border, $params) { @@ -1805,6 +1817,7 @@ protected function _buildBinaryBody($input) * @param string $url The URL to download from * * @return mixed The file contents or FALSE + * @throws \Exception */ protected function _fetchRemoteFile($url) { @@ -1955,6 +1968,7 @@ protected function _getEndpoint($method, $method_template) * @param bool optional $app_only_auth Whether to use app-only bearer authentication * * @return string The API reply, encoded in the set return_format + * @throws \Exception */ protected function _callApi($httpmethod, $method, $method_template, $params = [], $multipart = false, $app_only_auth = false) @@ -1987,6 +2001,7 @@ protected function _callApi($httpmethod, $method, $method_template, $params = [] * @param bool optional $app_only_auth Whether to use app-only bearer authentication * * @return string The API reply, encoded in the set return_format + * @throws \Exception */ protected function _callApiCurl( @@ -2047,6 +2062,7 @@ protected function _callApiCurl( * @param bool optional $app_only_auth Whether to use app-only bearer authentication * * @return string The API reply, encoded in the set return_format + * @throws \Exception */ protected function _callApiNoCurl( @@ -2221,6 +2237,7 @@ protected function _appendHttpStatusAndRate($reply, $httpstatus, $rate) * Get Bearer authorization string * * @return string authorization + * @throws \Exception */ protected function _getBearerAuthorization() { @@ -2282,6 +2299,7 @@ protected function _callApiPreparations( * @param bool optional $app_only_auth Whether to use app-only bearer authentication * * @return void + * @throws \Exception */ protected function _callApiStreaming( From 5682f3758ae865e8eec0ffc3aaf169e9f7062256 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 6 Jan 2016 21:58:10 +0100 Subject: [PATCH 10/18] Add tests for _fetchRemoteFile --- src/codebird.php | 11 ++++++--- test/README | 1 - test/codebirdm.php | 13 +++++++++- test/media_tests.php | 56 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 test/media_tests.php diff --git a/src/codebird.php b/src/codebird.php index ee6cf6a..d0c726b 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -1836,7 +1836,9 @@ protected function _fetchRemoteFile($url) // process compressed images $this->_curl_setopt($connection, CURLOPT_ENCODING, 'gzip,deflate,sdch'); $result = $this->_curl_exec($connection); - if ($result !== false) { + if ($result !== false + && $this->_curl_getinfo($connection, CURLINFO_HTTP_CODE) === 200 + ) { return $result; } throw new \Exception('Downloading a remote media file failed.'); @@ -1853,10 +1855,13 @@ protected function _fetchRemoteFile($url) 'verify_peer' => false ] ]; - list($result) = $this->_getNoCurlInitialization($url, $contextOptions); - if ($result !== false) { + list($result, $headers) = $this->_getNoCurlInitialization($url, $contextOptions); + if ($result !== false + && preg_match('/^HTTP\/\d\.\d 200 OK$/', $headers[0]) + ) { return $result; } + throw new \Exception('Downloading a remote media file failed.'); return false; } diff --git a/test/README b/test/README index 515cdd7..fd344c4 100644 --- a/test/README +++ b/test/README @@ -4,7 +4,6 @@ _getMultipartRequestFromParams _checkForFiles _buildMultipart _buildBinaryBody -_fetchRemoteFile _callApi _callApiCurl diff --git a/test/codebirdm.php b/test/codebirdm.php index e340f21..6cbae4a 100644 --- a/test/codebirdm.php +++ b/test/codebirdm.php @@ -37,9 +37,13 @@ class CodebirdM extends CodebirdT 'httpstatus' => 404, 'reply' => "HTTP/1.1 200 Connection Established\r\n\r\nHTTP/1.1 404 Not Found\r\ncontent-length: 68\r\ncontent-type: application/json;charset=utf-8\r\ndate: Sun, 06 Dec 2015 14:43:28 GMT\r\nserver: tsa_b\r\nset-cookie: guest_id=v1%3A144941300885288055; Domain=.twitter.com; Path=/; Expires=Tue, 05-Dec-2017 14:43:28 UTC\r\nstrict-transport-security: max-age=631138519\r\nx-connection-hash: 12218aef9e9757609afb08e661fa3b9b\r\nx-response-time: 2\r\n\r\n{\"errors\":[{\"message\":\"Sorry, that page does not exist\",\"code\":34}]}" ], + 'GET http://www.example.org/found.txt' => [ + 'httpstatus' => 200, + 'reply' => "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nA test file." + ], 'GET https://api.twitter.com/1.1/users/show.json?screen_name=TwitterAPI' => [ 'httpstatus' => 200, - 'reply' => "HTTP/1.1 200 OKcache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0\r\ncontent-disposition: attachment; filename=json.json\r\ncontent-length: 2801\r\ncontent-type: application/json;charset=utf-8\r\ndate: Sun, 06 Dec 2015 14:55:46 GMT\r\nexpires: Tue, 31 Mar 1981 05:00:00 GMT\r\nlast-modified: Sun, 06 Dec 2015 14:55:46 GMT\r\npragma: no-cache\r\nserver: tsa_b\r\nset-cookie: lang=en-gb; Path=/\r\nset-cookie: guest_id=v1%3A144941374684866365; Domain=.twitter.com; Path=/; Expires=Tue, 05-Dec-2017 14:55:46 UTC\r\nstatus: 200 OK\r\nstrict-transport-security: max-age=631138519\r\nx-access-level: read-write-directmessages\r\nx-connection-hash: 1906b689730b92318bccf65b496f74d0\r\nx-content-type-options: nosniff\r\nx-frame-options: SAMEORIGIN\r\nx-rate-limit-limit: 181\r\nx-rate-limit-remaining: 177\r\nx-rate-limit-reset: 1449414513\r\nx-response-time: 44\r\nx-transaction: 663cc05c64857ba0\r\nx-twitter-response-tags: BouncerCompliant\r\nx-xss-protection: 1; mode=block\r\n\r\n{\"id\":6253282,\"id_str\":\"6253282\",\"name\":\"Twitter API\",\"screen_name\":\"twitterapi\",\"location\":\"San Francisco, CA\",\"profile_location\":null,\"description\":\"The Real Twitter API. I tweet about API changes, service issues and happily answer questions about Twitter and our API. Don't get an answer? It's on my website.\",\"url\":\"http:\/\/t.co\/78pYTvWfJd\",\"entities\":{\"url\":{\"urls\":[{\"url\":\"http:\/\/t.co\/78pYTvWfJd\",\"expanded_url\":\"http:\/\/dev.twitter.com\",\"display_url\":\"dev.twitter.com\",\"indices\":[0,22]}]},\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":4993679,\"friends_count\":48,\"listed_count\":13001,\"created_at\":\"Wed May 23 06:01:13 +0000 2007\",\"favourites_count\":27,\"utc_offset\":-28800,\"time_zone\":\"Pacific Time (US & Canada)\",\"geo_enabled\":true,\"verified\":true,\"statuses_count\":3553,\"lang\":\"en\",\"status\":{\"created_at\":\"Tue Nov 24 08:56:07 +0000 2015\",\"id\":669077021138493440,\"id_str\":\"669077021138493440\",\"text\":\"Additional 64-bit entity ID migration coming in Feb 2016 https:\/\/t.co\/eQIGvw1rsJ\",\"source\":\"\u003ca href=\\\"https:\/\/about.twitter.com\/products\/tweetdeck\\\" rel=\\\"nofollow\\\"\u003eTweetDeck\u003c\/a\u003e\",\"truncated\":false,\"in_reply_to_status_id\":null,\"in_reply_to_status_id_str\":null,\"in_reply_to_user_id\":null,\"in_reply_to_user_id_str\":null,\"in_reply_to_screen_name\":null,\"geo\":null,\"coordinates\":null,\"place\":null,\"contributors\":null,\"retweet_count\":67,\"favorite_count\":79,\"entities\":{\"hashtags\":[],\"symbols\":[],\"user_mentions\":[],\"urls\":[{\"url\":\"https:\/\/t.co\/eQIGvw1rsJ\",\"expanded_url\":\"https:\/\/twittercommunity.com\/t\/migration-of-twitter-core-entities-to-64-bit-ids\/56881\",\"display_url\":\"twittercommunity.com\/t\/migration-of\u2026\",\"indices\":[57,80]}]},\"favorited\":false,\"retweeted\":false,\"possibly_sensitive\":false,\"lang\":\"en\"},\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\/\/pbs.twimg.com\/profile_background_images\/656927849\/miyt9dpjz77sc0w3d4vj.png\",\"profile_background_image_url_https\":\"https:\/\/pbs.twimg.com\/profile_background_images\/656927849\/miyt9dpjz77sc0w3d4vj.png\",\"profile_background_tile\":true,\"profile_image_url\":\"http:\/\/pbs.twimg.com\/profile_images\/2284174872\/7df3h38zabcvjylnyfe3_normal.png\",\"profile_image_url_https\":\"https:\/\/pbs.twimg.com\/profile_images\/2284174872\/7df3h38zabcvjylnyfe3_normal.png\",\"profile_banner_url\":\"https:\/\/pbs.twimg.com\/profile_banners\/6253282\/1431474710\",\"profile_link_color\":\"0084B4\",\"profile_sidebar_border_color\":\"C0DEED\",\"profile_sidebar_fill_color\":\"DDEEF6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":false,\"default_profile\":false,\"default_profile_image\":false,\"following\":true,\"follow_request_sent\":false,\"notifications\":false}" + 'reply' => "HTTP/1.1 200 OK\r\ncache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0\r\ncontent-disposition: attachment; filename=json.json\r\ncontent-length: 2801\r\ncontent-type: application/json;charset=utf-8\r\ndate: Sun, 06 Dec 2015 14:55:46 GMT\r\nexpires: Tue, 31 Mar 1981 05:00:00 GMT\r\nlast-modified: Sun, 06 Dec 2015 14:55:46 GMT\r\npragma: no-cache\r\nserver: tsa_b\r\nset-cookie: lang=en-gb; Path=/\r\nset-cookie: guest_id=v1%3A144941374684866365; Domain=.twitter.com; Path=/; Expires=Tue, 05-Dec-2017 14:55:46 UTC\r\nstatus: 200 OK\r\nstrict-transport-security: max-age=631138519\r\nx-access-level: read-write-directmessages\r\nx-connection-hash: 1906b689730b92318bccf65b496f74d0\r\nx-content-type-options: nosniff\r\nx-frame-options: SAMEORIGIN\r\nx-rate-limit-limit: 181\r\nx-rate-limit-remaining: 177\r\nx-rate-limit-reset: 1449414513\r\nx-response-time: 44\r\nx-transaction: 663cc05c64857ba0\r\nx-twitter-response-tags: BouncerCompliant\r\nx-xss-protection: 1; mode=block\r\n\r\n{\"id\":6253282,\"id_str\":\"6253282\",\"name\":\"Twitter API\",\"screen_name\":\"twitterapi\",\"location\":\"San Francisco, CA\",\"profile_location\":null,\"description\":\"The Real Twitter API. I tweet about API changes, service issues and happily answer questions about Twitter and our API. Don't get an answer? It's on my website.\",\"url\":\"http:\/\/t.co\/78pYTvWfJd\",\"entities\":{\"url\":{\"urls\":[{\"url\":\"http:\/\/t.co\/78pYTvWfJd\",\"expanded_url\":\"http:\/\/dev.twitter.com\",\"display_url\":\"dev.twitter.com\",\"indices\":[0,22]}]},\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":4993679,\"friends_count\":48,\"listed_count\":13001,\"created_at\":\"Wed May 23 06:01:13 +0000 2007\",\"favourites_count\":27,\"utc_offset\":-28800,\"time_zone\":\"Pacific Time (US & Canada)\",\"geo_enabled\":true,\"verified\":true,\"statuses_count\":3553,\"lang\":\"en\",\"status\":{\"created_at\":\"Tue Nov 24 08:56:07 +0000 2015\",\"id\":669077021138493440,\"id_str\":\"669077021138493440\",\"text\":\"Additional 64-bit entity ID migration coming in Feb 2016 https:\/\/t.co\/eQIGvw1rsJ\",\"source\":\"\u003ca href=\\\"https:\/\/about.twitter.com\/products\/tweetdeck\\\" rel=\\\"nofollow\\\"\u003eTweetDeck\u003c\/a\u003e\",\"truncated\":false,\"in_reply_to_status_id\":null,\"in_reply_to_status_id_str\":null,\"in_reply_to_user_id\":null,\"in_reply_to_user_id_str\":null,\"in_reply_to_screen_name\":null,\"geo\":null,\"coordinates\":null,\"place\":null,\"contributors\":null,\"retweet_count\":67,\"favorite_count\":79,\"entities\":{\"hashtags\":[],\"symbols\":[],\"user_mentions\":[],\"urls\":[{\"url\":\"https:\/\/t.co\/eQIGvw1rsJ\",\"expanded_url\":\"https:\/\/twittercommunity.com\/t\/migration-of-twitter-core-entities-to-64-bit-ids\/56881\",\"display_url\":\"twittercommunity.com\/t\/migration-of\u2026\",\"indices\":[57,80]}]},\"favorited\":false,\"retweeted\":false,\"possibly_sensitive\":false,\"lang\":\"en\"},\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\/\/pbs.twimg.com\/profile_background_images\/656927849\/miyt9dpjz77sc0w3d4vj.png\",\"profile_background_image_url_https\":\"https:\/\/pbs.twimg.com\/profile_background_images\/656927849\/miyt9dpjz77sc0w3d4vj.png\",\"profile_background_tile\":true,\"profile_image_url\":\"http:\/\/pbs.twimg.com\/profile_images\/2284174872\/7df3h38zabcvjylnyfe3_normal.png\",\"profile_image_url_https\":\"https:\/\/pbs.twimg.com\/profile_images\/2284174872\/7df3h38zabcvjylnyfe3_normal.png\",\"profile_banner_url\":\"https:\/\/pbs.twimg.com\/profile_banners\/6253282\/1431474710\",\"profile_link_color\":\"0084B4\",\"profile_sidebar_border_color\":\"C0DEED\",\"profile_sidebar_fill_color\":\"DDEEF6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":false,\"default_profile\":false,\"default_profile_image\":false,\"following\":true,\"follow_request_sent\":false,\"notifications\":false}" ], 'POST https://api.twitter.com/oauth2/token' => [ 'httpstatus' => 200, @@ -114,6 +118,13 @@ protected function _curl_exec($id) $this->_requests[$id][CURLINFO_HTTP_CODE] = $reply['httpstatus']; $this->_requests[$id]['reply'] = $reply['reply']; + if (! $this->_requests[$id][CURLOPT_HEADER] + && stristr($reply['reply'], "\r\n\r\n") + ) { + $reply_parts = explode("\r\n\r\n", $reply['reply'], 2); + $reply['reply'] = $reply_parts[1]; + } + if ($this->_requests[$id][CURLOPT_RETURNTRANSFER]) { return $reply['reply']; } diff --git a/test/media_tests.php b/test/media_tests.php new file mode 100644 index 0000000..021f58a --- /dev/null +++ b/test/media_tests.php @@ -0,0 +1,56 @@ + + * @copyright 2010-2016 Jublo Solutions + * @license https://opensource.org/licenses/GPL-3.0 GNU General Public License 3.0 + * @link https://github.com/jublonet/codebird-php + */ + +/** + * Media tests + * + * @package codebird-test + */ +class Media_Test extends \PHPUnit_Framework_TestCase +{ + /** + * Initialise Codebird class + * + * @return \Codebird\Codebird The Codebird class + */ + protected function getCB() + { + Codebird::setConsumerKey('123', '456'); + $cb = new CodebirdM(); + + return $cb; + } + + /** + * Tests _fetchRemoteFile + */ + public function testFetchRemoteFile() + { + $cb = $this->getCB(); + $expected = $cb->call('_fetchRemoteFile', ['http://www.example.org/found.txt']); + $this->assertEquals($expected, 'A test file.'); + } + + /** + * Tests _fetchRemoteFile + * @expectedException \Exception + * @expectedExceptionMessage Downloading a remote media file failed. + */ + public function testFetchRemoteFile1() + { + $cb = $this->getCB(); + $reply = $cb->call('_fetchRemoteFile', ['http://www.example.org/not-found.jpg']); + } +} From 26095a4e903f99ee024107b72a654d3f1df0938b Mon Sep 17 00:00:00 2001 From: "J.M" Date: Tue, 12 Jan 2016 17:08:25 +0100 Subject: [PATCH 11/18] Add POST statuses/unretweet/:id --- CHANGELOG | 1 + src/codebird.php | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 909aa19..8b2187f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ codebird-php - changelog 3.1.0 (not yet released) + #143 Add support for proxy types + #152 Throw Exception on failed remote media download ++ Add POST statuses/unretweet/:id 3.0.0 (2016-01-01) + Add unit testing suite diff --git a/src/codebird.php b/src/codebird.php index d0c726b..ac816f8 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -417,6 +417,7 @@ class Codebird 'statuses/filter', 'statuses/lookup', 'statuses/retweet/:id', + 'statuses/unretweet/:id', 'statuses/update', 'statuses/update_with_media', // deprecated, use media/upload 'ton/bucket/:bucket', From b5e066fb35f0e438f8804437a11ec8935e764c54 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 22 Jan 2016 20:33:32 +0100 Subject: [PATCH 12/18] Add Ads API GET insights/keywords/search --- CHANGELOG | 3 ++- src/codebird.php | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 8b2187f..e8e475e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,7 +4,8 @@ codebird-php - changelog 3.1.0 (not yet released) + #143 Add support for proxy types + #152 Throw Exception on failed remote media download -+ Add POST statuses/unretweet/:id ++ Add REST API POST statuses/unretweet/:id ++ Add Ads API GET insights/keywords/search 3.0.0 (2016-01-01) + Add unit testing suite diff --git a/src/codebird.php b/src/codebird.php index ac816f8..2753948 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -143,6 +143,7 @@ class Codebird 'ads/iab_categories', 'ads/insights/accounts/:account_id', 'ads/insights/accounts/:account_id/available_audiences', + 'ads/insights/keywords/search', 'ads/line_items/placements', 'ads/sandbox/accounts', 'ads/sandbox/accounts/:account_id', @@ -198,6 +199,7 @@ class Codebird 'ads/sandbox/iab_categories', 'ads/sandbox/insights/accounts/:account_id', 'ads/sandbox/insights/accounts/:account_id/available_audiences', + 'ads/sandbox/insights/keywords/search', 'ads/sandbox/line_items/placements', 'ads/sandbox/stats/accounts/:account_id', 'ads/sandbox/stats/accounts/:account_id/campaigns', From 95eeea02bfef2439f0cc620da3d79a5eb211d79a Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 23 Jan 2016 13:46:16 +0100 Subject: [PATCH 13/18] Add quote tweet sample to Readme, see #154 --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index f5ea038..4fb41d7 100644 --- a/README.md +++ b/README.md @@ -847,3 +847,30 @@ use the corresponding [`CURLPROXY_*` constants](http://php.net/curl_setopt), lik ```php $cb->setProxy('', '', CURLPROXY_SOCKS5); ``` + +### …quote a Tweet? + +Quoting a Tweet is different from a Retweet because you may add your own text. +The original Tweet will appear below your quote. +To quote a Tweet, add a link to the original Tweet to your quote, like in this sample: + +```php +$original_tweet = [ + 'id_str' => '684483801687392256', + 'user' => [ + 'screen_name' => 'LarryMcTweet' + ] +]; +$original_tweet = (object) $original_tweet; // sample, get real Tweet from API + +$id = $original_tweet->id_str; // use the `id_str` field because of long numbers +$screen_name = $original_tweet->user->screen_name; + +// looks like this: https://twitter.com/LarryMcTweet/status/684483801687392256 +$url = "https://twitter.com/$screen_name/status/$id"; +$text = 'I’d like to quote a tweet.'; // maximum length = 140 minus 24 (link length) minus 1 space + +$reply = $cb->statuses_update([ + 'status' => "$text $url" +]); +``` From 080a03bb7c0bab2fe6328b2ecd4b709095b47f88 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 23 Jan 2016 14:07:59 +0100 Subject: [PATCH 14/18] Add json_decode wrapper, see #151 --- CHANGELOG | 1 + src/codebird.php | 24 +++++++++++++++++++++--- test/requestparse_tests.php | 19 +++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e8e475e..c64ae00 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ codebird-php - changelog + #152 Throw Exception on failed remote media download + Add REST API POST statuses/unretweet/:id + Add Ads API GET insights/keywords/search ++ #151 Avoid JSON_BIGINT_AS_STRING errors 3.0.0 (2016-01-01) + Add unit testing suite diff --git a/src/codebird.php b/src/codebird.php index 2753948..1d9eca0 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -996,6 +996,24 @@ protected function _mapFnInlineParams($method, &$apiparams) return [$method, $method_template]; } + /** + * Avoids any JSON_BIGINT_AS_STRING errors + * + * @param string $data JSON data to decode + * @param int optional $need_array Decode as array, otherwise as object + * + * @return array|object The decoded object + */ + protected function _json_decode($data, $need_array = false) + { + if (!(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) { + return json_decode($data, $need_array, 512, JSON_BIGINT_AS_STRING); + } + $max_int_length = strlen((string) PHP_INT_MAX) - 1; + $json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $data); + $obj = json_decode($json_without_bigints, $need_array); + return $obj; + } /** * Uncommon API methods @@ -1336,7 +1354,7 @@ protected function _parseBearerReply($result, $httpstatus) break; case CODEBIRD_RETURNFORMAT_JSON: if ($httpstatus === 200) { - $parsed = json_decode($reply, false, 512, JSON_BIGINT_AS_STRING); + $parsed = $this->_json_decode($reply); self::setBearerToken($parsed->access_token); } break; @@ -2232,7 +2250,7 @@ protected function _appendHttpStatusAndRate($reply, $httpstatus, $rate) $reply->rate = $rate; break; case CODEBIRD_RETURNFORMAT_JSON: - $reply = json_decode($reply); + $reply = $this->_json_decode($reply); $reply->httpstatus = $httpstatus; $reply->rate = $rate; $reply = json_encode($reply); @@ -2566,7 +2584,7 @@ protected function _parseApiReply($reply) return new \stdClass; } } - if (! $parsed = json_decode($reply, $need_array, 512, JSON_BIGINT_AS_STRING)) { + if (! $parsed = $this->_json_decode($reply, $need_array)) { if ($reply) { // assume query format $reply = explode('&', $reply); diff --git a/test/requestparse_tests.php b/test/requestparse_tests.php index 3de5810..279fdda 100644 --- a/test/requestparse_tests.php +++ b/test/requestparse_tests.php @@ -190,4 +190,23 @@ public function testMapFnInlineParams() $this->assertArrayNotHasKey('resumeId', $apiparams); $this->assertEquals(['test' => 1], $apiparams); } + + /** + * Tests _json_decode + */ + public function testJsonDecode() + { + $json = '{"id": 123456789123456789, "id_str": "123456789123456789"}'; + $array = [ + 'id' => 123456789123456789, + 'id_str' => '123456789123456789' + ]; + $object = (object) $array; + + $cb = $this->getCB(); + $result = $cb->call('_json_decode', [$json]); + $this->assertEquals($object, $result); + $result = $cb->call('_json_decode', [$json, true]); + $this->assertEquals($array, $result); + } } From 326cecb746a082ee000a4b0788c2bf3561e6d171 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 23 Jan 2016 14:17:24 +0100 Subject: [PATCH 15/18] Update README to capitalise Tweet --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 4fb41d7..de7cf6f 100644 --- a/README.md +++ b/README.md @@ -168,11 +168,11 @@ print_r($reply); Tweeting is as easy as this: ```php -$reply = $cb->statuses_update('status=Whohoo, I just tweeted!'); +$reply = $cb->statuses_update('status=Whohoo, I just Tweeted!'); ``` :warning: *Make sure to urlencode any parameter values that contain -query-reserved characters, like tweeting the `&` sign:* +query-reserved characters, like Tweeting the `&` sign:* ```php $reply = $cb->statuses_update('status=' . urlencode('Fish & chips')); @@ -205,7 +205,7 @@ $params = [ ]; $reply = $cb->users_show($params); ``` -This is the [resulting tweet](https://twitter.com/LarryMcTweet/status/482239971399835648) +This is the [resulting Tweet](https://twitter.com/LarryMcTweet/status/482239971399835648) sent with the code above. ### Requests with app-only auth @@ -314,7 +314,7 @@ to ```statuses/update```, like this: // convert media ids to string list $media_ids = implode(',', $media_ids); -// send tweet with these medias +// send Tweet with these medias $reply = $cb->statuses_update([ 'status' => 'These are some of my relatives.', 'media_ids' => $media_ids @@ -323,7 +323,7 @@ print_r($reply); ); ``` -Here is a [sample tweet](https://twitter.com/LarryMcTweet/status/475276535386365952) +Here is a [sample Tweet](https://twitter.com/LarryMcTweet/status/475276535386365952) sent with the code above. More [documentation for uploading media](https://dev.twitter.com/rest/public/uploading-media) is available on the Twitter Developer site. @@ -353,8 +353,8 @@ You need to perform at least 3 calls to obtain your `media_id` for the video: 1. Send an `INIT` event to get a `media_id` draft. 2. Upload your chunks with `APPEND` events, each one up to 5MB in size. -3. Send a `FINALIZE` event to convert the draft to a ready-to-tweet `media_id`. -4. Post your tweet with video attached. +3. Send a `FINALIZE` event to convert the draft to a ready-to-Tweet `media_id`. +4. Post your Tweet with video attached. Here’s a sample for video uploads: @@ -405,7 +405,7 @@ if ($reply->httpstatus < 200 || $reply->httpstatus > 299) { die(); } -// Now use the media_id in a tweet +// Now use the media_id in a Tweet $reply = $cb->statuses_update([ 'status' => 'Twitter now accepts video uploads.', 'media_ids' => $media_id @@ -496,7 +496,7 @@ and will always send the correct Content-Type automatically. Find out more about the [Collections API](https://dev.twitter.com/rest/collections/about) in the Twitter API docs. -Here’s a sample for adding a tweet using that API method: +Here’s a sample for adding a Tweet using that API method: ```php $reply = $cb->collections_entries_curate([ @@ -706,7 +706,7 @@ stdClass Object ) ``` -If you need to get more details, such as the user’s latest tweet, +If you need to get more details, such as the user’s latest Tweet, you should fetch the complete User Entity. The simplest way to get the user entity of the currently authenticated user is to use the ```account/verify_credentials``` API method. In Codebird, it works like this: @@ -868,7 +868,7 @@ $screen_name = $original_tweet->user->screen_name; // looks like this: https://twitter.com/LarryMcTweet/status/684483801687392256 $url = "https://twitter.com/$screen_name/status/$id"; -$text = 'I’d like to quote a tweet.'; // maximum length = 140 minus 24 (link length) minus 1 space +$text = 'I’d like to quote a Tweet.'; // maximum length = 140 minus 24 (link length) minus 1 space $reply = $cb->statuses_update([ 'status' => "$text $url" From ed00fe6cb065f19d64cd2ff23deab2223eb3938b Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 30 Jan 2016 13:53:31 +0100 Subject: [PATCH 16/18] Set version to 3.1.0-rc.1 --- bower.json | 2 +- src/codebird.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index bc1bdd2..3084244 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "codebird-php", - "version": "3.1.0-dev", + "version": "3.1.0-rc.1", "homepage": "https://www.jublo.net/projects/codebird/php", "authors": [ "Joshua Atkins ", diff --git a/src/codebird.php b/src/codebird.php index 1d9eca0..63f8169 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -6,7 +6,7 @@ * A Twitter library in PHP. * * @package codebird - * @version 3.1.0-dev + * @version 3.1.0-rc.1 * @author Jublo Solutions * @copyright 2010-2016 Jublo Solutions * @license https://opensource.org/licenses/GPL-3.0 GNU General Public License 3.0 @@ -508,7 +508,7 @@ class Codebird /** * The current Codebird version */ - protected static $_version = '3.1.0-dev'; + protected static $_version = '3.1.0-rc.1'; /** * The Request or access token. Used to sign requests From d7ae662fc53cb7efb79487e45b988eaca0c8f612 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 15 Feb 2016 13:11:04 -0500 Subject: [PATCH 17/18] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index de7cf6f..ed1e871 100644 --- a/README.md +++ b/README.md @@ -320,7 +320,6 @@ $reply = $cb->statuses_update([ 'media_ids' => $media_ids ]); print_r($reply); -); ``` Here is a [sample Tweet](https://twitter.com/LarryMcTweet/status/475276535386365952) From 1eb6ecab24c29f95d0dce242d4809637d5e3d965 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Mon, 15 Feb 2016 19:38:47 +0100 Subject: [PATCH 18/18] Set version to 3.1.0 --- CHANGELOG | 2 +- bower.json | 2 +- src/codebird.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9c006ca..9eb31a7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,7 @@ codebird-php - changelog ======================== -3.1.0 (not yet released) +3.1.0 (2016-02-15) + #143 Add support for proxy types + #152 Throw Exception on failed remote media download + Add REST API POST statuses/unretweet/:id diff --git a/bower.json b/bower.json index 3084244..2250042 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "codebird-php", - "version": "3.1.0-rc.1", + "version": "3.1.0", "homepage": "https://www.jublo.net/projects/codebird/php", "authors": [ "Joshua Atkins ", diff --git a/src/codebird.php b/src/codebird.php index 63f8169..c47996f 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -6,7 +6,7 @@ * A Twitter library in PHP. * * @package codebird - * @version 3.1.0-rc.1 + * @version 3.1.0 * @author Jublo Solutions * @copyright 2010-2016 Jublo Solutions * @license https://opensource.org/licenses/GPL-3.0 GNU General Public License 3.0 @@ -508,7 +508,7 @@ class Codebird /** * The current Codebird version */ - protected static $_version = '3.1.0-rc.1'; + protected static $_version = '3.1.0'; /** * The Request or access token. Used to sign requests