├── twitteroauth
├── .gitignore
├── tests
│ ├── bootstrap.php
│ ├── kitten.jpg
│ ├── video.mp4
│ ├── ConsumerTest.php
│ ├── scripts
│ │ └── cacert.sh
│ ├── vars.php
│ ├── sample_env
│ ├── TokenTest.php
│ ├── HmacSha1Test.php
│ ├── Util
│ │ └── JsonDecoderTest.php
│ ├── AbstractSignatureMethodTest.php
│ └── TwitterOAuthTest.php
├── src
│ ├── TwitterOAuthException.php
│ ├── Util
│ │ └── JsonDecoder.php
│ ├── Consumer.php
│ ├── Token.php
│ ├── HmacSha1.php
│ ├── SignatureMethod.php
│ ├── Response.php
│ ├── Config.php
│ ├── Util.php
│ ├── Request.php
│ └── TwitterOAuth.php
├── phpunit.xml
├── .travis.yml
├── phpmd.xml
├── autoload.php
├── composer.json
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
└── CODE_OF_CONDUCT.md
├── config.default.php
├── .gitignore
├── LICENSE.txt
├── README.md
└── AutoFavoriter.php
/twitteroauth/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | composer.lock
3 | vendor
4 | env
5 | .cache
6 |
--------------------------------------------------------------------------------
/twitteroauth/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | class TwitterOAuthException extends \Exception
9 | {
10 | }
11 |
--------------------------------------------------------------------------------
/twitteroauth/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
The most popular PHP library for Twitter's OAuth REST API.
6 | 7 | See documentation at https://twitteroauth.com. 8 | 9 | PHP versions [listed](https://secure.php.net/supported-versions.php) as "active support" or "security fixes only" are supported. 10 | 11 |
12 |
--------------------------------------------------------------------------------
/twitteroauth/src/HmacSha1.php:
--------------------------------------------------------------------------------
1 | getSignatureBaseString();
31 |
32 | $parts = [$consumer->secret, null !== $token ? $token->secret : ""];
33 |
34 | $parts = Util::urlencodeRfc3986($parts);
35 | $key = implode('&', $parts);
36 |
37 | return base64_encode(hash_hmac('sha1', $signatureBase, $key, true));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/twitteroauth/tests/Util/JsonDecoderTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($expected, JsonDecoder::decode($input, $asArray));
15 | }
16 |
17 | public function jsonProvider()
18 | {
19 | return [
20 | ['[]', true, []],
21 | ['[1,2,3]', true, [1, 2, 3]],
22 | ['[{"id": 556179961825226750}]', true, [['id' => 556179961825226750]]],
23 | ['[]', false, []],
24 | ['[1,2,3]', false, [1, 2, 3]],
25 | [
26 | '[{"id": 556179961825226750}]',
27 | false,
28 | [
29 | $this->getClass(function ($object) {
30 | $object->id = 556179961825226750;
31 | return $object;
32 | })
33 | ]
34 | ],
35 |
36 | ];
37 | }
38 |
39 | /**
40 | * @param callable $callable
41 | *
42 | * @return stdClass
43 | */
44 | private function getClass(\Closure $callable)
45 | {
46 | $object = new \stdClass();
47 |
48 | return $callable($object);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/twitteroauth/tests/AbstractSignatureMethodTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($this->name, $this->getClass()->getName());
21 | }
22 |
23 | /**
24 | * @dataProvider signatureDataProvider
25 | */
26 | public function testBuildSignature($expected, $request, $consumer, $token)
27 | {
28 | $this->assertEquals($expected, $this->getClass()->buildSignature($request, $consumer, $token));
29 | }
30 |
31 | protected function getRequest()
32 | {
33 | return $this->getMockBuilder('Abraham\TwitterOAuth\Request')
34 | ->disableOriginalConstructor()
35 | ->getMock();
36 | }
37 |
38 | protected function getConsumer($key = null, $secret = null, $callbackUrl = null)
39 | {
40 | return $this->getMockBuilder('Abraham\TwitterOAuth\Consumer')
41 | ->setConstructorArgs([$key, $secret, $callbackUrl])
42 | ->getMock();
43 | }
44 |
45 | protected function getToken($key = null, $secret = null)
46 | {
47 | return $this->getMockBuilder('Abraham\TwitterOAuth\Token')
48 | ->setConstructorArgs([$key, $secret])
49 | ->getMock();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/twitteroauth/src/SignatureMethod.php:
--------------------------------------------------------------------------------
1 | buildSignature($request, $consumer, $token);
48 |
49 | // Check for zero length, although unlikely here
50 | if (strlen($built) == 0 || strlen($signature) == 0) {
51 | return false;
52 | }
53 |
54 | if (strlen($built) != strlen($signature)) {
55 | return false;
56 | }
57 |
58 | // Avoid a timing leak with a (hopefully) time insensitive compare
59 | $result = 0;
60 | for ($i = 0; $i < strlen($signature); $i++) {
61 | $result |= ord($built[$i]) ^ ord($signature[$i]);
62 | }
63 |
64 | return $result == 0;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/AutoFavoriter.php:
--------------------------------------------------------------------------------
1 | get('account/verify_credentials');
18 |
19 | if (isset($info->errors)){
20 |
21 | // 例外
22 | echo ' Error: Connection to Twitter failed.'."\n";
23 | echo ' Exception: '.$info->errors[0]->message."\n\n";
24 | exit(1);
25 |
26 | }
27 |
28 | } catch(Exception $e) {
29 |
30 | // 例外
31 | echo ' Error: Connection to Twitter failed.'."\n";
32 | echo ' Exception: '.$e."\n\n";
33 | exit(1);
34 |
35 | }
36 |
37 | echo ' Connected to Twitter.'."\n\n";
38 |
39 | // 検索設定
40 | if ($retweet_ignore){
41 |
42 | // RTをいいねしないようにする
43 | $tweets_params = array('q' => $keywords.' exclude:retweets' ,'count' => $count, 'result_type' => $result_type);
44 |
45 | } else {
46 |
47 | // RTを検索結果に含める
48 | $tweets_params = array('q' => $keywords ,'count' => $count, 'result_type' => $result_type);
49 |
50 | }
51 |
52 | // ツイートを検索
53 | $tweets = $connection->get('search/tweets', $tweets_params)->statuses;
54 |
55 | // ツイートごとに実行
56 | foreach ($tweets as $key => $value) {
57 |
58 | if ($favorite) $connection->post('favorites/create', array('id' => $value->id)); // いいねを実行
59 | if ($retweet) $connection->post('statuses/retweet', array('id' => $value->id)); // RT を実行
60 |
61 | // 実行タイプを判定
62 | if ($favorite and $retweet){
63 | $type = 'Favorite and Retweet';
64 | } else if ($favorite){
65 | $type = 'Favorite';
66 | } else if ($retweet){
67 | $type = 'Retweet';
68 | } else {
69 | $type = 'View';
70 | }
71 |
72 | // 表示
73 | echo ' '.$type.' '.sprintf('%02d', ($key + 1)).': Date: '.date('Y-m-d H:i:s', strtotime($value->created_at)).' ID: '.$value->id.
74 | ' Tweet: '.str_replace("\n", '', mb_substr($value->text, 0, 16)).'…'."\n";
75 |
76 | // 高速で実行すると凍結しかねないのでランダムで1.5~5秒くらい間をおく
77 | $sleep = rand(150, 500) * 10000;
78 | usleep($sleep);
79 |
80 | }
81 |
82 | echo "\n".' Finished. Exit.'."\n\n";
83 |
--------------------------------------------------------------------------------
/twitteroauth/src/Response.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class Response
11 | {
12 | /** @var string|null API path from the most recent request */
13 | private $apiPath;
14 | /** @var int HTTP status code from the most recent request */
15 | private $httpCode = 0;
16 | /** @var array HTTP headers from the most recent request */
17 | private $headers = [];
18 | /** @var array|object Response body from the most recent request */
19 | private $body = [];
20 | /** @var array HTTP headers from the most recent request that start with X */
21 | private $xHeaders = [];
22 |
23 | /**
24 | * @param string $apiPath
25 | */
26 | public function setApiPath($apiPath)
27 | {
28 | $this->apiPath = $apiPath;
29 | }
30 |
31 | /**
32 | * @return string|null
33 | */
34 | public function getApiPath()
35 | {
36 | return $this->apiPath;
37 | }
38 |
39 | /**
40 | * @param array|object $body
41 | */
42 | public function setBody($body)
43 | {
44 | $this->body = $body;
45 | }
46 |
47 | /**
48 | * @return array|object|string
49 | */
50 | public function getBody()
51 | {
52 | return $this->body;
53 | }
54 |
55 | /**
56 | * @param int $httpCode
57 | */
58 | public function setHttpCode($httpCode)
59 | {
60 | $this->httpCode = $httpCode;
61 | }
62 |
63 | /**
64 | * @return int
65 | */
66 | public function getHttpCode()
67 | {
68 | return $this->httpCode;
69 | }
70 |
71 | /**
72 | * @param array $headers
73 | */
74 | public function setHeaders(array $headers)
75 | {
76 | foreach ($headers as $key => $value) {
77 | if (substr($key, 0, 1) == 'x') {
78 | $this->xHeaders[$key] = $value;
79 | }
80 | }
81 | $this->headers = $headers;
82 | }
83 |
84 | /**
85 | * @return array
86 | */
87 | public function getsHeaders()
88 | {
89 | return $this->headers;
90 | }
91 |
92 | /**
93 | * @param array $xHeaders
94 | */
95 | public function setXHeaders(array $xHeaders = [])
96 | {
97 | $this->xHeaders = $xHeaders;
98 | }
99 |
100 | /**
101 | * @return array
102 | */
103 | public function getXHeaders()
104 | {
105 | return $this->xHeaders;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/twitteroauth/src/Config.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class Config
11 | {
12 | /** @var int How long to wait for a response from the API */
13 | protected $timeout = 5;
14 | /** @var int how long to wait while connecting to the API */
15 | protected $connectionTimeout = 5;
16 | /** @var int How many times we retry request when API is down */
17 | protected $maxRetries = 0;
18 | /** @var int Delay in seconds before we retry the request */
19 | protected $retriesDelay = 1;
20 |
21 | /**
22 | * Decode JSON Response as associative Array
23 | *
24 | * @see http://php.net/manual/en/function.json-decode.php
25 | *
26 | * @var bool
27 | */
28 | protected $decodeJsonAsArray = false;
29 | /** @var string User-Agent header */
30 | protected $userAgent = 'TwitterOAuth (+https://twitteroauth.com)';
31 | /** @var array Store proxy connection details */
32 | protected $proxy = [];
33 |
34 | /** @var bool Whether to encode the curl requests with gzip or not */
35 | protected $gzipEncoding = true;
36 |
37 | /** @var integer Size for Chunked Uploads */
38 | protected $chunkSize = 250000; // 0.25 MegaByte
39 |
40 | /**
41 | * Set the connection and response timeouts.
42 | *
43 | * @param int $connectionTimeout
44 | * @param int $timeout
45 | */
46 | public function setTimeouts($connectionTimeout, $timeout)
47 | {
48 | $this->connectionTimeout = (int)$connectionTimeout;
49 | $this->timeout = (int)$timeout;
50 | }
51 |
52 | /**
53 | * Set the number of times to retry on error and how long between each.
54 | *
55 | * @param int $maxRetries
56 | * @param int $retriesDelay
57 | */
58 | public function setRetries($maxRetries, $retriesDelay)
59 | {
60 | $this->maxRetries = (int)$maxRetries;
61 | $this->retriesDelay = (int)$retriesDelay;
62 | }
63 |
64 | /**
65 | * @param bool $value
66 | */
67 | public function setDecodeJsonAsArray($value)
68 | {
69 | $this->decodeJsonAsArray = (bool)$value;
70 | }
71 |
72 | /**
73 | * @param string $userAgent
74 | */
75 | public function setUserAgent($userAgent)
76 | {
77 | $this->userAgent = (string)$userAgent;
78 | }
79 |
80 | /**
81 | * @param array $proxy
82 | */
83 | public function setProxy(array $proxy)
84 | {
85 | $this->proxy = $proxy;
86 | }
87 |
88 | /**
89 | * Whether to encode the curl requests with gzip or not.
90 | *
91 | * @param boolean $gzipEncoding
92 | */
93 | public function setGzipEncoding($gzipEncoding)
94 | {
95 | $this->gzipEncoding = (bool)$gzipEncoding;
96 | }
97 |
98 | /**
99 | * Set the size of each part of file for chunked media upload.
100 | *
101 | * @param int $value
102 | */
103 | public function setChunkSize($value)
104 | {
105 | $this->chunkSize = (int)$value;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/twitteroauth/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | 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.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | 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.
28 |
29 | 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.
30 |
31 | ## Scope
32 |
33 | 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.
34 |
35 | ## Enforcement
36 |
37 | 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.
38 |
39 | 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.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/twitteroauth/src/Util.php:
--------------------------------------------------------------------------------
1 | array('b','c'), 'd' => 'e')
40 | *
41 | * @param string $input
42 | *
43 | * @return array
44 | */
45 | public static function parseParameters($input)
46 | {
47 | if (!is_string($input)) {
48 | return [];
49 | }
50 |
51 | $pairs = explode('&', $input);
52 |
53 | $parameters = [];
54 | foreach ($pairs as $pair) {
55 | $split = explode('=', $pair, 2);
56 | $parameter = Util::urldecodeRfc3986($split[0]);
57 | $value = isset($split[1]) ? Util::urldecodeRfc3986($split[1]) : '';
58 |
59 | if (isset($parameters[$parameter])) {
60 | // We have already recieved parameter(s) with this name, so add to the list
61 | // of parameters with this name
62 |
63 | if (is_scalar($parameters[$parameter])) {
64 | // This is the first duplicate, so transform scalar (string) into an array
65 | // so we can add the duplicates
66 | $parameters[$parameter] = [$parameters[$parameter]];
67 | }
68 |
69 | $parameters[$parameter][] = $value;
70 | } else {
71 | $parameters[$parameter] = $value;
72 | }
73 | }
74 | return $parameters;
75 | }
76 |
77 | /**
78 | * @param array $params
79 | *
80 | * @return string
81 | */
82 | public static function buildHttpQuery(array $params)
83 | {
84 | if (empty($params)) {
85 | return '';
86 | }
87 |
88 | // Urlencode both keys and values
89 | $keys = Util::urlencodeRfc3986(array_keys($params));
90 | $values = Util::urlencodeRfc3986(array_values($params));
91 | $params = array_combine($keys, $values);
92 |
93 | // Parameters are sorted by name, using lexicographical byte value ordering.
94 | // Ref: Spec: 9.1.1 (1)
95 | uksort($params, 'strcmp');
96 |
97 | $pairs = [];
98 | foreach ($params as $parameter => $value) {
99 | if (is_array($value)) {
100 | // If two or more parameters share the same name, they are sorted by their value
101 | // Ref: Spec: 9.1.1 (1)
102 | // June 12th, 2010 - changed to sort because of issue 164 by hidetaka
103 | sort($value, SORT_STRING);
104 | foreach ($value as $duplicateValue) {
105 | $pairs[] = $parameter . '=' . $duplicateValue;
106 | }
107 | } else {
108 | $pairs[] = $parameter . '=' . $value;
109 | }
110 | }
111 | // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
112 | // Each name-value pair is separated by an '&' character (ASCII code 38)
113 | return implode('&', $pairs);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/twitteroauth/src/Request.php:
--------------------------------------------------------------------------------
1 | parameters = $parameters;
27 | $this->httpMethod = $httpMethod;
28 | $this->httpUrl = $httpUrl;
29 | }
30 |
31 | /**
32 | * pretty much a helper function to set up the request
33 | *
34 | * @param Consumer $consumer
35 | * @param Token $token
36 | * @param string $httpMethod
37 | * @param string $httpUrl
38 | * @param array $parameters
39 | *
40 | * @return Request
41 | */
42 | public static function fromConsumerAndToken(
43 | Consumer $consumer,
44 | Token $token = null,
45 | $httpMethod,
46 | $httpUrl,
47 | array $parameters = [],
48 | $json = false
49 | ) {
50 | $defaults = [
51 | "oauth_version" => Request::$version,
52 | "oauth_nonce" => Request::generateNonce(),
53 | "oauth_timestamp" => time(),
54 | "oauth_consumer_key" => $consumer->key
55 | ];
56 | if (null !== $token) {
57 | $defaults['oauth_token'] = $token->key;
58 | }
59 |
60 | // The json payload is not included in the signature on json requests,
61 | // therefore it shouldn't be included in the parameters array.
62 | if ($json) {
63 | $parameters = $defaults;
64 | } else {
65 | $parameters = array_merge($defaults, $parameters);
66 | }
67 |
68 | return new Request($httpMethod, $httpUrl, $parameters);
69 | }
70 |
71 | /**
72 | * @param string $name
73 | * @param string $value
74 | */
75 | public function setParameter($name, $value)
76 | {
77 | $this->parameters[$name] = $value;
78 | }
79 |
80 | /**
81 | * @param string $name
82 | *
83 | * @return string|null
84 | */
85 | public function getParameter($name)
86 | {
87 | return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
88 | }
89 |
90 | /**
91 | * @return array
92 | */
93 | public function getParameters()
94 | {
95 | return $this->parameters;
96 | }
97 |
98 | /**
99 | * @param string $name
100 | */
101 | public function removeParameter($name)
102 | {
103 | unset($this->parameters[$name]);
104 | }
105 |
106 | /**
107 | * The request parameters, sorted and concatenated into a normalized string.
108 | *
109 | * @return string
110 | */
111 | public function getSignableParameters()
112 | {
113 | // Grab all parameters
114 | $params = $this->parameters;
115 |
116 | // Remove oauth_signature if present
117 | // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
118 | if (isset($params['oauth_signature'])) {
119 | unset($params['oauth_signature']);
120 | }
121 |
122 | return Util::buildHttpQuery($params);
123 | }
124 |
125 | /**
126 | * Returns the base string of this request
127 | *
128 | * The base string defined as the method, the url
129 | * and the parameters (normalized), each urlencoded
130 | * and the concated with &.
131 | *
132 | * @return string
133 | */
134 | public function getSignatureBaseString()
135 | {
136 | $parts = [
137 | $this->getNormalizedHttpMethod(),
138 | $this->getNormalizedHttpUrl(),
139 | $this->getSignableParameters()
140 | ];
141 |
142 | $parts = Util::urlencodeRfc3986($parts);
143 |
144 | return implode('&', $parts);
145 | }
146 |
147 | /**
148 | * Returns the HTTP Method in uppercase
149 | *
150 | * @return string
151 | */
152 | public function getNormalizedHttpMethod()
153 | {
154 | return strtoupper($this->httpMethod);
155 | }
156 |
157 | /**
158 | * parses the url and rebuilds it to be
159 | * scheme://host/path
160 | *
161 | * @return string
162 | */
163 | public function getNormalizedHttpUrl()
164 | {
165 | $parts = parse_url($this->httpUrl);
166 |
167 | $scheme = $parts['scheme'];
168 | $host = strtolower($parts['host']);
169 | $path = $parts['path'];
170 |
171 | return "$scheme://$host$path";
172 | }
173 |
174 | /**
175 | * Builds a url usable for a GET request
176 | *
177 | * @return string
178 | */
179 | public function toUrl()
180 | {
181 | $postData = $this->toPostdata();
182 | $out = $this->getNormalizedHttpUrl();
183 | if ($postData) {
184 | $out .= '?' . $postData;
185 | }
186 | return $out;
187 | }
188 |
189 | /**
190 | * Builds the data one would send in a POST request
191 | *
192 | * @return string
193 | */
194 | public function toPostdata()
195 | {
196 | return Util::buildHttpQuery($this->parameters);
197 | }
198 |
199 | /**
200 | * Builds the Authorization: header
201 | *
202 | * @return string
203 | * @throws TwitterOAuthException
204 | */
205 | public function toHeader()
206 | {
207 | $first = true;
208 | $out = 'Authorization: OAuth';
209 | foreach ($this->parameters as $k => $v) {
210 | if (substr($k, 0, 5) != "oauth") {
211 | continue;
212 | }
213 | if (is_array($v)) {
214 | throw new TwitterOAuthException('Arrays not supported in headers');
215 | }
216 | $out .= ($first) ? ' ' : ', ';
217 | $out .= Util::urlencodeRfc3986($k) . '="' . Util::urlencodeRfc3986($v) . '"';
218 | $first = false;
219 | }
220 | return $out;
221 | }
222 |
223 | /**
224 | * @return string
225 | */
226 | public function __toString()
227 | {
228 | return $this->toUrl();
229 | }
230 |
231 | /**
232 | * @param SignatureMethod $signatureMethod
233 | * @param Consumer $consumer
234 | * @param Token $token
235 | */
236 | public function signRequest(SignatureMethod $signatureMethod, Consumer $consumer, Token $token = null)
237 | {
238 | $this->setParameter("oauth_signature_method", $signatureMethod->getName());
239 | $signature = $this->buildSignature($signatureMethod, $consumer, $token);
240 | $this->setParameter("oauth_signature", $signature);
241 | }
242 |
243 | /**
244 | * @param SignatureMethod $signatureMethod
245 | * @param Consumer $consumer
246 | * @param Token $token
247 | *
248 | * @return string
249 | */
250 | public function buildSignature(SignatureMethod $signatureMethod, Consumer $consumer, Token $token = null)
251 | {
252 | return $signatureMethod->buildSignature($this, $consumer, $token);
253 | }
254 |
255 | /**
256 | * @return string
257 | */
258 | public static function generateNonce()
259 | {
260 | return md5(microtime() . mt_rand());
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/twitteroauth/tests/TwitterOAuthTest.php:
--------------------------------------------------------------------------------
1 | twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET);
17 | $this->userId = explode('-', ACCESS_TOKEN)[0];
18 | }
19 |
20 | public function testBuildClient()
21 | {
22 | $this->assertObjectHasAttribute('consumer', $this->twitter);
23 | $this->assertObjectHasAttribute('token', $this->twitter);
24 | }
25 |
26 | public function testSetOauthToken()
27 | {
28 | $twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
29 | $twitter->setOauthToken(ACCESS_TOKEN, ACCESS_TOKEN_SECRET);
30 | $this->assertObjectHasAttribute('consumer', $twitter);
31 | $this->assertObjectHasAttribute('token', $twitter);
32 | $twitter->get('friendships/show', ['target_screen_name' => 'twitterapi']);
33 | $this->assertEquals(200, $twitter->getLastHttpCode());
34 | }
35 |
36 | public function testOauth2Token()
37 | {
38 | $twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
39 | $result = $twitter->oauth2('oauth2/token', ['grant_type' => 'client_credentials']);
40 | $this->assertEquals(200, $twitter->getLastHttpCode());
41 | $this->assertObjectHasAttribute('token_type', $result);
42 | $this->assertObjectHasAttribute('access_token', $result);
43 | $this->assertEquals('bearer', $result->token_type);
44 | return $result;
45 | }
46 |
47 | /**
48 | * @depends testOauth2Token
49 | */
50 | public function testBearerToken($accessToken)
51 | {
52 | $twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, null, $accessToken->access_token);
53 | $result = $twitter->get('statuses/user_timeline', ['screen_name' => 'twitterapi']);
54 | if ($twitter->getLastHttpCode() !== 200) {
55 | $this->assertEquals('foo', substr($accessToken->access_token, 0, 75));
56 | $this->assertEquals('foo', print_r($result, true));
57 | }
58 | $this->assertEquals(200, $twitter->getLastHttpCode());
59 | return $accessToken;
60 | }
61 |
62 | // This causes issues for parallel run tests.
63 | // /**
64 | // * @depends testBearerToken
65 | // */
66 | // public function testOauth2TokenInvalidate($accessToken)
67 | // {
68 | // $twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
69 | // // HACK: access_token is already urlencoded but gets urlencoded again breaking the invalidate request.
70 | // $result = $twitter->oauth2(
71 | // 'oauth2/invalidate_token',
72 | // array('access_token' => urldecode($accessToken->access_token))
73 | // );
74 | // $this->assertEquals(200, $twitter->getLastHttpCode());
75 | // $this->assertObjectHasAttribute('access_token', $result);
76 | // return $result;
77 | // }
78 |
79 | public function testOauthRequestToken()
80 | {
81 | $twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
82 | $result = $twitter->oauth('oauth/request_token', ['oauth_callback' => OAUTH_CALLBACK]);
83 | $this->assertEquals(200, $twitter->getLastHttpCode());
84 | $this->assertArrayHasKey('oauth_token', $result);
85 | $this->assertArrayHasKey('oauth_token_secret', $result);
86 | $this->assertArrayHasKey('oauth_callback_confirmed', $result);
87 | $this->assertEquals('true', $result['oauth_callback_confirmed']);
88 | return $result;
89 | }
90 |
91 | /**
92 | * @expectedException \Abraham\TwitterOAuth\TwitterOAuthException
93 | * @expectedExceptionMessage Could not authenticate you
94 | */
95 | public function testOauthRequestTokenException()
96 | {
97 | $twitter = new TwitterOAuth('CONSUMER_KEY', 'CONSUMER_SECRET');
98 | $result = $twitter->oauth('oauth/request_token', ['oauth_callback' => OAUTH_CALLBACK]);
99 | return $result;
100 | }
101 |
102 | /**
103 | * @expectedException \Abraham\TwitterOAuth\TwitterOAuthException
104 | * @expectedExceptionMessage Invalid oauth_verifier parameter
105 | * @depends testOauthRequestToken
106 | */
107 | public function testOauthAccessTokenTokenException(array $requestToken)
108 | {
109 | // Can't test this without a browser logging into Twitter so check for the correct error instead.
110 | $twitter = new TwitterOAuth(
111 | CONSUMER_KEY,
112 | CONSUMER_SECRET,
113 | $requestToken['oauth_token'],
114 | $requestToken['oauth_token_secret']
115 | );
116 | $twitter->oauth("oauth/access_token", ["oauth_verifier" => "fake_oauth_verifier"]);
117 | }
118 |
119 | public function testUrl()
120 | {
121 | $url = $this->twitter->url('oauth/authorize', ['foo' => 'bar', 'baz' => 'qux']);
122 | $this->assertEquals('https://api.twitter.com/oauth/authorize?foo=bar&baz=qux', $url);
123 | }
124 |
125 | public function testGetAccountVerifyCredentials()
126 | {
127 | $user = $this->twitter->get('account/verify_credentials', [
128 | 'include_entities' => false,
129 | 'include_email' => true
130 | ]);
131 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
132 | $this->assertObjectHasAttribute('email', $user);
133 | }
134 |
135 | // BUG: testing is too unreliable for now
136 | // public function testSetProxy()
137 | // {
138 | // $this->twitter->setProxy(array(
139 | // 'CURLOPT_PROXY' => PROXY,
140 | // 'CURLOPT_PROXYUSERPWD' => PROXYUSERPWD,
141 | // 'CURLOPT_PROXYPORT' => PROXYPORT,
142 | // ));
143 | // $this->twitter->setTimeouts(60, 60);
144 | // $result = $this->twitter->get('account/verify_credentials');
145 | // $this->assertEquals(200, $this->twitter->getLastHttpCode());
146 | // $this->assertObjectHasAttribute('id', $result);
147 | // }
148 |
149 | public function testGetStatusesMentionsTimeline()
150 | {
151 | $this->twitter->get('statuses/mentions_timeline');
152 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
153 | }
154 |
155 | public function testGetSearchTweets()
156 | {
157 | $result = $this->twitter->get('search/tweets', ['q' => 'twitter']);
158 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
159 | return $result->statuses;
160 | }
161 |
162 | /**
163 | * @depends testGetSearchTweets
164 | */
165 | public function testGetSearchTweetsWithMaxId($statuses)
166 | {
167 | $maxId = array_pop($statuses)->id_str;
168 | $this->twitter->get('search/tweets', ['q' => 'twitter', 'max_id' => $maxId]);
169 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
170 | }
171 |
172 | public function testPostFavoritesCreate()
173 | {
174 | $result = $this->twitter->post('favorites/create', ['id' => '6242973112']);
175 | if ($this->twitter->getLastHttpCode() == 403) {
176 | // Status already favorited
177 | $this->assertEquals(139, $result->errors[0]->code);
178 | } else {
179 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
180 | }
181 | }
182 |
183 | /**
184 | * @depends testPostFavoritesCreate
185 | */
186 | public function testPostFavoritesDestroy()
187 | {
188 | $this->twitter->post('favorites/destroy', ['id' => '6242973112']);
189 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
190 | }
191 |
192 | public function testPostDirectMessagesEventsNew()
193 | {
194 | $data = [
195 | 'event' => [
196 | 'type' => 'message_create',
197 | 'message_create' => [
198 | 'target' => [
199 | 'recipient_id' => $this->userId
200 | ],
201 | 'message_data' => [
202 | 'text' => 'Hello World!'
203 | ]
204 | ]
205 | ]
206 | ];
207 | $result = $this->twitter->post('direct_messages/events/new', $data, true);
208 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
209 | return $result;
210 | }
211 |
212 | /**
213 | * @depends testPostDirectMessagesEventsNew
214 | */
215 | public function testDeleteDirectMessagesEventsDestroy($message)
216 | {
217 | $this->twitter->delete('direct_messages/events/destroy', ['id' => $message->event->id]);
218 | $this->assertEquals(204, $this->twitter->getLastHttpCode());
219 | }
220 |
221 | public function testPostStatusesUpdateWithMedia()
222 | {
223 | $this->twitter->setTimeouts(60, 30);
224 | // Image source https://www.flickr.com/photos/titrans/8548825587/
225 | $file_path = __DIR__ . '/kitten.jpg';
226 | $result = $this->twitter->upload('media/upload', ['media' => $file_path]);
227 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
228 | $this->assertObjectHasAttribute('media_id_string', $result);
229 | $parameters = ['status' => 'Hello World ' . time(), 'media_ids' => $result->media_id_string];
230 | $result = $this->twitter->post('statuses/update', $parameters);
231 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
232 | if ($this->twitter->getLastHttpCode() == 200) {
233 | $result = $this->twitter->post('statuses/destroy/' . $result->id_str);
234 | }
235 | return $result;
236 | }
237 |
238 | public function testPostStatusUpdateWithInvalidMediaThrowsException()
239 | {
240 | $this->expectException(\InvalidArgumentException::class);
241 | $file_path = __DIR__ . '/12345678900987654321.jpg';
242 | $this->assertFalse(\is_readable($file_path));
243 | $result = $this->twitter->upload('media/upload', ['media' => $file_path]);
244 | }
245 |
246 | public function testPostStatusesUpdateWithMediaChunked()
247 | {
248 | $this->twitter->setTimeouts(60, 30);
249 | // Video source http://www.sample-videos.com/
250 | $file_path = __DIR__ . '/video.mp4';
251 | $result = $this->twitter->upload('media/upload', ['media' => $file_path, 'media_type' => 'video/mp4'], true);
252 | $this->assertEquals(201, $this->twitter->getLastHttpCode());
253 | $this->assertObjectHasAttribute('media_id_string', $result);
254 | $parameters = ['status' => 'Hello World ' . time(), 'media_ids' => $result->media_id_string];
255 | $result = $this->twitter->post('statuses/update', $parameters);
256 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
257 | if ($this->twitter->getLastHttpCode() == 200) {
258 | $result = $this->twitter->post('statuses/destroy/' . $result->id_str);
259 | }
260 | return $result;
261 | }
262 |
263 | public function testPostStatusesUpdateUtf8()
264 | {
265 | $result = $this->twitter->post('statuses/update', ['status' => 'xこんにちは世界 ' . time()]);
266 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
267 | return $result;
268 | }
269 |
270 | /**
271 | * @depends testPostStatusesUpdateUtf8
272 | */
273 | public function testPostStatusesDestroy($status)
274 | {
275 | $this->twitter->post('statuses/destroy/' . $status->id_str);
276 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
277 | }
278 |
279 | public function testLastResult()
280 | {
281 | $this->twitter->get('search/tweets', ['q' => 'twitter']);
282 | $this->assertEquals('search/tweets', $this->twitter->getLastApiPath());
283 | $this->assertEquals(200, $this->twitter->getLastHttpCode());
284 | $this->assertObjectHasAttribute('statuses', $this->twitter->getLastBody());
285 | }
286 |
287 | /**
288 | * @depends testLastResult
289 | */
290 | public function testResetLastResponse()
291 | {
292 | $this->twitter->resetLastResponse();
293 | $this->assertEquals('', $this->twitter->getLastApiPath());
294 | $this->assertEquals(0, $this->twitter->getLastHttpCode());
295 | $this->assertEquals([], $this->twitter->getLastBody());
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/twitteroauth/src/TwitterOAuth.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class TwitterOAuth extends Config
17 | {
18 | const API_VERSION = '1.1';
19 | const API_HOST = 'https://api.twitter.com';
20 | const UPLOAD_HOST = 'https://upload.twitter.com';
21 |
22 | /** @var Response details about the result of the last request */
23 | private $response;
24 | /** @var string|null Application bearer token */
25 | private $bearer;
26 | /** @var Consumer Twitter application details */
27 | private $consumer;
28 | /** @var Token|null User access token details */
29 | private $token;
30 | /** @var HmacSha1 OAuth 1 signature type used by Twitter */
31 | private $signatureMethod;
32 | /** @var int Number of attempts we made for the request */
33 | private $attempts = 0;
34 |
35 | /**
36 | * Constructor
37 | *
38 | * @param string $consumerKey The Application Consumer Key
39 | * @param string $consumerSecret The Application Consumer Secret
40 | * @param string|null $oauthToken The Client Token (optional)
41 | * @param string|null $oauthTokenSecret The Client Token Secret (optional)
42 | */
43 | public function __construct($consumerKey, $consumerSecret, $oauthToken = null, $oauthTokenSecret = null)
44 | {
45 | $this->resetLastResponse();
46 | $this->signatureMethod = new HmacSha1();
47 | $this->consumer = new Consumer($consumerKey, $consumerSecret);
48 | if (!empty($oauthToken) && !empty($oauthTokenSecret)) {
49 | $this->setOauthToken($oauthToken, $oauthTokenSecret);
50 | }
51 | if (empty($oauthToken) && !empty($oauthTokenSecret)) {
52 | $this->setBearer($oauthTokenSecret);
53 | }
54 | }
55 |
56 | /**
57 | * @param string $oauthToken
58 | * @param string $oauthTokenSecret
59 | */
60 | public function setOauthToken($oauthToken, $oauthTokenSecret)
61 | {
62 | $this->token = new Token($oauthToken, $oauthTokenSecret);
63 | $this->bearer = null;
64 | }
65 |
66 | /**
67 | * @param string $oauthTokenSecret
68 | */
69 | public function setBearer($oauthTokenSecret)
70 | {
71 | $this->bearer = $oauthTokenSecret;
72 | $this->token = null;
73 | }
74 |
75 | /**
76 | * @return string|null
77 | */
78 | public function getLastApiPath()
79 | {
80 | return $this->response->getApiPath();
81 | }
82 |
83 | /**
84 | * @return int
85 | */
86 | public function getLastHttpCode()
87 | {
88 | return $this->response->getHttpCode();
89 | }
90 |
91 | /**
92 | * @return array
93 | */
94 | public function getLastXHeaders()
95 | {
96 | return $this->response->getXHeaders();
97 | }
98 |
99 | /**
100 | * @return array|object|null
101 | */
102 | public function getLastBody()
103 | {
104 | return $this->response->getBody();
105 | }
106 |
107 | /**
108 | * Resets the last response cache.
109 | */
110 | public function resetLastResponse()
111 | {
112 | $this->response = new Response();
113 | }
114 |
115 | /**
116 | * Resets the attempts number.
117 | */
118 | private function resetAttemptsNumber()
119 | {
120 | $this->attempts = 0;
121 | }
122 |
123 | /**
124 | * Delays the retries when they're activated.
125 | */
126 | private function sleepIfNeeded()
127 | {
128 | if ($this->maxRetries && $this->attempts) {
129 | sleep($this->retriesDelay);
130 | }
131 | }
132 |
133 |
134 | /**
135 | * Make URLs for user browser navigation.
136 | *
137 | * @param string $path
138 | * @param array $parameters
139 | *
140 | * @return string
141 | */
142 | public function url($path, array $parameters)
143 | {
144 | $this->resetLastResponse();
145 | $this->response->setApiPath($path);
146 | $query = http_build_query($parameters);
147 | return sprintf('%s/%s?%s', self::API_HOST, $path, $query);
148 | }
149 |
150 | /**
151 | * Make /oauth/* requests to the API.
152 | *
153 | * @param string $path
154 | * @param array $parameters
155 | *
156 | * @return array
157 | * @throws TwitterOAuthException
158 | */
159 | public function oauth($path, array $parameters = [])
160 | {
161 | $response = [];
162 | $this->resetLastResponse();
163 | $this->response->setApiPath($path);
164 | $url = sprintf('%s/%s', self::API_HOST, $path);
165 | $result = $this->oAuthRequest($url, 'POST', $parameters);
166 |
167 | if ($this->getLastHttpCode() != 200) {
168 | throw new TwitterOAuthException($result);
169 | }
170 |
171 | parse_str($result, $response);
172 | $this->response->setBody($response);
173 |
174 | return $response;
175 | }
176 |
177 | /**
178 | * Make /oauth2/* requests to the API.
179 | *
180 | * @param string $path
181 | * @param array $parameters
182 | *
183 | * @return array|object
184 | */
185 | public function oauth2($path, array $parameters = [])
186 | {
187 | $method = 'POST';
188 | $this->resetLastResponse();
189 | $this->response->setApiPath($path);
190 | $url = sprintf('%s/%s', self::API_HOST, $path);
191 | $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
192 | $authorization = 'Authorization: Basic ' . $this->encodeAppAuthorization($this->consumer);
193 | $result = $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
194 | $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
195 | $this->response->setBody($response);
196 | return $response;
197 | }
198 |
199 | /**
200 | * Make GET requests to the API.
201 | *
202 | * @param string $path
203 | * @param array $parameters
204 | *
205 | * @return array|object
206 | */
207 | public function get($path, array $parameters = [])
208 | {
209 | return $this->http('GET', self::API_HOST, $path, $parameters, false);
210 | }
211 |
212 | /**
213 | * Make POST requests to the API.
214 | *
215 | * @param string $path
216 | * @param array $parameters
217 | * @param bool $json
218 | *
219 | * @return array|object
220 | */
221 | public function post($path, array $parameters = [], $json = false)
222 | {
223 | return $this->http('POST', self::API_HOST, $path, $parameters, $json);
224 | }
225 |
226 | /**
227 | * Make DELETE requests to the API.
228 | *
229 | * @param string $path
230 | * @param array $parameters
231 | *
232 | * @return array|object
233 | */
234 | public function delete($path, array $parameters = [])
235 | {
236 | return $this->http('DELETE', self::API_HOST, $path, $parameters, false);
237 | }
238 |
239 | /**
240 | * Make PUT requests to the API.
241 | *
242 | * @param string $path
243 | * @param array $parameters
244 | *
245 | * @return array|object
246 | */
247 | public function put($path, array $parameters = [])
248 | {
249 | return $this->http('PUT', self::API_HOST, $path, $parameters, false);
250 | }
251 |
252 | /**
253 | * Upload media to upload.twitter.com.
254 | *
255 | * @param string $path
256 | * @param array $parameters
257 | * @param boolean $chunked
258 | *
259 | * @return array|object
260 | */
261 | public function upload($path, array $parameters = [], $chunked = false)
262 | {
263 | if ($chunked) {
264 | return $this->uploadMediaChunked($path, $parameters);
265 | } else {
266 | return $this->uploadMediaNotChunked($path, $parameters);
267 | }
268 | }
269 |
270 | /**
271 | * Progression of media upload
272 | *
273 | * @param string $media_id
274 | *
275 | * @return array|object
276 | */
277 | public function mediaStatus($media_id)
278 | {
279 | return $this->http('GET', self::UPLOAD_HOST, 'media/upload', [
280 | 'command' => 'STATUS',
281 | 'media_id' => $media_id
282 | ], false);
283 | }
284 |
285 | /**
286 | * Private method to upload media (not chunked) to upload.twitter.com.
287 | *
288 | * @param string $path
289 | * @param array $parameters
290 | *
291 | * @return array|object
292 | */
293 | private function uploadMediaNotChunked($path, array $parameters)
294 | {
295 | if (! is_readable($parameters['media']) ||
296 | ($file = file_get_contents($parameters['media'])) === false) {
297 | throw new \InvalidArgumentException('You must supply a readable file');
298 | }
299 | $parameters['media'] = base64_encode($file);
300 | return $this->http('POST', self::UPLOAD_HOST, $path, $parameters, false);
301 | }
302 |
303 | /**
304 | * Private method to upload media (chunked) to upload.twitter.com.
305 | *
306 | * @param string $path
307 | * @param array $parameters
308 | *
309 | * @return array|object
310 | */
311 | private function uploadMediaChunked($path, array $parameters)
312 | {
313 | $init = $this->http('POST', self::UPLOAD_HOST, $path, $this->mediaInitParameters($parameters), false);
314 | // Append
315 | $segmentIndex = 0;
316 | $media = fopen($parameters['media'], 'rb');
317 | while (!feof($media)) {
318 | $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
319 | 'command' => 'APPEND',
320 | 'media_id' => $init->media_id_string,
321 | 'segment_index' => $segmentIndex++,
322 | 'media_data' => base64_encode(fread($media, $this->chunkSize))
323 | ], false);
324 | }
325 | fclose($media);
326 | // Finalize
327 | $finalize = $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
328 | 'command' => 'FINALIZE',
329 | 'media_id' => $init->media_id_string
330 | ], false);
331 | return $finalize;
332 | }
333 |
334 | /**
335 | * Private method to get params for upload media chunked init.
336 | * Twitter docs: https://dev.twitter.com/rest/reference/post/media/upload-init.html
337 | *
338 | * @param array $parameters
339 | *
340 | * @return array
341 | */
342 | private function mediaInitParameters(array $parameters)
343 | {
344 | $allowed_keys = ['media_type', 'additional_owners', 'media_category', 'shared'];
345 | $base = [
346 | 'command' => 'INIT',
347 | 'total_bytes' => filesize($parameters['media'])
348 | ];
349 | $allowed_parameters = array_intersect_key($parameters, array_flip($allowed_keys));
350 | return array_merge($base, $allowed_parameters);
351 | }
352 |
353 | /**
354 | * Cleanup any parameters that are known not to work.
355 | *
356 | * @param array $parameters
357 | *
358 | * @return array
359 | */
360 | private function cleanUpParameters(array $parameters)
361 | {
362 | foreach ($parameters as $key => $value) {
363 | // PHP coerces `true` to `"1"` which some Twitter APIs don't like.
364 | if (is_bool($value)) {
365 | $parameters[$key] = var_export($value, true);
366 | }
367 | }
368 | return $parameters;
369 | }
370 |
371 | /**
372 | * @param string $method
373 | * @param string $host
374 | * @param string $path
375 | * @param array $parameters
376 | * @param bool $json
377 | *
378 | * @return array|object
379 | */
380 | private function http($method, $host, $path, array $parameters, $json)
381 | {
382 | $this->resetLastResponse();
383 | $this->resetAttemptsNumber();
384 | $url = sprintf('%s/%s/%s.json', $host, self::API_VERSION, $path);
385 | $this->response->setApiPath($path);
386 | if (!$json) {
387 | $parameters = $this->cleanUpParameters($parameters);
388 | }
389 | return $this->makeRequests($url, $method, $parameters, $json);
390 | }
391 |
392 | /**
393 | *
394 | * Make requests and retry them (if enabled) in case of Twitter's problems.
395 | *
396 | * @param string $method
397 | * @param string $url
398 | * @param string $method
399 | * @param array $parameters
400 | * @param bool $json
401 | *
402 | * @return array|object
403 | */
404 | private function makeRequests($url, $method, array $parameters, $json)
405 | {
406 | do {
407 | $this->sleepIfNeeded();
408 | $result = $this->oAuthRequest($url, $method, $parameters, $json);
409 | $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
410 | $this->response->setBody($response);
411 | $this->attempts++;
412 | // Retry up to our $maxRetries number if we get errors greater than 500 (over capacity etc)
413 | } while ($this->requestsAvailable());
414 |
415 | return $response;
416 | }
417 |
418 | /**
419 | * Checks if we have to retry request if API is down.
420 | *
421 | * @return bool
422 | */
423 | private function requestsAvailable()
424 | {
425 | return ($this->maxRetries && ($this->attempts <= $this->maxRetries) && $this->getLastHttpCode() >= 500);
426 | }
427 |
428 | /**
429 | * Format and sign an OAuth / API request
430 | *
431 | * @param string $url
432 | * @param string $method
433 | * @param array $parameters
434 | * @param bool $json
435 | *
436 | * @return string
437 | * @throws TwitterOAuthException
438 | */
439 | private function oAuthRequest($url, $method, array $parameters, $json = false)
440 | {
441 | $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters, $json);
442 | if (array_key_exists('oauth_callback', $parameters)) {
443 | // Twitter doesn't like oauth_callback as a parameter.
444 | unset($parameters['oauth_callback']);
445 | }
446 | if ($this->bearer === null) {
447 | $request->signRequest($this->signatureMethod, $this->consumer, $this->token);
448 | $authorization = $request->toHeader();
449 | if (array_key_exists('oauth_verifier', $parameters)) {
450 | // Twitter doesn't always work with oauth in the body and in the header
451 | // and it's already included in the $authorization header
452 | unset($parameters['oauth_verifier']);
453 | }
454 | } else {
455 | $authorization = 'Authorization: Bearer ' . $this->bearer;
456 | }
457 | return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters, $json);
458 | }
459 |
460 | /**
461 | * Set Curl options.
462 | *
463 | * @return array
464 | */
465 | private function curlOptions()
466 | {
467 | $options = [
468 | // CURLOPT_VERBOSE => true,
469 | CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
470 | CURLOPT_HEADER => true,
471 | CURLOPT_RETURNTRANSFER => true,
472 | CURLOPT_SSL_VERIFYHOST => 2,
473 | CURLOPT_SSL_VERIFYPEER => true,
474 | CURLOPT_TIMEOUT => $this->timeout,
475 | CURLOPT_USERAGENT => $this->userAgent,
476 | ];
477 |
478 | if ($this->useCAFile()) {
479 | $options[CURLOPT_CAINFO] = __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem';
480 | }
481 |
482 | if ($this->gzipEncoding) {
483 | $options[CURLOPT_ENCODING] = 'gzip';
484 | }
485 |
486 | if (!empty($this->proxy)) {
487 | $options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY'];
488 | $options[CURLOPT_PROXYUSERPWD] = $this->proxy['CURLOPT_PROXYUSERPWD'];
489 | $options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT'];
490 | $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
491 | $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
492 | }
493 |
494 | return $options;
495 | }
496 |
497 | /**
498 | * Make an HTTP request
499 | *
500 | * @param string $url
501 | * @param string $method
502 | * @param string $authorization
503 | * @param array $postfields
504 | * @param bool $json
505 | *
506 | * @return string
507 | * @throws TwitterOAuthException
508 | */
509 | private function request($url, $method, $authorization, array $postfields, $json = false)
510 | {
511 | $options = $this->curlOptions();
512 | $options[CURLOPT_URL] = $url;
513 | $options[CURLOPT_HTTPHEADER] = ['Accept: application/json', $authorization, 'Expect:'];
514 |
515 | switch ($method) {
516 | case 'GET':
517 | break;
518 | case 'POST':
519 | $options[CURLOPT_POST] = true;
520 | if ($json) {
521 | $options[CURLOPT_HTTPHEADER][] = 'Content-type: application/json';
522 | $options[CURLOPT_POSTFIELDS] = json_encode($postfields);
523 | } else {
524 | $options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields);
525 | }
526 | break;
527 | case 'DELETE':
528 | $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
529 | break;
530 | case 'PUT':
531 | $options[CURLOPT_CUSTOMREQUEST] = 'PUT';
532 | break;
533 | }
534 |
535 | if (in_array($method, ['GET', 'PUT', 'DELETE']) && !empty($postfields)) {
536 | $options[CURLOPT_URL] .= '?' . Util::buildHttpQuery($postfields);
537 | }
538 |
539 |
540 | $curlHandle = curl_init();
541 | curl_setopt_array($curlHandle, $options);
542 | $response = curl_exec($curlHandle);
543 |
544 | // Throw exceptions on cURL errors.
545 | if (curl_errno($curlHandle) > 0) {
546 | $error = curl_error($curlHandle);
547 | $errorNo = curl_errno($curlHandle);
548 | curl_close($curlHandle);
549 | throw new TwitterOAuthException($error, $errorNo);
550 | }
551 |
552 | $this->response->setHttpCode(curl_getinfo($curlHandle, CURLINFO_HTTP_CODE));
553 | $parts = explode("\r\n\r\n", $response);
554 | $responseBody = array_pop($parts);
555 | $responseHeader = array_pop($parts);
556 | $this->response->setHeaders($this->parseHeaders($responseHeader));
557 |
558 | curl_close($curlHandle);
559 |
560 | return $responseBody;
561 | }
562 |
563 | /**
564 | * Get the header info to store.
565 | *
566 | * @param string $header
567 | *
568 | * @return array
569 | */
570 | private function parseHeaders($header)
571 | {
572 | $headers = [];
573 | foreach (explode("\r\n", $header) as $line) {
574 | if (strpos($line, ':') !== false) {
575 | list ($key, $value) = explode(': ', $line);
576 | $key = str_replace('-', '_', strtolower($key));
577 | $headers[$key] = trim($value);
578 | }
579 | }
580 | return $headers;
581 | }
582 |
583 | /**
584 | * Encode application authorization header with base64.
585 | *
586 | * @param Consumer $consumer
587 | *
588 | * @return string
589 | */
590 | private function encodeAppAuthorization(Consumer $consumer)
591 | {
592 | $key = rawurlencode($consumer->key);
593 | $secret = rawurlencode($consumer->secret);
594 | return base64_encode($key . ':' . $secret);
595 | }
596 |
597 | /**
598 | * Is the code running from a Phar module.
599 | *
600 | * @return boolean
601 | */
602 | private function pharRunning()
603 | {
604 | return class_exists('Phar') && \Phar::running(false) !== '';
605 | }
606 |
607 | /**
608 | * Use included CA file instead of OS provided list.
609 | *
610 | * @return boolean
611 | */
612 | private function useCAFile()
613 | {
614 | /* Use CACert file when not in a PHAR file. */
615 | return !$this->pharRunning();
616 | }
617 | }
618 |
--------------------------------------------------------------------------------