Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Post Body #610

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a767955
update cacert.pem
abraham Nov 10, 2016
04b20de
Merge pull request #1 from abraham/master
rafasashi Dec 12, 2016
cb20fe3
check Phar class exists
rafasashi Dec 12, 2016
56d63aa
Move check to see if Phar class is defined to discrete method
abraham Dec 12, 2016
119d5a8
Test PHP 7.1
abraham Dec 12, 2016
946209e
Fix syntax error on PHP 5.4
jtojnar Feb 5, 2017
a030b1b
Merge pull request #538 from jtojnar/php_5.4
abraham Feb 8, 2017
d2a3d0b
Use vender version of PHPUnit
abraham Feb 8, 2017
153ffdc
Add support to media_category in upload media chunked.
apizzini Feb 11, 2017
cf35729
Use array syntax short to improve readability
shakaran Feb 22, 2017
f0ae2f8
Merge pull request #547 from shakaran/master
abraham Feb 22, 2017
1d42f89
Add support for new parameters in upload media chunked init.
apizzini Feb 23, 2017
a3df975
Merge pull request #542 from apizzini/master
abraham Feb 23, 2017
4703dde
exclude oauth_verifier from the body
viniychuk May 2, 2017
dea88e3
Merge pull request #565 from Youshido/master
abraham May 2, 2017
78ece83
Update cacert.pem
abraham Jun 1, 2017
10c6cdd
Merge branch 'master' of github.com:abraham/twitteroauth
abraham Jun 1, 2017
2fc21c9
Switch TravisCi to trusty
abraham Jun 1, 2017
8266be8
Create CODE_OF_CONDUCT.md
abraham Jun 14, 2017
a517ac2
Increase and allow to change chunk size for media upload.
apizzini Jun 20, 2017
ba570a1
Merge pull request #575 from apizzini/master
abraham Jun 27, 2017
53dd815
Update dev dependencies
abraham Jun 30, 2017
a165a3c
PHPMD variable name
abraham Jun 30, 2017
a7b5953
Move curl options to a method
abraham Jun 30, 2017
11e3a11
Add new method to check if local CA file should be used
abraham Jun 30, 2017
c6f9e69
Merge pull request #580 from abraham/update-dev-dep
abraham Jun 30, 2017
d707ce7
Remove unused function variables
abraham Jun 30, 2017
520206c
Retry feature for server errors
kinoute Jul 26, 2017
f184807
Typo
kinoute Jul 26, 2017
033197e
Reset Attempts number
kinoute Jul 26, 2017
8becb71
Fix comments
kinoute Jul 26, 2017
46f90db
Update Config.php
kinoute Jul 26, 2017
6bb241d
Refactoring
kinoute Jul 26, 2017
1e787e3
Fix comments
kinoute Jul 26, 2017
e3d4b14
Refactoring
kinoute Jul 26, 2017
1b9dd4d
Merge pull request #588 from kinoute/master
abraham Jul 26, 2017
bb24525
Fix: only retry on errors equal or greater than 500 (#589)
kinoute Aug 1, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion .travis.yml
@@ -1,10 +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: phpunit
script:
- vendor/bin/phpunit
46 changes: 46 additions & 0 deletions CODE_OF_CONDUCT.md
@@ -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/
6 changes: 3 additions & 3 deletions composer.json
Expand Up @@ -22,9 +22,9 @@
"ext-curl": "*"
},
"require-dev": {
"phpunit/phpunit": "~5.6",
"squizlabs/php_codesniffer": "~2.7",
"phpmd/phpmd": "~2.4"
"phpunit/phpunit": "~5.7",
"squizlabs/php_codesniffer": "~3.0",
"phpmd/phpmd": "~2.6"
},
"autoload": {
"psr-4": {
Expand Down
32 changes: 32 additions & 0 deletions src/Config.php
Expand Up @@ -13,6 +13,13 @@ class Config
protected $timeout = 5;
/** @var int how long to wait while connecting to the API */
protected $connectionTimeout = 5;
/** @var int How many times we retry request when API is down */
protected $maxRetries = 0;
/** @var int Delay in seconds before we retry the request */
protected $retriesDelay = 1;



/**
* Decode JSON Response as associative Array
*
Expand All @@ -29,6 +36,9 @@ class Config
/** @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.
*
Expand All @@ -41,6 +51,18 @@ public function setTimeouts($connectionTimeout, $timeout)
$this->timeout = (int)$timeout;
}

/**
* Set the number of times to retry on error and how long between each.
*
* @param int $maxRetries
* @param int $retriesDelay
*/
public function setRetries($maxRetries, $retriesDelay)
{
$this->maxRetries = (int)$maxRetries;
$this->retriesDelay = (int)$retriesDelay;
}

/**
* @param bool $value
*/
Expand Down Expand Up @@ -74,4 +96,14 @@ 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;
}
}
170 changes: 138 additions & 32 deletions src/TwitterOAuth.php
Expand Up @@ -18,7 +18,6 @@ class TwitterOAuth extends Config
const API_VERSION = '1.1';
const API_HOST = 'https://api.twitter.com';
const UPLOAD_HOST = 'https://upload.twitter.com';
const UPLOAD_CHUNK = 40960; // 1024 * 40

/** @var Response details about the result of the last request */
private $response;
Expand All @@ -30,6 +29,8 @@ class TwitterOAuth extends Config
private $token;
/** @var HmacSha1 OAuth 1 signature type used by Twitter */
private $signatureMethod;
/** @var int Number of attempts we made for the request */
private $attempts = 0;

/**
* Constructor
Expand Down Expand Up @@ -101,6 +102,25 @@ public function resetLastResponse()
$this->response = new Response();
}

/**
* Resets the attempts number.
*/
private function resetAttemptsNumber()
{
$this->attempts = 0;
}

/**
* Delays the retries when they're activated.
*/
private function sleepIfNeeded()
{
if ($this->maxRetries && $this->attempts) {
sleep($this->retriesDelay);
}
}


/**
* Make URLs for user browser navigation.
*
Expand Down Expand Up @@ -262,22 +282,16 @@ private function uploadMediaNotChunked($path, array $parameters)
*/
private function uploadMediaChunked($path, array $parameters)
{
// Init
$init = $this->http('POST', self::UPLOAD_HOST, $path, [
'command' => 'INIT',
'media_type' => $parameters['media_type'],
'total_bytes' => filesize($parameters['media'])
]);
$init = $this->http('POST', self::UPLOAD_HOST, $path, $this->mediaInitParameters($parameters));
// Append
$segment_index = 0;
$segmentIndex = 0;
$media = fopen($parameters['media'], 'rb');
while (!feof($media))
{
while (!feof($media)) {
$this->http('POST', self::UPLOAD_HOST, 'media/upload', [
'command' => 'APPEND',
'media_id' => $init->media_id_string,
'segment_index' => $segment_index++,
'media_data' => base64_encode(fread($media, self::UPLOAD_CHUNK))
'segment_index' => $segmentIndex++,
'media_data' => base64_encode(fread($media, $this->chunkSize))
]);
}
fclose($media);
Expand All @@ -289,6 +303,30 @@ private function uploadMediaChunked($path, array $parameters)
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
Expand All @@ -300,14 +338,47 @@ private function uploadMediaChunked($path, array $parameters)
private function http($method, $host, $path, array $parameters)
{
$this->resetLastResponse();
$this->resetAttemptsNumber();
$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 $this->makeRequests($url, $method, $parameters);
}

/**
*
* Make requests and retry them (if enabled) in case of Twitter's problems.
*
* @param string $method
* @param string $url
* @param string $method
* @param array $parameters
*
* @return array|object
*/
private function makeRequests($url, $method, array $parameters)
{
do {
$this->sleepIfNeeded();
$result = $this->oAuthRequest($url, $method, $parameters);
$response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
$this->response->setBody($response);
$this->attempts++;
// Retry up to our $maxRetries number if we get errors greater than 500 (over capacity etc)
} while ($this->requestsAvailable());

return $response;
}

/**
* Checks if we have to retry request if API is down.
*
* @return bool
*/
private function requestsAvailable()
{
return ($this->maxRetries && ($this->attempts <= $this->maxRetries) && $this->getLastHttpCode() >= 500);
}

/**
* Format and sign an OAuth / API request
*
Expand All @@ -328,46 +399,40 @@ private function oAuthRequest($url, $method, array $parameters)
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);
}

/**
* Make an HTTP request
* Set Curl options.
*
* @param string $url
* @param string $method
* @param string $authorization
* @param array $postfields
*
* @return string
* @throws TwitterOAuthException
* @return array
*/
private function request($url, $method, $authorization, array $postfields)
private function curlOptions()
{
/* Curl settings */
$options = [
// CURLOPT_VERBOSE => true,
CURLOPT_CAINFO => __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem',
CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
CURLOPT_HEADER => true,
CURLOPT_HTTPHEADER => ['Accept: application/json', $authorization, 'Expect:'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_URL => $url,
CURLOPT_USERAGENT => $this->userAgent,
];

/* Remove CACert file when in a PHAR file. */
if (!empty(\Phar::running(false))) {
unset($options[CURLOPT_CAINFO]);
if ($this->useCAFile()) {
$options[CURLOPT_CAINFO] = __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem';
}

if($this->gzipEncoding) {
if ($this->gzipEncoding) {
$options[CURLOPT_ENCODING] = 'gzip';
}

Expand All @@ -379,6 +444,26 @@ private function request($url, $method, $authorization, array $postfields)
$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();
$options[CURLOPT_URL] = $url;
$options[CURLOPT_HTTPHEADER] = ['Accept: application/json', $authorization, 'Expect:'];

switch ($method) {
case 'GET':
break;
Expand Down Expand Up @@ -452,4 +537,25 @@ private function encodeAppAuthorization(Consumer $consumer)
$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();
}
}