Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/custom-request.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
<title>Twitter retweets of me</title>

<ul>
<?php foreach ($statuses as $status): ?>
<?php foreach ($statuses as $status) { ?>
<li><a href="http://twitter.com/<?php echo $status->user->screen_name ?>"><img src="<?php echo htmlspecialchars($status->user->profile_image_url_https) ?>">
<?php echo htmlspecialchars($status->user->name) ?></a>:
<?php echo Twitter::clickable($status) ?>
<small>at <?php echo date('j.n.Y H:i', strtotime($status->created_at)) ?></small>
</li>
<?php endforeach ?>
<?php } ?>
</ul>
4 changes: 2 additions & 2 deletions examples/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
<title>Twitter timeline demo</title>

<ul>
<?php foreach ($statuses as $status): ?>
<?php foreach ($statuses as $status) { ?>
<li><a href="https://twitter.com/<?php echo $status->user->screen_name ?>"><img src="<?php echo htmlspecialchars($status->user->profile_image_url_https) ?>">
<?php echo htmlspecialchars($status->user->name) ?></a>:
<?php echo Twitter::clickable($status) ?>
<small>at <?php echo date('j.n.Y H:i', strtotime($status->created_at)) ?></small>
</li>
<?php endforeach ?>
<?php } ?>
</ul>
4 changes: 2 additions & 2 deletions examples/search.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
<title>Twitter search demo</title>

<ul>
<?php foreach ($results as $status): ?>
<?php foreach ($results as $status) { ?>
<li><a href="https://twitter.com/<?php echo $status->user->screen_name ?>"><img src="<?php echo htmlspecialchars($status->user->profile_image_url_https) ?>">
<?php echo htmlspecialchars($status->user->name) ?></a>:
<?php echo Twitter::clickable($status) ?>
<small>at <?php echo date('j.n.Y H:i', strtotime($status->created_at)) ?></small>
</li>
<?php endforeach ?>
<?php } ?>
</ul>
20 changes: 17 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@
Twitter for PHP is a very small and easy-to-use library for sending
messages to Twitter and receiving status updates.

If you like this, **[please make a donation now](https://nette.org/make-donation?to=twitter-php)**. Thank you!

It requires PHP 5.4 or newer with CURL extension and is licensed under the New BSD License.
You can obtain the latest version from our [GitHub repository](https://github.com/dg/twitter-php)
or install it via Composer:

composer require dg/twitter-php


[Support Me](https://github.com/sponsors/dg)
--------------------------------------------

Do you like Nette DI? Are you looking forward to the new features?

[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg)

Thank you!


Usage
-----
Sign in to the https://twitter.com and register an application from the https://apps.twitter.com page. Remember
Expand Down Expand Up @@ -108,14 +116,20 @@ if (!$twitter->authenticate()) {
Other commands
--------------

You can use all commands defined by [Twitter API 1.1](https://dev.twitter.com/rest/public).
You can use all commands defined by [Twitter API](https://dev.twitter.com/rest/public).
For example [GET statuses/retweets_of_me](https://dev.twitter.com/rest/reference/get/statuses/retweets_of_me)
returns the array of most recent tweets authored by the authenticating user:

```php
$statuses = $twitter->request('statuses/retweets_of_me', 'GET', ['count' => 20]);
```

You can also specify which API version to use with the API_*_SUFFIX constants:

```php
$statuses = $twitter->request('tweets', 'GET', [], [], Twitter::API_2_SUFFIX);
```

Changelog
---------
v4.1 (11/2019)
Expand Down
31 changes: 24 additions & 7 deletions src/OAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,18 @@ public function __construct(string $http_method, string $http_url, array $parame
/**
* attempt to build up a request from what was passed to the server
*/
public static function from_request(string $http_method = null, string $http_url = null, array $parameters = null): self
public static function from_request(
string $http_method = null,
string $http_url = null,
array $parameters = null
): self
{
$scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on')
? 'http'
: 'https';
$http_url = ($http_url) ? $http_url : $scheme .
$http_url = ($http_url)
? $http_url
: $scheme .
'://' . $_SERVER['HTTP_HOST'] .
':' .
$_SERVER['SERVER_PORT'] .
Expand Down Expand Up @@ -339,7 +345,10 @@ public static function from_request(string $http_method = null, string $http_url

// We have a Authorization-header with OAuth data. Parse the header
// and add those overriding any duplicates from GET or POST
if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') {
if (
isset($request_headers['Authorization'])
&& substr($request_headers['Authorization'], 0, 6) == 'OAuth '
) {
$header_parameters = Util::split_header(
$request_headers['Authorization']
);
Expand All @@ -354,7 +363,13 @@ public static function from_request(string $http_method = null, string $http_url
/**
* pretty much a helper function to set up the request
*/
public static function from_consumer_and_token(Consumer $consumer, ?Token $token, string $http_method, string $http_url, array $parameters = null): self
public static function from_consumer_and_token(
Consumer $consumer,
?Token $token,
string $http_method,
string $http_url,
array $parameters = null
): self
{
$parameters = $parameters ?: [];
$defaults = [
Expand Down Expand Up @@ -392,7 +407,7 @@ public function set_parameter(string $name, $value, bool $allow_duplicates = tru

public function get_parameter(string $name)
{
return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
return $this->parameters[$name] ?? null;
}


Expand Down Expand Up @@ -465,7 +480,9 @@ public function get_normalized_http_url(): string
$parts = parse_url($this->http_url);

$scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
$port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
$port = (isset($parts['port']))
? $parts['port']
: (($scheme == 'https') ? '443' : '80');
$host = (isset($parts['host'])) ? $parts['host'] : '';
$path = (isset($parts['path'])) ? $parts['path'] : '';

Expand Down Expand Up @@ -581,7 +598,7 @@ class Util
public static function urlencode_rfc3986($input)
{
if (is_array($input)) {
return array_map([__CLASS__, 'urlencode_rfc3986'], $input);
return array_map([self::class, 'urlencode_rfc3986'], $input);
} elseif (is_scalar($input)) {
return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode((string) $input)));
} else {
Expand Down
104 changes: 85 additions & 19 deletions src/Twitter.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ class Twitter
public const REPLIES = 3;
public const RETWEETS = 128; // include retweets?

private const API_URL = 'https://api.twitter.com/1.1/';
public const API_1_SUFFIX = "1.1";
public const API_2_SUFFIX = "2";

private const API_URL = 'https://api.twitter.com/';

/** @var int */
public static $cacheExpire = '30 minutes';
Expand All @@ -54,8 +57,12 @@ class Twitter
* Creates object using consumer and access keys.
* @throws Exception when CURL extension is not loaded
*/
public function __construct(string $consumerKey, string $consumerSecret, string $accessToken = null, string $accessTokenSecret = null)
{
public function __construct(
string $consumerKey,
string $consumerSecret,
string $accessToken = null,
string $accessTokenSecret = null
) {
if (!extension_loaded('curl')) {
throw new Exception('PHP extension CURL is not loaded.');
}
Expand Down Expand Up @@ -196,7 +203,12 @@ public function loadUserInfoById(string $id): stdClass
* https://dev.twitter.com/rest/reference/get/followers/ids
* @throws Exception
*/
public function loadUserFollowers(string $username, int $count = 5000, int $cursor = -1, $cacheExpiry = null): stdClass
public function loadUserFollowers(
string $username,
int $count = 5000,
int $cursor = -1,
$cacheExpiry = null
): stdClass
{
return $this->cachedRequest('followers/ids', [
'screen_name' => $username,
Expand All @@ -211,7 +223,12 @@ public function loadUserFollowers(string $username, int $count = 5000, int $curs
* https://dev.twitter.com/rest/reference/get/followers/list
* @throws Exception
*/
public function loadUserFollowersList(string $username, int $count = 200, int $cursor = -1, $cacheExpiry = null): stdClass
public function loadUserFollowersList(
string $username,
int $count = 200,
int $cursor = -1,
$cacheExpiry = null
): stdClass
{
return $this->cachedRequest('followers/list', [
'screen_name' => $username,
Expand All @@ -228,7 +245,7 @@ public function loadUserFollowersList(string $username, int $count = 200, int $c
*/
public function destroy($id)
{
$res = $this->request("statuses/destroy/$id", 'POST');
$res = $this->request("statuses/destroy/$id", 'POST', ['id' => $id]);
return $res->id ?: false;
}

Expand Down Expand Up @@ -270,18 +287,62 @@ public function getTrends(int $WOEID): array


/**
* Process HTTP request.
* @param string $method GET|POST|JSONPOST|DELETE
* @return mixed
* Generates an API url, requires at minimum 2 parts (version & path).
*
* @param string ...$parts Collection of URL parts to combine
* @throws Exception
* @return string
*/
protected static function makeApiURL(string ...$parts) {
$url = [];
$partsCount = count($parts);

if ($partsCount < 1) {
throw new Exception("Invalid API URL components provided. Must have at least 2 parts (version & path)");
}

$url[] = substr(self::API_URL, 0, strlen(self::API_URL) - 1);

for ($i = 0; $i < $partsCount; $i++) {
$part = $parts[$i];
$partLen = strlen($part);

if ($part[$partLen - 1] == '/') {
$part = substr($part, 0, $partLen - 1);
}

if ($part[0] == '/') {
$part = substr($part, 1);
}

$url[] = $part;
}

return implode('/', $url);
}


/**
* Process HTTP request. If $resource contains only endpoint path (no http://|https://), API_URL will be prefixed
* onto resource path. If $apiSuffix is '1.1' (default), resource will have '.json' added as a suffix if '.'
* character not found.
*
* @param string $resource API endpoint
* @param string $method GET|POST|JSONPOST|DELETE
* @param array $data Optional query/body data
* @param array $files Optional file data
* @param string $apiSuffix Optional API version suffix (1.1 by default)
* @throws Exception|\DG\Twitter\OAuth\Exception
* @return mixed
*/
public function request(string $resource, string $method, array $data = [], array $files = [])
public function request(string $resource, string $method, array $data = [], array $files = [], string $apiSuffix = self::API_1_SUFFIX)
{
if (!strpos($resource, '://')) {
if (!strpos($resource, '.')) {
if ($apiSuffix == self::API_1_SUFFIX && !strpos($resource, '.')) {
$resource .= '.json';
}
$resource = self::API_URL . $resource;

$resource = static::makeApiURL($apiSuffix, $resource);
}

foreach ($data as $key => $val) {
Expand All @@ -294,6 +355,7 @@ public function request(string $resource, string $method, array $data = [], arra
if (!is_file($file)) {
throw new Exception("Cannot read the file $file. Check if file exists on disk and check its permissions.");
}

$data[$key] = new \CURLFile($file);
}

Expand All @@ -303,7 +365,6 @@ public function request(string $resource, string $method, array $data = [], arra
$method = 'POST';
$data = json_encode($data);
$headers[] = 'Content-Type: application/json';

} elseif (($method === 'GET' || $method === 'DELETE') && $data) {
$resource .= '?' . http_build_query($data, '', '&');
}
Expand Down Expand Up @@ -334,22 +395,25 @@ public function request(string $resource, string $method, array $data = [], arra
$curl = curl_init();
curl_setopt_array($curl, $options);
$result = curl_exec($curl);

if (curl_errno($curl)) {
throw new Exception('Server error: ' . curl_error($curl));
}

if (strpos(curl_getinfo($curl, CURLINFO_CONTENT_TYPE), 'application/json') !== false) {
$contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);

if ($contentType === false || strpos($contentType, 'application/json') !== false) {
$payload = @json_decode($result, false, 128, JSON_BIGINT_AS_STRING); // intentionally @

if ($payload === false) {
throw new Exception('Invalid server response');
}
}

$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($code >= 400) {
throw new Exception(isset($payload->errors[0]->message)
? $payload->errors[0]->message
: "Server error #$code with answer $result",
throw new Exception(
$payload->errors[0]->message ?? "Server error #$code with answer $result",
$code
);
} elseif ($code === 204) {
Expand Down Expand Up @@ -379,7 +443,9 @@ public function cachedRequest(string $resource, array $data = [], $cacheExpire =
. '.json';

$cache = @json_decode((string) @file_get_contents($cacheFile)); // intentionally @
$expiration = is_string($cacheExpire) ? strtotime($cacheExpire) - time() : $cacheExpire;
$expiration = is_string($cacheExpire)
? strtotime($cacheExpire) - time()
: $cacheExpire;
if ($cache && @filemtime($cacheFile) + $expiration > time()) { // intentionally @
return $cache;
}
Expand Down Expand Up @@ -424,7 +490,7 @@ public static function clickable(stdClass $status): string
}

krsort($all);
$s = isset($status->full_text) ? $status->full_text : $status->text;
$s = $status->full_text ?? $status->text;
foreach ($all as $pos => $item) {
$s = iconv_substr($s, 0, $pos, 'UTF-8')
. '<a href="' . htmlspecialchars($item[0]) . '">' . htmlspecialchars($item[1]) . '</a>'
Expand Down