diff --git a/CHANGELOG.md b/CHANGELOG.md index a6de588..bc1b753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,12 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. -## 3.0.2 - TBD +## 3.0.2 - 2017-11-21 ### Added -- Nothing. +- [#55](https://github.com/zendframework/ZendService_Twitter/pull/55) adds + support for 280-character tweets. ### Changed diff --git a/TODO.md b/TODO.md index ed4773d..9f73a08 100644 --- a/TODO.md +++ b/TODO.md @@ -71,9 +71,3 @@ The following is a list of Twitter API methods not yet implemented: - users/suggestions - users/suggestions/:slug - users/suggestions/:slug/members - -## Fixes/functionality updates - -- Use `Normalizer` class to get normalized string, and then use that to count - number of characters for purpose of string lengths. -- Update allowed status length to 280 characters. diff --git a/src/Twitter.php b/src/Twitter.php index 30c1ebd..b862a57 100644 --- a/src/Twitter.php +++ b/src/Twitter.php @@ -8,6 +8,7 @@ namespace ZendService\Twitter; use Closure; +use Normalizer; use Traversable; use ZendOAuth as OAuth; use Zend\Http; @@ -19,6 +20,15 @@ * * Note: most `$id` parameters accept either string or integer values. This is * due to the fact that identifiers in the Twitter API may exceed PHP_INT_MAX. + * + * Note on character counting: Twitter accepts UTF-8 encoded text via the API, + * and counts multi-byte characters as a single character. PHP's strlen(), + * however, treats each byte as a character for purposes of determing the + * string length. To get around that, we can pass the message to utf8_decode, + * which will replace any multi-byte characters with a `?`; this works fine + * for counting lengths. + * + * @see https://developer.twitter.com/en/docs/basics/counting-characters */ class Twitter { @@ -42,14 +52,9 @@ class Twitter ]; /** - * 246 is the current limit for a status message, 140 characters are displayed - * initially, with the remainder linked from the web UI or client. The limit is - * applied to a html encoded UTF-8 string (i.e. entities are counted in the limit - * which may appear unusual but is a security measure). - * - * This should be reviewed in the future... + * As of November 2017, the character limit for status messages is 280. */ - const STATUS_MAX_CHARACTERS = 246; + const STATUS_MAX_CHARACTERS = 280; /** * @var array @@ -586,7 +591,7 @@ public function directMessagesEventsNew($user, string $text, array $extraParams { $path = 'direct_messages/events/new'; - $len = iconv_strlen($text, 'UTF-8'); + $len = strlen(utf8_decode($text)); if (0 === $len) { throw new Exception\InvalidArgumentException( 'Direct message must contain at least one character' @@ -980,7 +985,7 @@ public function searchTweets(string $query, array $options = []) : Response { $path = 'search/tweets'; - $len = iconv_strlen($query, 'UTF-8'); + $len = strlen(utf8_decode($query)); if (0 == $len) { throw new Exception\InvalidArgumentException( 'Query must contain at least one character' @@ -1260,13 +1265,13 @@ public function statusesShow($id, array $options = []) : Response public function statusesUpdate(string $status, $inReplyToStatusId = null, $extraAttributes = []) : Response { $path = 'statuses/update'; - $len = iconv_strlen(htmlspecialchars($status, ENT_QUOTES, 'UTF-8'), 'UTF-8'); + $len = strlen(utf8_decode($status)); if ($len > self::STATUS_MAX_CHARACTERS) { - throw new Exception\OutOfRangeException( - 'Status must be no more than ' - . self::STATUS_MAX_CHARACTERS - . ' characters in length' - ); + throw new Exception\OutOfRangeException(sprintf( + 'Status must be no more than %d characters in length; received %d', + self::STATUS_MAX_CHARACTERS, + $len + )); } elseif (0 == $len) { throw new Exception\InvalidArgumentException( 'Status must contain at least one character' @@ -1392,7 +1397,7 @@ public function usersSearch(string $query, array $options = []) : Response { $path = 'users/search'; - $len = iconv_strlen($query, 'UTF-8'); + $len = strlen(utf8_decode($query)); if (0 == $len) { throw new Exception\InvalidArgumentException( 'Query must contain at least one character' diff --git a/test/TwitterTest.php b/test/TwitterTest.php index a2cfc5c..3106945 100644 --- a/test/TwitterTest.php +++ b/test/TwitterTest.php @@ -485,6 +485,20 @@ public function testPostStatusUpdateToLongShouldThrowException() $twitter->statuses->update('Test Message - ' . str_repeat(' Hello ', 140)); } + public function testStatusUpdateShouldAllow280CharactersOfUTF8Encoding() + { + $message = str_repeat('é', 280); + $twitter = new Twitter\Twitter; + $twitter->setHttpClient($this->stubOAuthClient( + 'statuses/update.json', + Http\Request::METHOD_POST, + 'statuses.update.json', + ['status' => $message] + )); + $response = $twitter->statuses->update($message); + $this->assertInstanceOf(TwitterResponse::class, $response); + } + public function testPostStatusUpdateEmptyShouldThrowException() { $this->expectException(Twitter\Exception\ExceptionInterface::class);