Add explicit dependencies to twitter addon

This commit is contained in:
Hypolite Petovan 2018-01-28 12:17:19 -05:00
parent 01738518a2
commit d18bb44f8a
45 changed files with 6653 additions and 14 deletions

3
.gitignore vendored
View File

@ -22,6 +22,3 @@ report/
#ignore smarty cache
/view/smarty3/compiled/
#ignore Composer folder
vendor/

View File

@ -1,10 +0,0 @@
{
"name": "friendica/friendica-addons",
"description": "Addons library for Friendica",
"type": "library",
"require": {
"abraham/twitteroauth": "^0.7.4"
},
"license": "3-clause BSD license",
"minimum-stability": "stable"
}

28
twitter/composer.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "friendica-addons/twitter",
"description": "Twitter Connector addon for Friendica",
"type": "friendica-addon",
"authors": [
{
"name": "Tobias Diekershoff",
"homepage": "https://f.diekershoff.de/profile/tobias",
"role": "Developer"
},
{
"name": "Michael Vogel",
"homepage": "https://pirati.ca/profile/heluecht",
"role": "Developer"
},
{
"name": "Hypolite Petovan",
"email": "mrpetovan@gmail.com",
"homepage": "https://friendica.mrpetovan.com/profile/hypolite",
"role": "Developer"
}
],
"require": {
"abraham/twitteroauth": "^0.7.4"
},
"license": "3-clause BSD license",
"minimum-stability": "stable"
}

View File

@ -83,7 +83,7 @@ require_once 'include/dba.php';
require_once 'include/enotify.php';
require_once 'include/text.php';
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
define('TWITTER_DEFAULT_POLL_INTERVAL', 5); // given in minutes

View File

@ -0,0 +1,4 @@
.DS_Store
composer.lock
vendor
env

View File

@ -0,0 +1,13 @@
language: php
dist: trusty
php:
- '5.6'
- '7.0'
- '7.1'
- hhvm
sudo: false
before_script:
- composer self-update
- composer install --prefer-source --no-interaction
script:
- vendor/bin/phpunit

View File

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at abraham@abrah.am. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@ -0,0 +1,22 @@
Copyright (c) 2009 Abraham Williams - http://abrah.am - abraham@abrah.am
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,10 @@
<span itemprop="name">TwitterOAuth</span> [![Build Status](https://img.shields.io/travis/abraham/twitteroauth.svg)](https://travis-ci.org/abraham/twitteroauth) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/abraham/twitteroauth/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/abraham/twitteroauth/?branch=master) [![Issues Count](https://img.shields.io/github/issues/abraham/twitteroauth.svg)](https://github.com/abraham/twitteroauth/issues) [![Latest Version](https://img.shields.io/packagist/v/abraham/twitteroauth.svg)](https://packagist.org/packages/abraham/twitteroauth)
------------
<p itemprop="description">The most popular PHP library for Twitter's OAuth REST API.</p>
See documentation at https://twitteroauth.com.
PHP versions [listed](https://secure.php.net/supported-versions.php) as "active support" or "security fixes only" are supported.
<img src="https://raw.githubusercontent.com/abraham/twitteroauth-demo/master/images/twitter-logo-blue.png" itemprop="image" alt="Twitter bird" width="200px">

View File

@ -0,0 +1,36 @@
<?php
/**
* Use to autoload needed classes without Composer.
*
* @param string $class The fully-qualified class name.
* @return void
*/
spl_autoload_register(function ($class) {
// project-specific namespace prefix
$prefix = 'Abraham\\TwitterOAuth\\';
// base directory for the namespace prefix
$base_dir = __DIR__ . '/src/';
// does the class use the namespace prefix?
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
// no, move to the next registered autoloader
return;
}
// get the relative class name
$relative_class = substr($class, $len);
// replace the namespace prefix with the base directory, replace namespace
// separators with directory separators in the relative class name, append
// with .php
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
// if the file exists, require it
if (file_exists($file)) {
require $file;
}
});

View File

@ -0,0 +1,34 @@
{
"name": "abraham/twitteroauth",
"type": "library",
"description": "The most popular PHP library for use with the Twitter OAuth REST API.",
"keywords": ["twitter", "api", "oauth", "rest", "social", "twitter api", "twitter oauth"],
"license": "MIT",
"homepage": "https://twitteroauth.com",
"authors": [
{
"name": "Abraham Williams",
"email": "abraham@abrah.am",
"homepage": "https://abrah.am",
"role": "Developer"
}
],
"support": {
"source": "https://github.com/abraham/twitteroauth",
"issues": "https://github.com/abraham/twitteroauth/issues"
},
"require": {
"php": "^5.6 || ^7.0",
"ext-curl": "*"
},
"require-dev": {
"phpunit/phpunit": "~5.7",
"squizlabs/php_codesniffer": "~3.0",
"phpmd/phpmd": "~2.6"
},
"autoload": {
"psr-4": {
"Abraham\\TwitterOAuth\\": "src"
}
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<ruleset name="My first PHPMD rule set"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>Keep TwitterOAuth source code clean.</description>
<!-- <rule ref="rulesets/cleancode.xml" /> -->
<rule ref="rulesets/codesize.xml" />
<rule ref="rulesets/controversial.xml" />
<rule ref="rulesets/design.xml" />
<rule ref="rulesets/naming.xml" />
<rule ref="rulesets/unusedcode.xml" />
</ruleset>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
colors="true"
bootstrap="tests/bootstrap.php">
<testsuites>
<testsuite name="TwitterOAuth Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@ -0,0 +1,90 @@
<?php
namespace Abraham\TwitterOAuth;
/**
* Handle setting and storing config for TwitterOAuth.
*
* @author Abraham Williams <abraham@abrah.am>
*/
class Config
{
/** @var int How long to wait for a response from the API */
protected $timeout = 5;
/** @var int how long to wait while connecting to the API */
protected $connectionTimeout = 5;
/**
* Decode JSON Response as associative Array
*
* @see http://php.net/manual/en/function.json-decode.php
*
* @var bool
*/
protected $decodeJsonAsArray = false;
/** @var string User-Agent header */
protected $userAgent = 'TwitterOAuth (+https://twitteroauth.com)';
/** @var array Store proxy connection details */
protected $proxy = [];
/** @var bool Whether to encode the curl requests with gzip or not */
protected $gzipEncoding = true;
/** @var integer Size for Chunked Uploads */
protected $chunkSize = 250000; // 0.25 MegaByte
/**
* Set the connection and response timeouts.
*
* @param int $connectionTimeout
* @param int $timeout
*/
public function setTimeouts($connectionTimeout, $timeout)
{
$this->connectionTimeout = (int)$connectionTimeout;
$this->timeout = (int)$timeout;
}
/**
* @param bool $value
*/
public function setDecodeJsonAsArray($value)
{
$this->decodeJsonAsArray = (bool)$value;
}
/**
* @param string $userAgent
*/
public function setUserAgent($userAgent)
{
$this->userAgent = (string)$userAgent;
}
/**
* @param array $proxy
*/
public function setProxy(array $proxy)
{
$this->proxy = $proxy;
}
/**
* Whether to encode the curl requests with gzip or not.
*
* @param boolean $gzipEncoding
*/
public function setGzipEncoding($gzipEncoding)
{
$this->gzipEncoding = (bool)$gzipEncoding;
}
/**
* Set the size of each part of file for chunked media upload.
*
* @param int $value
*/
public function setChunkSize($value)
{
$this->chunkSize = (int)$value;
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* The MIT License
* Copyright (c) 2007 Andy Smith
*/
namespace Abraham\TwitterOAuth;
class Consumer
{
/** @var string */
public $key;
/** @var string */
public $secret;
/** @var string|null */
public $callbackUrl;
/**
* @param string $key
* @param string $secret
* @param null $callbackUrl
*/
public function __construct($key, $secret, $callbackUrl = null)
{
$this->key = $key;
$this->secret = $secret;
$this->callbackUrl = $callbackUrl;
}
/**
* @return string
*/
public function __toString()
{
return "Consumer[key=$this->key,secret=$this->secret]";
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* The MIT License
* Copyright (c) 2007 Andy Smith
*/
namespace Abraham\TwitterOAuth;
/**
* The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]
* where the Signature Base String is the text and the key is the concatenated values (each first
* encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&'
* character (ASCII code 38) even if empty.
* - Chapter 9.2 ("HMAC-SHA1")
*/
class HmacSha1 extends SignatureMethod
{
/**
* {@inheritDoc}
*/
public function getName()
{
return "HMAC-SHA1";
}
/**
* {@inheritDoc}
*/
public function buildSignature(Request $request, Consumer $consumer, Token $token = null)
{
$signatureBase = $request->getSignatureBaseString();
$parts = [$consumer->secret, null !== $token ? $token->secret : ""];
$parts = Util::urlencodeRfc3986($parts);
$key = implode('&', $parts);
return base64_encode(hash_hmac('sha1', $signatureBase, $key, true));
}
}

View File

@ -0,0 +1,254 @@
<?php
/**
* The MIT License
* Copyright (c) 2007 Andy Smith
*/
namespace Abraham\TwitterOAuth;
class Request
{
protected $parameters;
protected $httpMethod;
protected $httpUrl;
public static $version = '1.0';
/**
* Constructor
*
* @param string $httpMethod
* @param string $httpUrl
* @param array|null $parameters
*/
public function __construct($httpMethod, $httpUrl, array $parameters = [])
{
$parameters = array_merge(Util::parseParameters(parse_url($httpUrl, PHP_URL_QUERY)), $parameters);
$this->parameters = $parameters;
$this->httpMethod = $httpMethod;
$this->httpUrl = $httpUrl;
}
/**
* pretty much a helper function to set up the request
*
* @param Consumer $consumer
* @param Token $token
* @param string $httpMethod
* @param string $httpUrl
* @param array $parameters
*
* @return Request
*/
public static function fromConsumerAndToken(
Consumer $consumer,
Token $token = null,
$httpMethod,
$httpUrl,
array $parameters = []
) {
$defaults = [
"oauth_version" => Request::$version,
"oauth_nonce" => Request::generateNonce(),
"oauth_timestamp" => time(),
"oauth_consumer_key" => $consumer->key
];
if (null !== $token) {
$defaults['oauth_token'] = $token->key;
}
$parameters = array_merge($defaults, $parameters);
return new Request($httpMethod, $httpUrl, $parameters);
}
/**
* @param string $name
* @param string $value
*/
public function setParameter($name, $value)
{
$this->parameters[$name] = $value;
}
/**
* @param $name
*
* @return string|null
*/
public function getParameter($name)
{
return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
}
/**
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
/**
* @param $name
*/
public function removeParameter($name)
{
unset($this->parameters[$name]);
}
/**
* The request parameters, sorted and concatenated into a normalized string.
*
* @return string
*/
public function getSignableParameters()
{
// Grab all parameters
$params = $this->parameters;
// Remove oauth_signature if present
// Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
if (isset($params['oauth_signature'])) {
unset($params['oauth_signature']);
}
return Util::buildHttpQuery($params);
}
/**
* Returns the base string of this request
*
* The base string defined as the method, the url
* and the parameters (normalized), each urlencoded
* and the concated with &.
*
* @return string
*/
public function getSignatureBaseString()
{
$parts = [
$this->getNormalizedHttpMethod(),
$this->getNormalizedHttpUrl(),
$this->getSignableParameters()
];
$parts = Util::urlencodeRfc3986($parts);
return implode('&', $parts);
}
/**
* Returns the HTTP Method in uppercase
*
* @return string
*/
public function getNormalizedHttpMethod()
{
return strtoupper($this->httpMethod);
}
/**
* parses the url and rebuilds it to be
* scheme://host/path
*
* @return string
*/
public function getNormalizedHttpUrl()
{
$parts = parse_url($this->httpUrl);
$scheme = $parts['scheme'];
$host = strtolower($parts['host']);
$path = $parts['path'];
return "$scheme://$host$path";
}
/**
* Builds a url usable for a GET request
*
* @return string
*/
public function toUrl()
{
$postData = $this->toPostdata();
$out = $this->getNormalizedHttpUrl();
if ($postData) {
$out .= '?' . $postData;
}
return $out;
}
/**
* Builds the data one would send in a POST request
*
* @return string
*/
public function toPostdata()
{
return Util::buildHttpQuery($this->parameters);
}
/**
* Builds the Authorization: header
*
* @return string
* @throws TwitterOAuthException
*/
public function toHeader()
{
$first = true;
$out = 'Authorization: OAuth';
foreach ($this->parameters as $k => $v) {
if (substr($k, 0, 5) != "oauth") {
continue;
}
if (is_array($v)) {
throw new TwitterOAuthException('Arrays not supported in headers');
}
$out .= ($first) ? ' ' : ', ';
$out .= Util::urlencodeRfc3986($k) . '="' . Util::urlencodeRfc3986($v) . '"';
$first = false;
}
return $out;
}
/**
* @return string
*/
public function __toString()
{
return $this->toUrl();
}
/**
* @param SignatureMethod $signatureMethod
* @param Consumer $consumer
* @param Token $token
*/
public function signRequest(SignatureMethod $signatureMethod, Consumer $consumer, Token $token = null)
{
$this->setParameter("oauth_signature_method", $signatureMethod->getName());
$signature = $this->buildSignature($signatureMethod, $consumer, $token);
$this->setParameter("oauth_signature", $signature);
}
/**
* @param SignatureMethod $signatureMethod
* @param Consumer $consumer
* @param Token $token
*
* @return string
*/
public function buildSignature(SignatureMethod $signatureMethod, Consumer $consumer, Token $token = null)
{
return $signatureMethod->buildSignature($this, $consumer, $token);
}
/**
* @return string
*/
public static function generateNonce()
{
return md5(microtime() . mt_rand());
}
}

View File

@ -0,0 +1,107 @@
<?php
namespace Abraham\TwitterOAuth;
/**
* The result of the most recent API request.
*
* @author Abraham Williams <abraham@abrah.am>
*/
class Response
{
/** @var string|null API path from the most recent request */
private $apiPath;
/** @var int HTTP status code from the most recent request */
private $httpCode = 0;
/** @var array HTTP headers from the most recent request */
private $headers = [];
/** @var array|object Response body from the most recent request */
private $body = [];
/** @var array HTTP headers from the most recent request that start with X */
private $xHeaders = [];
/**
* @param string $apiPath
*/
public function setApiPath($apiPath)
{
$this->apiPath = $apiPath;
}
/**
* @return string|null
*/
public function getApiPath()
{
return $this->apiPath;
}
/**
* @param array|object $body
*/
public function setBody($body)
{
$this->body = $body;
}
/**
* @return array|object|string
*/
public function getBody()
{
return $this->body;
}
/**
* @param int $httpCode
*/
public function setHttpCode($httpCode)
{
$this->httpCode = $httpCode;
}
/**
* @return int
*/
public function getHttpCode()
{
return $this->httpCode;
}
/**
* @param array $headers
*/
public function setHeaders(array $headers)
{
foreach ($headers as $key => $value) {
if (substr($key, 0, 1) == 'x') {
$this->xHeaders[$key] = $value;
}
}
$this->headers = $headers;
}
/**
* @return array
*/
public function getsHeaders()
{
return $this->headers;
}
/**
* @param array $xHeaders
*/
public function setXHeaders(array $xHeaders = [])
{
$this->xHeaders = $xHeaders;
}
/**
* @return array
*/
public function getXHeaders()
{
return $this->xHeaders;
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* The MIT License
* Copyright (c) 2007 Andy Smith
*/
namespace Abraham\TwitterOAuth;
/**
* A class for implementing a Signature Method
* See section 9 ("Signing Requests") in the spec
*/
abstract class SignatureMethod
{
/**
* Needs to return the name of the Signature Method (ie HMAC-SHA1)
*
* @return string
*/
abstract public function getName();
/**
* Build up the signature
* NOTE: The output of this function MUST NOT be urlencoded.
* the encoding is handled in OAuthRequest when the final
* request is serialized
*
* @param Request $request
* @param Consumer $consumer
* @param Token $token
*
* @return string
*/
abstract public function buildSignature(Request $request, Consumer $consumer, Token $token = null);
/**
* Verifies that a given signature is correct
*
* @param Request $request
* @param Consumer $consumer
* @param Token $token
* @param string $signature
*
* @return bool
*/
public function checkSignature(Request $request, Consumer $consumer, Token $token, $signature)
{
$built = $this->buildSignature($request, $consumer, $token);
// Check for zero length, although unlikely here
if (strlen($built) == 0 || strlen($signature) == 0) {
return false;
}
if (strlen($built) != strlen($signature)) {
return false;
}
// Avoid a timing leak with a (hopefully) time insensitive compare
$result = 0;
for ($i = 0; $i < strlen($signature); $i++) {
$result |= ord($built{$i}) ^ ord($signature{$i});
}
return $result == 0;
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* The MIT License
* Copyright (c) 2007 Andy Smith
*/
namespace Abraham\TwitterOAuth;
class Token
{
/** @var string */
public $key;
/** @var string */
public $secret;
/**
* @param string $key The OAuth Token
* @param string $secret The OAuth Token Secret
*/
public function __construct($key, $secret)
{
$this->key = $key;
$this->secret = $secret;
}
/**
* Generates the basic string serialization of a token that a server
* would respond to request_token and access_token calls with
*
* @return string
*/
public function __toString()
{
return sprintf("oauth_token=%s&oauth_token_secret=%s",
Util::urlencodeRfc3986($this->key),
Util::urlencodeRfc3986($this->secret)
);
}
}

View File

@ -0,0 +1,508 @@
<?php
/**
* The most popular PHP library for use with the Twitter OAuth REST API.
*
* @license MIT
*/
namespace Abraham\TwitterOAuth;
use Abraham\TwitterOAuth\Util\JsonDecoder;
/**
* TwitterOAuth class for interacting with the Twitter API.
*
* @author Abraham Williams <abraham@abrah.am>
*/
class TwitterOAuth extends Config
{
const API_VERSION = '1.1';
const API_HOST = 'https://api.twitter.com';
const UPLOAD_HOST = 'https://upload.twitter.com';
/** @var Response details about the result of the last request */
private $response;
/** @var string|null Application bearer token */
private $bearer;
/** @var Consumer Twitter application details */
private $consumer;
/** @var Token|null User access token details */
private $token;
/** @var HmacSha1 OAuth 1 signature type used by Twitter */
private $signatureMethod;
/**
* Constructor
*
* @param string $consumerKey The Application Consumer Key
* @param string $consumerSecret The Application Consumer Secret
* @param string|null $oauthToken The Client Token (optional)
* @param string|null $oauthTokenSecret The Client Token Secret (optional)
*/
public function __construct($consumerKey, $consumerSecret, $oauthToken = null, $oauthTokenSecret = null)
{
$this->resetLastResponse();
$this->signatureMethod = new HmacSha1();
$this->consumer = new Consumer($consumerKey, $consumerSecret);
if (!empty($oauthToken) && !empty($oauthTokenSecret)) {
$this->token = new Token($oauthToken, $oauthTokenSecret);
}
if (empty($oauthToken) && !empty($oauthTokenSecret)) {
$this->bearer = $oauthTokenSecret;
}
}
/**
* @param string $oauthToken
* @param string $oauthTokenSecret
*/
public function setOauthToken($oauthToken, $oauthTokenSecret)
{
$this->token = new Token($oauthToken, $oauthTokenSecret);
}
/**
* @return string|null
*/
public function getLastApiPath()
{
return $this->response->getApiPath();
}
/**
* @return int
*/
public function getLastHttpCode()
{
return $this->response->getHttpCode();
}
/**
* @return array
*/
public function getLastXHeaders()
{
return $this->response->getXHeaders();
}
/**
* @return array|object|null
*/
public function getLastBody()
{
return $this->response->getBody();
}
/**
* Resets the last response cache.
*/
public function resetLastResponse()
{
$this->response = new Response();
}
/**
* Make URLs for user browser navigation.
*
* @param string $path
* @param array $parameters
*
* @return string
*/
public function url($path, array $parameters)
{
$this->resetLastResponse();
$this->response->setApiPath($path);
$query = http_build_query($parameters);
return sprintf('%s/%s?%s', self::API_HOST, $path, $query);
}
/**
* Make /oauth/* requests to the API.
*
* @param string $path
* @param array $parameters
*
* @return array
* @throws TwitterOAuthException
*/
public function oauth($path, array $parameters = [])
{
$response = [];
$this->resetLastResponse();
$this->response->setApiPath($path);
$url = sprintf('%s/%s', self::API_HOST, $path);
$result = $this->oAuthRequest($url, 'POST', $parameters);
if ($this->getLastHttpCode() != 200) {
throw new TwitterOAuthException($result);
}
parse_str($result, $response);
$this->response->setBody($response);
return $response;
}
/**
* Make /oauth2/* requests to the API.
*
* @param string $path
* @param array $parameters
*
* @return array|object
*/
public function oauth2($path, array $parameters = [])
{
$method = 'POST';
$this->resetLastResponse();
$this->response->setApiPath($path);
$url = sprintf('%s/%s', self::API_HOST, $path);
$request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
$authorization = 'Authorization: Basic ' . $this->encodeAppAuthorization($this->consumer);
$result = $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
$response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
$this->response->setBody($response);
return $response;
}
/**
* Make GET requests to the API.
*
* @param string $path
* @param array $parameters
*
* @return array|object
*/
public function get($path, array $parameters = [])
{
return $this->http('GET', self::API_HOST, $path, $parameters);
}
/**
* Make POST requests to the API.
*
* @param string $path
* @param array $parameters
*
* @return array|object
*/
public function post($path, array $parameters = [])
{
return $this->http('POST', self::API_HOST, $path, $parameters);
}
/**
* Make DELETE requests to the API.
*
* @param string $path
* @param array $parameters
*
* @return array|object
*/
public function delete($path, array $parameters = [])
{
return $this->http('DELETE', self::API_HOST, $path, $parameters);
}
/**
* Make PUT requests to the API.
*
* @param string $path
* @param array $parameters
*
* @return array|object
*/
public function put($path, array $parameters = [])
{
return $this->http('PUT', self::API_HOST, $path, $parameters);
}
/**
* Upload media to upload.twitter.com.
*
* @param string $path
* @param array $parameters
* @param boolean $chunked
*
* @return array|object
*/
public function upload($path, array $parameters = [], $chunked = false)
{
if ($chunked) {
return $this->uploadMediaChunked($path, $parameters);
} else {
return $this->uploadMediaNotChunked($path, $parameters);
}
}
/**
* Private method to upload media (not chunked) to upload.twitter.com.
*
* @param string $path
* @param array $parameters
*
* @return array|object
*/
private function uploadMediaNotChunked($path, array $parameters)
{
$file = file_get_contents($parameters['media']);
$base = base64_encode($file);
$parameters['media'] = $base;
return $this->http('POST', self::UPLOAD_HOST, $path, $parameters);
}
/**
* Private method to upload media (chunked) to upload.twitter.com.
*
* @param string $path
* @param array $parameters
*
* @return array|object
*/
private function uploadMediaChunked($path, array $parameters)
{
$init = $this->http('POST', self::UPLOAD_HOST, $path, $this->mediaInitParameters($parameters));
// Append
$segmentIndex = 0;
$media = fopen($parameters['media'], 'rb');
while (!feof($media))
{
$this->http('POST', self::UPLOAD_HOST, 'media/upload', [
'command' => 'APPEND',
'media_id' => $init->media_id_string,
'segment_index' => $segmentIndex++,
'media_data' => base64_encode(fread($media, $this->chunkSize))
]);
}
fclose($media);
// Finalize
$finalize = $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
'command' => 'FINALIZE',
'media_id' => $init->media_id_string
]);
return $finalize;
}
/**
* Private method to get params for upload media chunked init.
* Twitter docs: https://dev.twitter.com/rest/reference/post/media/upload-init.html
*
* @param array $parameters
*
* @return array
*/
private function mediaInitParameters(array $parameters)
{
$return = [
'command' => 'INIT',
'media_type' => $parameters['media_type'],
'total_bytes' => filesize($parameters['media'])
];
if (isset($parameters['additional_owners'])) {
$return['additional_owners'] = $parameters['additional_owners'];
}
if (isset($parameters['media_category'])) {
$return['media_category'] = $parameters['media_category'];
}
return $return;
}
/**
* @param string $method
* @param string $host
* @param string $path
* @param array $parameters
*
* @return array|object
*/
private function http($method, $host, $path, array $parameters)
{
$this->resetLastResponse();
$url = sprintf('%s/%s/%s.json', $host, self::API_VERSION, $path);
$this->response->setApiPath($path);
$result = $this->oAuthRequest($url, $method, $parameters);
$response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
$this->response->setBody($response);
return $response;
}
/**
* Format and sign an OAuth / API request
*
* @param string $url
* @param string $method
* @param array $parameters
*
* @return string
* @throws TwitterOAuthException
*/
private function oAuthRequest($url, $method, array $parameters)
{
$request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
if (array_key_exists('oauth_callback', $parameters)) {
// Twitter doesn't like oauth_callback as a parameter.
unset($parameters['oauth_callback']);
}
if ($this->bearer === null) {
$request->signRequest($this->signatureMethod, $this->consumer, $this->token);
$authorization = $request->toHeader();
if (array_key_exists('oauth_verifier', $parameters)) {
// Twitter doesn't always work with oauth in the body and in the header
// and it's already included in the $authorization header
unset($parameters['oauth_verifier']);
}
} else {
$authorization = 'Authorization: Bearer ' . $this->bearer;
}
return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
}
/**
* Set Curl options.
*
* @return array
*/
private function curlOptions()
{
$options = [
// CURLOPT_VERBOSE => true,
CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
CURLOPT_HEADER => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_USERAGENT => $this->userAgent,
];
if ($this->useCAFile()) {
$options[CURLOPT_CAINFO] = __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem';
}
if($this->gzipEncoding) {
$options[CURLOPT_ENCODING] = 'gzip';
}
if (!empty($this->proxy)) {
$options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY'];
$options[CURLOPT_PROXYUSERPWD] = $this->proxy['CURLOPT_PROXYUSERPWD'];
$options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT'];
$options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
$options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
}
return $options;
}
/**
* Make an HTTP request
*
* @param string $url
* @param string $method
* @param string $authorization
* @param array $postfields
*
* @return string
* @throws TwitterOAuthException
*/
private function request($url, $method, $authorization, array $postfields)
{
$options = $this->curlOptions($url, $authorization);
$options[CURLOPT_URL] = $url;
$options[CURLOPT_HTTPHEADER] = ['Accept: application/json', $authorization, 'Expect:'];
switch ($method) {
case 'GET':
break;
case 'POST':
$options[CURLOPT_POST] = true;
$options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields);
break;
case 'DELETE':
$options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
break;
case 'PUT':
$options[CURLOPT_CUSTOMREQUEST] = 'PUT';
break;
}
if (in_array($method, ['GET', 'PUT', 'DELETE']) && !empty($postfields)) {
$options[CURLOPT_URL] .= '?' . Util::buildHttpQuery($postfields);
}
$curlHandle = curl_init();
curl_setopt_array($curlHandle, $options);
$response = curl_exec($curlHandle);
// Throw exceptions on cURL errors.
if (curl_errno($curlHandle) > 0) {
throw new TwitterOAuthException(curl_error($curlHandle), curl_errno($curlHandle));
}
$this->response->setHttpCode(curl_getinfo($curlHandle, CURLINFO_HTTP_CODE));
$parts = explode("\r\n\r\n", $response);
$responseBody = array_pop($parts);
$responseHeader = array_pop($parts);
$this->response->setHeaders($this->parseHeaders($responseHeader));
curl_close($curlHandle);
return $responseBody;
}
/**
* Get the header info to store.
*
* @param string $header
*
* @return array
*/
private function parseHeaders($header)
{
$headers = [];
foreach (explode("\r\n", $header) as $line) {
if (strpos($line, ':') !== false) {
list ($key, $value) = explode(': ', $line);
$key = str_replace('-', '_', strtolower($key));
$headers[$key] = trim($value);
}
}
return $headers;
}
/**
* Encode application authorization header with base64.
*
* @param Consumer $consumer
*
* @return string
*/
private function encodeAppAuthorization(Consumer $consumer)
{
$key = rawurlencode($consumer->key);
$secret = rawurlencode($consumer->secret);
return base64_encode($key . ':' . $secret);
}
/**
* Is the code running from a Phar module.
*
* @return boolean
*/
private function pharRunning()
{
return class_exists('Phar') && \Phar::running(false) !== '';
}
/**
* Use included CA file instead of OS provided list.
*
* @return boolean
*/
private function useCAFile()
{
/* Use CACert file when not in a PHAR file. */
return !$this->pharRunning();
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Abraham\TwitterOAuth;
/**
* @author Abraham Williams <abraham@abrah.am>
*/
class TwitterOAuthException extends \Exception
{
}

View File

@ -0,0 +1,115 @@
<?php
/**
* The MIT License
* Copyright (c) 2007 Andy Smith
*/
namespace Abraham\TwitterOAuth;
class Util
{
/**
* @param $input
*
* @return array|mixed|string
*/
public static function urlencodeRfc3986($input)
{
$output = '';
if (is_array($input)) {
$output = array_map([__NAMESPACE__ . '\Util', 'urlencodeRfc3986'], $input);
} elseif (is_scalar($input)) {
$output = rawurlencode($input);
}
return $output;
}
/**
* @param string $string
*
* @return string
*/
public static function urldecodeRfc3986($string)
{
return urldecode($string);
}
/**
* This function takes a input like a=b&a=c&d=e and returns the parsed
* parameters like this
* array('a' => array('b','c'), 'd' => 'e')
*
* @param string $input
*
* @return array
*/
public static function parseParameters($input)
{
if (!is_string($input)) {
return [];
}
$pairs = explode('&', $input);
$parameters = [];
foreach ($pairs as $pair) {
$split = explode('=', $pair, 2);
$parameter = Util::urldecodeRfc3986($split[0]);
$value = isset($split[1]) ? Util::urldecodeRfc3986($split[1]) : '';
if (isset($parameters[$parameter])) {
// We have already recieved parameter(s) with this name, so add to the list
// of parameters with this name
if (is_scalar($parameters[$parameter])) {
// This is the first duplicate, so transform scalar (string) into an array
// so we can add the duplicates
$parameters[$parameter] = [$parameters[$parameter]];
}
$parameters[$parameter][] = $value;
} else {
$parameters[$parameter] = $value;
}
}
return $parameters;
}
/**
* @param array $params
*
* @return string
*/
public static function buildHttpQuery(array $params)
{
if (empty($params)) {
return '';
}
// Urlencode both keys and values
$keys = Util::urlencodeRfc3986(array_keys($params));
$values = Util::urlencodeRfc3986(array_values($params));
$params = array_combine($keys, $values);
// Parameters are sorted by name, using lexicographical byte value ordering.
// Ref: Spec: 9.1.1 (1)
uksort($params, 'strcmp');
$pairs = [];
foreach ($params as $parameter => $value) {
if (is_array($value)) {
// If two or more parameters share the same name, they are sorted by their value
// Ref: Spec: 9.1.1 (1)
// June 12th, 2010 - changed to sort because of issue 164 by hidetaka
sort($value, SORT_STRING);
foreach ($value as $duplicateValue) {
$pairs[] = $parameter . '=' . $duplicateValue;
}
} else {
$pairs[] = $parameter . '=' . $value;
}
}
// For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
// Each name-value pair is separated by an '&' character (ASCII code 38)
return implode('&', $pairs);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Abraham\TwitterOAuth\Util;
/**
* @author louis <louis@systemli.org>
*/
class JsonDecoder
{
/**
* Decodes a JSON string to stdObject or associative array
*
* @param string $string
* @param bool $asArray
*
* @return array|object
*/
public static function decode($string, $asArray)
{
if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
return json_decode($string, $asArray, 512, JSON_BIGINT_AS_STRING);
}
return json_decode($string, $asArray);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
<?php
namespace Abraham\TwitterOAuth\Tests;
use Abraham\TwitterOAuth\SignatureMethod;
abstract class AbstractSignatureMethodTest extends \PHPUnit_Framework_TestCase
{
protected $name;
/**
* @return SignatureMethod
*/
abstract public function getClass();
abstract protected function signatureDataProvider();
public function testGetName()
{
$this->assertEquals($this->name, $this->getClass()->getName());
}
/**
* @dataProvider signatureDataProvider
*/
public function testBuildSignature($expected, $request, $consumer, $token)
{
$this->assertEquals($expected, $this->getClass()->buildSignature($request, $consumer, $token));
}
protected function getRequest()
{
return $this->getMockBuilder('Abraham\TwitterOAuth\Request')
->disableOriginalConstructor()
->getMock();
}
protected function getConsumer($key = null, $secret = null, $callbackUrl = null)
{
return $this->getMockBuilder('Abraham\TwitterOAuth\Consumer')
->setConstructorArgs([$key, $secret, $callbackUrl])
->getMock();
}
protected function getToken($key = null, $secret = null)
{
return $this->getMockBuilder('Abraham\TwitterOAuth\Token')
->setConstructorArgs([$key, $secret])
->getMock();
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Abraham\TwitterOAuth\Tests;
use Abraham\TwitterOAuth\Consumer;
class ConsumerTest extends \PHPUnit_Framework_TestCase {
public function testToString()
{
$key = uniqid();
$secret = uniqid();
$consumer = new Consumer($key, $secret);
$this->assertEquals("Consumer[key=$key,secret=$secret]", $consumer->__toString());
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Abraham\TwitterOAuth\Tests;
use Abraham\TwitterOAuth\HmacSha1;
class HmacSha1Test extends AbstractSignatureMethodTest
{
protected $name = 'HMAC-SHA1';
public function getClass()
{
return new HmacSha1();
}
public function signatureDataProvider()
{
return [
['5CoEcoq7XoKFjwYCieQvuzadeUA=', $this->getRequest(), $this->getConsumer(), $this->getToken()],
[
'EBw0gHngam3BTx8kfPfNNSyKem4=',
$this->getRequest(),
$this->getConsumer('key', 'secret'),
$this->getToken()
],
[
'kDsHFZzws2a5M6cAQjfpdNBo+v8=',
$this->getRequest(),
$this->getConsumer('key', 'secret'),
$this->getToken('key', 'secret')
],
['EBw0gHngam3BTx8kfPfNNSyKem4=', $this->getRequest(), $this->getConsumer('key', 'secret'), null],
];
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Abraham\TwitterOAuth\Tests;
use Abraham\TwitterOAuth\Token;
class TokenTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider tokenProvider
*/
public function testToString($expected, $key, $secret)
{
$token = new Token($key, $secret);
$this->assertEquals($expected, $token->__toString());
}
public function tokenProvider()
{
return [
['oauth_token=key&oauth_token_secret=secret', 'key', 'secret'],
['oauth_token=key%2Bkey&oauth_token_secret=secret', 'key+key', 'secret'],
['oauth_token=key~key&oauth_token_secret=secret', 'key~key', 'secret'],
];
}
}

View File

@ -0,0 +1,256 @@
<?php
/**
* WARNING: Running these tests will post and delete through the actual Twitter account.
*/
namespace Abraham\TwitterOAuth\Test;
use Abraham\TwitterOAuth\TwitterOAuth;
class TwitterOAuthTest extends \PHPUnit_Framework_TestCase
{
/** @var TwitterOAuth */
protected $twitter;
protected function setUp()
{
$this->twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET);
}
public function testBuildClient()
{
$this->assertObjectHasAttribute('consumer', $this->twitter);
$this->assertObjectHasAttribute('token', $this->twitter);
}
public function testSetOauthToken()
{
$twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
$twitter->setOauthToken(ACCESS_TOKEN, ACCESS_TOKEN_SECRET);
$this->assertObjectHasAttribute('consumer', $twitter);
$this->assertObjectHasAttribute('token', $twitter);
$twitter->get('friendships/show', ['target_screen_name' => 'twitterapi']);
$this->assertEquals(200, $twitter->getLastHttpCode());
}
public function testOauth2Token()
{
$twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
$result = $twitter->oauth2('oauth2/token', ['grant_type' => 'client_credentials']);
$this->assertEquals(200, $twitter->getLastHttpCode());
$this->assertObjectHasAttribute('token_type', $result);
$this->assertObjectHasAttribute('access_token', $result);
$this->assertEquals('bearer', $result->token_type);
return $result;
}
/**
* @depends testOauth2Token
*/
public function testBearerToken($accessToken)
{
$twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, null, $accessToken->access_token);
$result = $twitter->get('statuses/user_timeline', ['screen_name' => 'twitterapi']);
if ($twitter->getLastHttpCode() !== 200) {
$this->assertEquals('foo', substr($accessToken->access_token, 0, 75));
$this->assertEquals('foo', print_r($result, true));
}
$this->assertEquals(200, $twitter->getLastHttpCode());
return $accessToken;
}
// This causes issues for parallel run tests.
// /**
// * @depends testBearerToken
// */
// public function testOauth2TokenInvalidate($accessToken)
// {
// $twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
// // HACK: access_token is already urlencoded but gets urlencoded again breaking the invalidate request.
// $result = $twitter->oauth2(
// 'oauth2/invalidate_token',
// array('access_token' => urldecode($accessToken->access_token))
// );
// $this->assertEquals(200, $twitter->getLastHttpCode());
// $this->assertObjectHasAttribute('access_token', $result);
// return $result;
// }
public function testOauthRequestToken()
{
$twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
$result = $twitter->oauth('oauth/request_token', ['oauth_callback' => OAUTH_CALLBACK]);
$this->assertEquals(200, $twitter->getLastHttpCode());
$this->assertArrayHasKey('oauth_token', $result);
$this->assertArrayHasKey('oauth_token_secret', $result);
$this->assertArrayHasKey('oauth_callback_confirmed', $result);
$this->assertEquals('true', $result['oauth_callback_confirmed']);
return $result;
}
/**
* @expectedException \Abraham\TwitterOAuth\TwitterOAuthException
* @expectedExceptionMessage Could not authenticate you
*/
public function testOauthRequestTokenException()
{
$twitter = new TwitterOAuth('CONSUMER_KEY', 'CONSUMER_SECRET');
$result = $twitter->oauth('oauth/request_token', ['oauth_callback' => OAUTH_CALLBACK]);
return $result;
}
/**
* @expectedException \Abraham\TwitterOAuth\TwitterOAuthException
* @expectedExceptionMessage Invalid oauth_verifier parameter
* @depends testOauthRequestToken
*/
public function testOauthAccessTokenTokenException(array $requestToken)
{
// Can't test this without a browser logging into Twitter so check for the correct error instead.
$twitter = new TwitterOAuth(
CONSUMER_KEY,
CONSUMER_SECRET,
$requestToken['oauth_token'],
$requestToken['oauth_token_secret']
);
$twitter->oauth("oauth/access_token", ["oauth_verifier" => "fake_oauth_verifier"]);
}
public function testUrl()
{
$url = $this->twitter->url('oauth/authorize', ['foo' => 'bar', 'baz' => 'qux']);
$this->assertEquals('https://api.twitter.com/oauth/authorize?foo=bar&baz=qux', $url);
}
public function testGetAccountVerifyCredentials()
{
// Include entities boolean added to test parameter value cohearsion
$this->twitter->get('account/verify_credentials', ["include_entities" => false]);
$this->assertEquals(200, $this->twitter->getLastHttpCode());
}
// BUG: testing is too unreliable for now
// public function testSetProxy()
// {
// $this->twitter->setProxy(array(
// 'CURLOPT_PROXY' => PROXY,
// 'CURLOPT_PROXYUSERPWD' => PROXYUSERPWD,
// 'CURLOPT_PROXYPORT' => PROXYPORT,
// ));
// $this->twitter->setTimeouts(60, 60);
// $result = $this->twitter->get('account/verify_credentials');
// $this->assertEquals(200, $this->twitter->getLastHttpCode());
// $this->assertObjectHasAttribute('id', $result);
// }
public function testGetStatusesMentionsTimeline()
{
$this->twitter->get('statuses/mentions_timeline');
$this->assertEquals(200, $this->twitter->getLastHttpCode());
}
public function testGetSearchTweets()
{
$result = $this->twitter->get('search/tweets', ['q' => 'twitter']);
$this->assertEquals(200, $this->twitter->getLastHttpCode());
return $result->statuses;
}
/**
* @depends testGetSearchTweets
*/
public function testGetSearchTweetsWithMaxId($statuses)
{
$maxId = array_pop($statuses)->id_str;
$this->twitter->get('search/tweets', ['q' => 'twitter', 'max_id' => $maxId]);
$this->assertEquals(200, $this->twitter->getLastHttpCode());
}
public function testPostFavoritesCreate()
{
$result = $this->twitter->post('favorites/create', ['id' => '6242973112']);
if ($this->twitter->getLastHttpCode() == 403) {
// Status already favorited
$this->assertEquals(139, $result->errors[0]->code);
} else {
$this->assertEquals(200, $this->twitter->getLastHttpCode());
}
}
/**
* @depends testPostFavoritesCreate
*/
public function testPostFavoritesDestroy()
{
$this->twitter->post('favorites/destroy', ['id' => '6242973112']);
$this->assertEquals(200, $this->twitter->getLastHttpCode());
}
public function testPostStatusesUpdateWithMedia()
{
$this->twitter->setTimeouts(60, 30);
// Image source https://www.flickr.com/photos/titrans/8548825587/
$file_path = __DIR__ . '/kitten.jpg';
$result = $this->twitter->upload('media/upload', ['media' => $file_path]);
$this->assertEquals(200, $this->twitter->getLastHttpCode());
$this->assertObjectHasAttribute('media_id_string', $result);
$parameters = ['status' => 'Hello World ' . time(), 'media_ids' => $result->media_id_string];
$result = $this->twitter->post('statuses/update', $parameters);
$this->assertEquals(200, $this->twitter->getLastHttpCode());
if ($this->twitter->getLastHttpCode() == 200) {
$result = $this->twitter->post('statuses/destroy/' . $result->id_str);
}
return $result;
}
public function testPostStatusesUpdateWithMediaChunked()
{
$this->twitter->setTimeouts(60, 30);
// Video source http://www.sample-videos.com/
$file_path = __DIR__ . '/video.mp4';
$result = $this->twitter->upload('media/upload', ['media' => $file_path, 'media_type' => 'video/mp4'], true);
$this->assertEquals(201, $this->twitter->getLastHttpCode());
$this->assertObjectHasAttribute('media_id_string', $result);
$parameters = ['status' => 'Hello World ' . time(), 'media_ids' => $result->media_id_string];
$result = $this->twitter->post('statuses/update', $parameters);
$this->assertEquals(200, $this->twitter->getLastHttpCode());
if ($this->twitter->getLastHttpCode() == 200) {
$result = $this->twitter->post('statuses/destroy/' . $result->id_str);
}
return $result;
}
public function testPostStatusesUpdateUtf8()
{
$result = $this->twitter->post('statuses/update', ['status' => 'xこんにちは世界 ' . time()]);
$this->assertEquals(200, $this->twitter->getLastHttpCode());
return $result;
}
/**
* @depends testPostStatusesUpdateUtf8
*/
public function testPostStatusesDestroy($status)
{
$this->twitter->post('statuses/destroy/' . $status->id_str);
$this->assertEquals(200, $this->twitter->getLastHttpCode());
}
public function testLastResult()
{
$this->twitter->get('search/tweets', ['q' => 'twitter']);
$this->assertEquals('search/tweets', $this->twitter->getLastApiPath());
$this->assertEquals(200, $this->twitter->getLastHttpCode());
$this->assertObjectHasAttribute('statuses', $this->twitter->getLastBody());
}
/**
* @depends testLastResult
*/
public function testResetLastResponse()
{
$this->twitter->resetLastResponse();
$this->assertEquals('', $this->twitter->getLastApiPath());
$this->assertEquals(0, $this->twitter->getLastHttpCode());
$this->assertEquals([], $this->twitter->getLastBody());
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace Abraham\TwitterOAuth\Tests;
use Abraham\TwitterOAuth\Util\JsonDecoder;
class JsonDecoderTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider jsonProvider
*/
public function testDecode($input, $asArray, $expected)
{
$this->assertEquals($expected, JsonDecoder::decode($input, $asArray));
}
public function jsonProvider()
{
return [
['[]', true, []],
['[1,2,3]', true, [1, 2, 3]],
['[{"id": 556179961825226750}]', true, [['id' => 556179961825226750]]],
['[]', false, []],
['[1,2,3]', false, [1, 2, 3]],
[
'[{"id": 556179961825226750}]',
false,
[
$this->getClass(function ($object) {
$object->id = 556179961825226750;
return $object;
})
]
],
];
}
/**
* @param callable $callable
*
* @return stdClass
*/
private function getClass(\Closure $callable)
{
$object = new \stdClass();
return $callable($object);
}
}

View File

@ -0,0 +1,12 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
define('CONSUMER_KEY', getenv('TEST_CONSUMER_KEY'));
define('CONSUMER_SECRET', getenv('TEST_CONSUMER_SECRET'));
define('ACCESS_TOKEN', getenv('TEST_ACCESS_TOKEN'));
define('ACCESS_TOKEN_SECRET', getenv('TEST_ACCESS_TOKEN_SECRET'));
define('OAUTH_CALLBACK', getenv('TEST_OAUTH_CALLBACK'));
define('PROXY', getenv('TEST_CURLOPT_PROXY'));
define('PROXYUSERPWD', getenv('TEST_CURLOPT_PROXYUSERPWD'));
define('PROXYPORT', getenv('TEST_CURLOPT_PROXYPORT'));

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

View File

@ -0,0 +1,13 @@
# WARNING: Running the tests will perform live actions as the Twitter account.
# Set all values, move to `env`, run `source tests/env` and `phpunit` to start testing.
# To run the tests you must register Twitter application at https://app.twitter.com/.
export TEST_CONSUMER_KEY=
export TEST_CONSUMER_SECRET=
export TEST_ACCESS_TOKEN=
export TEST_ACCESS_TOKEN_SECRET=
export TEST_OAUTH_CALLBACK=
# You can find proxies for testing at http://proxylist.hidemyass.com/.
export TEST_CURLOPT_PROXY=
export TEST_CURLOPT_PROXYUSERPWD=
export TEST_CURLOPT_PROXYPORT=

Binary file not shown.

7
twitter/vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitd051a3fd38373f38066f9a7f62be4c25::getLoader();

445
twitter/vendor/composer/ClassLoader.php vendored Normal file
View File

@ -0,0 +1,445 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath.'\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

21
twitter/vendor/composer/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,9 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@ -0,0 +1,10 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Abraham\\TwitterOAuth\\' => array($vendorDir . '/abraham/twitteroauth/src'),
);

View File

@ -0,0 +1,52 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitd051a3fd38373f38066f9a7f62be4c25
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInitd051a3fd38373f38066f9a7f62be4c25', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInitd051a3fd38373f38066f9a7f62be4c25', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitd051a3fd38373f38066f9a7f62be4c25::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
return $loader;
}
}

View File

@ -0,0 +1,31 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitd051a3fd38373f38066f9a7f62be4c25
{
public static $prefixLengthsPsr4 = array (
'A' =>
array (
'Abraham\\TwitterOAuth\\' => 21,
),
);
public static $prefixDirsPsr4 = array (
'Abraham\\TwitterOAuth\\' =>
array (
0 => __DIR__ . '/..' . '/abraham/twitteroauth/src',
),
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitd051a3fd38373f38066f9a7f62be4c25::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitd051a3fd38373f38066f9a7f62be4c25::$prefixDirsPsr4;
}, null, ClassLoader::class);
}
}

58
twitter/vendor/composer/installed.json vendored Normal file
View File

@ -0,0 +1,58 @@
[
{
"name": "abraham/twitteroauth",
"version": "0.7.4",
"version_normalized": "0.7.4.0",
"source": {
"type": "git",
"url": "https://github.com/abraham/twitteroauth.git",
"reference": "c6f9e692552dd037b2324ed0dfa28a4e60875acf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/abraham/twitteroauth/zipball/c6f9e692552dd037b2324ed0dfa28a4e60875acf",
"reference": "c6f9e692552dd037b2324ed0dfa28a4e60875acf",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": "^5.6 || ^7.0"
},
"require-dev": {
"phpmd/phpmd": "~2.6",
"phpunit/phpunit": "~5.7",
"squizlabs/php_codesniffer": "~3.0"
},
"time": "2017-06-30T22:02:01+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Abraham\\TwitterOAuth\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Abraham Williams",
"email": "abraham@abrah.am",
"homepage": "https://abrah.am",
"role": "Developer"
}
],
"description": "The most popular PHP library for use with the Twitter OAuth REST API.",
"homepage": "https://twitteroauth.com",
"keywords": [
"Twitter API",
"Twitter oAuth",
"api",
"oauth",
"rest",
"social",
"twitter"
]
}
]