]>
Commit | Line | Data |
---|---|---|
54a3dd8d AD |
1 | <?php |
2 | /** | |
3 | * tmhOAuth | |
4 | * | |
5 | * An OAuth 1.0A library written in PHP. | |
6 | * The library supports file uploading using multipart/form as well as general | |
7 | * REST requests. OAuth authentication is sent using the an Authorization Header. | |
8 | * | |
9 | * @author themattharris | |
10 | * @version 0.4 | |
11 | * | |
12 | * 03 March 2011 | |
13 | */ | |
14 | class tmhOAuth { | |
15 | /** | |
16 | * Creates a new tmhOAuth object | |
17 | * | |
18 | * @param string $config, the configuration to use for this request | |
19 | */ | |
20 | function __construct($config) { | |
21 | $this->params = array(); | |
22 | $this->auto_fixed_time = false; | |
23 | ||
24 | // default configuration options | |
25 | $this->config = array_merge( | |
26 | array( | |
27 | 'consumer_key' => '', | |
28 | 'consumer_secret' => '', | |
29 | 'user_token' => '', | |
30 | 'user_secret' => '', | |
31 | 'use_ssl' => true, | |
32 | 'host' => 'api.twitter.com', | |
33 | 'debug' => false, | |
34 | 'force_nonce' => false, | |
35 | 'nonce' => false, // used for checking signatures. leave as false for auto | |
36 | 'force_timestamp' => false, | |
37 | 'timestamp' => false, // used for checking signatures. leave as false for auto | |
38 | 'oauth_version' => '1.0', | |
39 | ||
40 | // you probably don't want to change any of these curl values | |
41 | 'curl_connecttimeout' => 30, | |
42 | 'curl_timeout' => 10, | |
43 | // for security you may want to set this to TRUE. If you do you need | |
44 | // to install the servers certificate in your local certificate store. | |
45 | 'curl_ssl_verifypeer' => false, | |
46 | 'curl_followlocation' => false, // whether to follow redirects or not | |
47 | // support for proxy servers | |
48 | 'curl_proxy' => false, // really you don't want to use this if you are using streaming | |
49 | 'curl_proxyuserpwd' => false, // format username:password for proxy, if required | |
50 | ||
51 | // streaming API | |
52 | 'is_streaming' => false, | |
53 | 'streaming_eol' => "\r\n", | |
54 | 'streaming_metrics_interval' => 60, | |
55 | ), | |
56 | $config | |
57 | ); | |
58 | } | |
59 | ||
60 | /** | |
61 | * Generates a random OAuth nonce. | |
62 | * If 'force_nonce' is true a nonce is not generated and the value in the configuration will be retained. | |
63 | * | |
64 | * @param string $length how many characters the nonce should be before MD5 hashing. default 12 | |
65 | * @param string $include_time whether to include time at the beginning of the nonce. default true | |
66 | * @return void | |
67 | */ | |
68 | private function create_nonce($length=12, $include_time=true) { | |
69 | if ($this->config['force_nonce'] == false) { | |
70 | $sequence = array_merge(range(0,9), range('A','Z'), range('a','z')); | |
71 | $length = $length > count($sequence) ? count($sequence) : $length; | |
72 | shuffle($sequence); | |
73 | $this->config['nonce'] = md5(substr(microtime() . implode($sequence), 0, $length)); | |
74 | } | |
75 | } | |
76 | ||
77 | /** | |
78 | * Generates a timestamp. | |
79 | * If 'force_timestamp' is true a nonce is not generated and the value in the configuration will be retained. | |
80 | * | |
81 | * @return void | |
82 | */ | |
83 | private function create_timestamp() { | |
84 | $this->config['timestamp'] = ($this->config['force_timestamp'] == false ? time() : $this->config['timestamp']); | |
85 | } | |
86 | ||
87 | /** | |
88 | * Encodes the string or array passed in a way compatible with OAuth. | |
89 | * If an array is passed each array value will will be encoded. | |
90 | * | |
91 | * @param mixed $data the scalar or array to encode | |
92 | * @return $data encoded in a way compatible with OAuth | |
93 | */ | |
94 | private function safe_encode($data) { | |
95 | if (is_array($data)) { | |
96 | return array_map(array($this, 'safe_encode'), $data); | |
97 | } else if (is_scalar($data)) { | |
98 | return str_ireplace( | |
99 | array('+', '%7E'), | |
100 | array(' ', '~'), | |
101 | rawurlencode($data) | |
102 | ); | |
103 | } else { | |
104 | return ''; | |
105 | } | |
106 | } | |
107 | ||
108 | /** | |
109 | * Decodes the string or array from it's URL encoded form | |
110 | * If an array is passed each array value will will be decoded. | |
111 | * | |
112 | * @param mixed $data the scalar or array to decode | |
113 | * @return $data decoded from the URL encoded form | |
114 | */ | |
115 | private function safe_decode($data) { | |
116 | if (is_array($data)) { | |
117 | return array_map(array($this, 'safe_decode'), $data); | |
118 | } else if (is_scalar($data)) { | |
119 | return rawurldecode($data); | |
120 | } else { | |
121 | return ''; | |
122 | } | |
123 | } | |
124 | ||
125 | /** | |
126 | * Returns an array of the standard OAuth parameters. | |
127 | * | |
128 | * @return array all required OAuth parameters, safely encoded | |
129 | */ | |
130 | private function get_defaults() { | |
131 | $defaults = array( | |
132 | 'oauth_version' => $this->config['oauth_version'], | |
133 | 'oauth_nonce' => $this->config['nonce'], | |
134 | 'oauth_timestamp' => $this->config['timestamp'], | |
135 | 'oauth_consumer_key' => $this->config['consumer_key'], | |
136 | 'oauth_signature_method' => 'HMAC-SHA1', | |
137 | ); | |
138 | ||
139 | // include the user token if it exists | |
140 | if ( $this->config['user_token'] ) | |
141 | $defaults['oauth_token'] = $this->config['user_token']; | |
142 | ||
143 | // safely encode | |
144 | foreach ($defaults as $k => $v) { | |
145 | $_defaults[$this->safe_encode($k)] = $this->safe_encode($v); | |
146 | } | |
147 | ||
148 | return $_defaults; | |
149 | } | |
150 | ||
151 | /** | |
152 | * Extracts and decodes OAuth parameters from the passed string | |
153 | * | |
154 | * @param string $body the response body from an OAuth flow method | |
155 | * @return array the response body safely decoded to an array of key => values | |
156 | */ | |
157 | function extract_params($body) { | |
158 | $kvs = explode('&', $body); | |
159 | $decoded = array(); | |
160 | foreach ($kvs as $kv) { | |
161 | $kv = explode('=', $kv, 2); | |
162 | $kv[0] = $this->safe_decode($kv[0]); | |
163 | $kv[1] = $this->safe_decode($kv[1]); | |
164 | $decoded[$kv[0]] = $kv[1]; | |
165 | } | |
166 | return $decoded; | |
167 | } | |
168 | ||
169 | /** | |
170 | * Prepares the HTTP method for use in the base string by converting it to | |
171 | * uppercase. | |
172 | * | |
173 | * @param string $method an HTTP method such as GET or POST | |
174 | * @return void value is stored to a class variable | |
175 | * @author themattharris | |
176 | */ | |
177 | private function prepare_method($method) { | |
178 | $this->method = strtoupper($method); | |
179 | } | |
180 | ||
181 | /** | |
182 | * Prepares the URL for use in the base string by ripping it apart and | |
183 | * reconstructing it. | |
184 | * | |
185 | * @param string $url the request URL | |
186 | * @return void value is stored to a class variable | |
187 | * @author themattharris | |
188 | */ | |
189 | private function prepare_url($url) { | |
190 | $parts = parse_url($url); | |
191 | ||
192 | $port = @$parts['port']; | |
193 | $scheme = $parts['scheme']; | |
194 | $host = $parts['host']; | |
195 | $path = @$parts['path']; | |
196 | ||
197 | $port or $port = ($scheme == 'https') ? '443' : '80'; | |
198 | ||
199 | if (($scheme == 'https' && $port != '443') | |
200 | || ($scheme == 'http' && $port != '80')) { | |
201 | $host = "$host:$port"; | |
202 | } | |
203 | $this->url = "$scheme://$host$path"; | |
204 | } | |
205 | ||
206 | /** | |
207 | * Prepares all parameters for the base string and request. | |
208 | * Multipart parameters are ignored as they are not defined in the specification, | |
209 | * all other types of parameter are encoded for compatibility with OAuth. | |
210 | * | |
211 | * @param array $params the parameters for the request | |
212 | * @return void prepared values are stored in class variables | |
213 | */ | |
214 | private function prepare_params($params) { | |
215 | // do not encode multipart parameters, leave them alone | |
216 | if ($this->config['multipart']) { | |
217 | $this->request_params = $params; | |
218 | $params = array(); | |
219 | } | |
220 | ||
221 | // signing parameters are request parameters + OAuth default parameters | |
222 | $this->signing_params = array_merge($this->get_defaults(), (array)$params); | |
223 | ||
224 | // Remove oauth_signature if present | |
225 | // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") | |
226 | if (isset($this->signing_params['oauth_signature'])) { | |
227 | unset($this->signing_params['oauth_signature']); | |
228 | } | |
229 | ||
230 | // Parameters are sorted by name, using lexicographical byte value ordering. | |
231 | // Ref: Spec: 9.1.1 (1) | |
232 | uksort($this->signing_params, 'strcmp'); | |
233 | ||
234 | // encode. Also sort the signed parameters from the POST parameters | |
235 | foreach ($this->signing_params as $k => $v) { | |
236 | $k = $this->safe_encode($k); | |
237 | $v = $this->safe_encode($v); | |
238 | $_signing_params[$k] = $v; | |
239 | $kv[] = "{$k}={$v}"; | |
240 | } | |
241 | ||
242 | // auth params = the default oauth params which are present in our collection of signing params | |
243 | $this->auth_params = array_intersect_key($this->get_defaults(), $_signing_params); | |
244 | if (isset($_signing_params['oauth_callback'])) { | |
245 | $this->auth_params['oauth_callback'] = $_signing_params['oauth_callback']; | |
246 | unset($_signing_params['oauth_callback']); | |
247 | } | |
248 | ||
249 | // request_params is already set if we're doing multipart, if not we need to set them now | |
250 | if ( ! $this->config['multipart']) | |
251 | $this->request_params = array_diff_key($_signing_params, $this->get_defaults()); | |
252 | ||
253 | // create the parameter part of the base string | |
254 | $this->signing_params = implode('&', $kv); | |
255 | } | |
256 | ||
257 | /** | |
258 | * Prepares the OAuth signing key | |
259 | * | |
260 | * @return void prepared signing key is stored in a class variables | |
261 | */ | |
262 | private function prepare_signing_key() { | |
263 | $this->signing_key = $this->safe_encode($this->config['consumer_secret']) . '&' . $this->safe_encode($this->config['user_secret']); | |
264 | } | |
265 | ||
266 | /** | |
267 | * Prepare the base string. | |
268 | * Ref: Spec: 9.1.3 ("Concatenate Request Elements") | |
269 | * | |
270 | * @return void prepared base string is stored in a class variables | |
271 | */ | |
272 | private function prepare_base_string() { | |
273 | $base = array( | |
274 | $this->method, | |
275 | $this->url, | |
276 | $this->signing_params | |
277 | ); | |
278 | $this->base_string = implode('&', $this->safe_encode($base)); | |
279 | } | |
280 | ||
281 | /** | |
282 | * Prepares the Authorization header | |
283 | * | |
284 | * @return void prepared authorization header is stored in a class variables | |
285 | */ | |
286 | private function prepare_auth_header() { | |
287 | $this->headers = array(); | |
288 | uksort($this->auth_params, 'strcmp'); | |
289 | foreach ($this->auth_params as $k => $v) { | |
290 | $kv[] = "{$k}=\"{$v}\""; | |
291 | } | |
292 | $this->auth_header = 'OAuth ' . implode(', ', $kv); | |
293 | $this->headers[] = 'Authorization: ' . $this->auth_header; | |
294 | } | |
295 | ||
296 | /** | |
297 | * Signs the request and adds the OAuth signature. This runs all the request | |
298 | * parameter preparation methods. | |
299 | * | |
300 | * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc | |
301 | * @param string $url the request URL without query string parameters | |
302 | * @param array $params the request parameters as an array of key=value pairs | |
303 | * @param string $useauth whether to use authentication when making the request. | |
304 | */ | |
305 | private function sign($method, $url, $params, $useauth) { | |
306 | $this->prepare_method($method); | |
307 | $this->prepare_url($url); | |
308 | $this->prepare_params($params); | |
309 | ||
310 | // we don't sign anything is we're not using auth | |
311 | if ($useauth) { | |
312 | $this->prepare_base_string(); | |
313 | $this->prepare_signing_key(); | |
314 | ||
315 | $this->auth_params['oauth_signature'] = $this->safe_encode( | |
316 | base64_encode( | |
317 | hash_hmac( | |
318 | 'sha1', $this->base_string, $this->signing_key, true | |
319 | ))); | |
320 | ||
321 | $this->prepare_auth_header(); | |
322 | } | |
323 | } | |
324 | ||
325 | /** | |
326 | * Make an HTTP request using this library. This method doesn't return anything. | |
327 | * Instead the response should be inspected directly. | |
328 | * | |
329 | * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc | |
330 | * @param string $url the request URL without query string parameters | |
331 | * @param array $params the request parameters as an array of key=value pairs | |
332 | * @param string $useauth whether to use authentication when making the request. Default true. | |
333 | * @param string $multipart whether this request contains multipart data. Default false | |
334 | */ | |
335 | function request($method, $url, $params=array(), $useauth=true, $multipart=false) { | |
336 | $this->config['multipart'] = $multipart; | |
337 | ||
338 | $this->create_nonce(); | |
339 | $this->create_timestamp(); | |
340 | ||
341 | $this->sign($method, $url, $params, $useauth); | |
342 | return $this->curlit($multipart); | |
343 | } | |
344 | ||
345 | /** | |
346 | * Make an HTTP request using this library. This method is different to 'request' | |
347 | * because on a 401 error it will retry the request. | |
348 | * | |
349 | * When a 401 error is returned it is possible the timestamp of the client is | |
350 | * too different to that of the API server. In this situation it is recommended | |
351 | * the request is retried with the OAuth timestamp set to the same as the API | |
352 | * server. This method will automatically try that technique. | |
353 | * | |
354 | * This method doesn't return anything. Instead the response should be | |
355 | * inspected directly. | |
356 | * | |
357 | * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc | |
358 | * @param string $url the request URL without query string parameters | |
359 | * @param array $params the request parameters as an array of key=value pairs | |
360 | * @param string $useauth whether to use authentication when making the request. Default true. | |
361 | * @param string $multipart whether this request contains multipart data. Default false | |
362 | */ | |
363 | function auto_fix_time_request($method, $url, $params=array(), $useauth=true, $multipart=false) { | |
364 | $this->request($method, $url, $params, $useauth, $multipart); | |
365 | ||
366 | // if we're not doing auth the timestamp isn't important | |
367 | if ( ! $useauth) | |
368 | return; | |
369 | ||
370 | // some error that isn't a 401 | |
371 | if ($this->response['code'] != 401) | |
372 | return; | |
373 | ||
374 | // some error that is a 401 but isn't because the OAuth token and signature are incorrect | |
375 | // TODO: this check is horrid but helps avoid requesting twice when the username and password are wrong | |
376 | if (stripos($this->response['response'], 'password') !== false) | |
377 | return; | |
378 | ||
379 | // force the timestamp to be the same as the Twitter servers, and re-request | |
380 | $this->auto_fixed_time = true; | |
381 | $this->config['force_timestamp'] = true; | |
382 | $this->config['timestamp'] = strtotime($this->response['headers']['date']); | |
383 | $this->request($method, $url, $params, $useauth, $multipart); | |
384 | } | |
385 | ||
386 | /** | |
387 | * Make a long poll HTTP request using this library. This method is | |
388 | * different to the other request methods as it isn't supposed to disconnect | |
389 | * | |
390 | * Using this method expects a callback which will receive the streaming | |
391 | * responses. | |
392 | * | |
393 | * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc | |
394 | * @param string $url the request URL without query string parameters | |
395 | * @param array $params the request parameters as an array of key=value pairs | |
396 | * @param string $callback the callback function to stream the buffer to. | |
397 | */ | |
398 | function streaming_request($method, $url, $params=array(), $callback='') { | |
399 | if ( ! empty($callback) ) { | |
400 | if ( ! function_exists($callback) ) { | |
401 | return false; | |
402 | } | |
403 | $this->config['streaming_callback'] = $callback; | |
404 | } | |
405 | $this->metrics['start'] = time(); | |
406 | $this->metrics['interval_start'] = $this->metrics['start']; | |
407 | $this->metrics['tweets'] = 0; | |
408 | $this->metrics['last_tweets'] = 0; | |
409 | $this->metrics['bytes'] = 0; | |
410 | $this->metrics['last_bytes'] = 0; | |
411 | $this->config['is_streaming'] = true; | |
412 | $this->request($method, $url, $params); | |
413 | } | |
414 | ||
415 | /** | |
416 | * Handles the updating of the current Streaming API metrics. | |
417 | */ | |
418 | function update_metrics() { | |
419 | $now = time(); | |
420 | if (($this->metrics['interval_start'] + $this->config['streaming_metrics_interval']) > $now) | |
421 | return false; | |
422 | ||
423 | $this->metrics['tps'] = round( ($this->metrics['tweets'] - $this->metrics['last_tweets']) / $this->config['streaming_metrics_interval'], 2); | |
424 | $this->metrics['bps'] = round( ($this->metrics['bytes'] - $this->metrics['last_bytes']) / $this->config['streaming_metrics_interval'], 2); | |
425 | ||
426 | $this->metrics['last_bytes'] = $this->metrics['bytes']; | |
427 | $this->metrics['last_tweets'] = $this->metrics['tweets']; | |
428 | $this->metrics['interval_start'] = $now; | |
429 | return $this->metrics; | |
430 | } | |
431 | ||
432 | /** | |
433 | * Utility function to create the request URL in the requested format | |
434 | * | |
435 | * @param string $request the API method without extension | |
436 | * @param string $format the format of the response. Default json. Set to an empty string to exclude the format | |
437 | * @return string the concatenation of the host, API version, API method and format | |
438 | */ | |
439 | function url($request, $format='json') { | |
440 | $format = strlen($format) > 0 ? ".$format" : ''; | |
441 | $proto = $this->config['use_ssl'] ? 'https:/' : 'http:/'; | |
442 | ||
443 | // backwards compatibility with v0.1 | |
444 | if (isset($this->config['v'])) | |
445 | $this->config['host'] = $this->config['host'] . '/' . $this->config['v']; | |
446 | ||
447 | return implode('/', array( | |
448 | $proto, | |
449 | $this->config['host'], | |
450 | $request . $format | |
451 | )); | |
452 | } | |
453 | ||
454 | /** | |
455 | * Utility function to parse the returned curl headers and store them in the | |
456 | * class array variable. | |
457 | * | |
458 | * @param object $ch curl handle | |
459 | * @param string $header the response headers | |
460 | * @return the string length of the header | |
461 | */ | |
462 | private function curlHeader($ch, $header) { | |
463 | $i = strpos($header, ':'); | |
464 | if ( ! empty($i) ) { | |
465 | $key = str_replace('-', '_', strtolower(substr($header, 0, $i))); | |
466 | $value = trim(substr($header, $i + 2)); | |
467 | $this->response['headers'][$key] = $value; | |
468 | } | |
469 | return strlen($header); | |
470 | } | |
471 | ||
472 | /** | |
473 | * Utility function to parse the returned curl buffer and store them until | |
474 | * an EOL is found. The buffer for curl is an undefined size so we need | |
475 | * to collect the content until an EOL is found. | |
476 | * | |
477 | * This function calls the previously defined streaming callback method. | |
478 | * | |
479 | * @param object $ch curl handle | |
480 | * @param string $data the current curl buffer | |
481 | */ | |
482 | private function curlWrite($ch, $data) { | |
483 | $l = strlen($data); | |
484 | if (strpos($data, $this->config['streaming_eol']) === false) { | |
485 | $this->buffer .= $data; | |
486 | return $l; | |
487 | } | |
488 | ||
489 | $buffered = explode($this->config['streaming_eol'], $data); | |
490 | $content = $this->buffer . $buffered[0]; | |
491 | ||
492 | $this->metrics['tweets']++; | |
493 | $this->metrics['bytes'] += strlen($content); | |
494 | ||
495 | if ( ! function_exists($this->config['streaming_callback'])) | |
496 | return 0; | |
497 | ||
498 | $metrics = $this->update_metrics(); | |
499 | $stop = call_user_func( | |
500 | $this->config['streaming_callback'], | |
501 | $content, | |
502 | strlen($content), | |
503 | $metrics | |
504 | ); | |
505 | $this->buffer = $buffered[1]; | |
506 | if ($stop) | |
507 | return 0; | |
508 | ||
509 | return $l; | |
510 | } | |
511 | ||
512 | /** | |
513 | * Makes a curl request. Takes no parameters as all should have been prepared | |
514 | * by the request method | |
515 | * | |
516 | * @return void response data is stored in the class variable 'response' | |
517 | */ | |
518 | private function curlit() { | |
519 | // method handling | |
520 | switch ($this->method) { | |
521 | case 'POST': | |
522 | break; | |
523 | default: | |
524 | // GET, DELETE request so convert the parameters to a querystring | |
525 | if ( ! empty($this->request_params)) { | |
526 | foreach ($this->request_params as $k => $v) { | |
527 | // Multipart params haven't been encoded yet. | |
528 | // Not sure why you would do a multipart GET but anyway, here's the support for it | |
529 | if ($this->config['multipart']) { | |
530 | $params[] = $this->safe_encode($k) . '=' . $this->safe_encode($v); | |
531 | } else { | |
532 | $params[] = $k . '=' . $v; | |
533 | } | |
534 | } | |
535 | $qs = implode('&', $params); | |
536 | $this->url = strlen($qs) > 0 ? $this->url . '?' . $qs : $this->url; | |
537 | $this->request_params = array(); | |
538 | } | |
539 | break; | |
540 | } | |
541 | ||
542 | if (@$this->config['prevent_request']) | |
543 | return; | |
544 | ||
545 | // configure curl | |
546 | $c = curl_init(); | |
547 | curl_setopt($c, CURLOPT_USERAGENT, "themattharris' HTTP Client"); | |
548 | curl_setopt($c, CURLOPT_CONNECTTIMEOUT, $this->config['curl_connecttimeout']); | |
549 | curl_setopt($c, CURLOPT_TIMEOUT, $this->config['curl_timeout']); | |
550 | curl_setopt($c, CURLOPT_RETURNTRANSFER, TRUE); | |
551 | curl_setopt($c, CURLOPT_SSL_VERIFYPEER, $this->config['curl_ssl_verifypeer']); | |
552 | curl_setopt($c, CURLOPT_FOLLOWLOCATION, $this->config['curl_followlocation']); | |
553 | curl_setopt($c, CURLOPT_PROXY, $this->config['curl_proxy']); | |
554 | curl_setopt($c, CURLOPT_URL, $this->url); | |
555 | // process the headers | |
556 | curl_setopt($c, CURLOPT_HEADERFUNCTION, array($this, 'curlHeader')); | |
557 | curl_setopt($c, CURLOPT_HEADER, FALSE); | |
558 | curl_setopt($c, CURLINFO_HEADER_OUT, true); | |
559 | ||
560 | if ($this->config['curl_proxyuserpwd'] !== false) | |
561 | curl_setopt($c, CURLOPT_PROXYUSERPWD, $this->config['curl_proxyuserpwd']); | |
562 | ||
563 | if ($this->config['is_streaming']) { | |
564 | // process the body | |
565 | $this->response['content-length'] = 0; | |
566 | curl_setopt($c, CURLOPT_TIMEOUT, 0); | |
567 | curl_setopt($c, CURLOPT_WRITEFUNCTION, array($this, 'curlWrite')); | |
568 | } | |
569 | ||
570 | switch ($this->method) { | |
571 | case 'GET': | |
572 | break; | |
573 | case 'POST': | |
574 | curl_setopt($c, CURLOPT_POST, TRUE); | |
575 | break; | |
576 | default: | |
577 | curl_setopt($c, CURLOPT_CUSTOMREQUEST, $this->method); | |
578 | } | |
579 | ||
580 | if ( ! empty($this->request_params) ) { | |
581 | // if not doing multipart we need to implode the parameters | |
582 | if ( ! $this->config['multipart'] ) { | |
583 | foreach ($this->request_params as $k => $v) { | |
584 | $ps[] = "{$k}={$v}"; | |
585 | } | |
586 | $this->request_params = implode('&', $ps); | |
587 | } | |
588 | curl_setopt($c, CURLOPT_POSTFIELDS, $this->request_params); | |
589 | } else { | |
590 | // CURL will set length to -1 when there is no data, which breaks Twitter | |
591 | $this->headers[] = 'Content-Type:'; | |
592 | $this->headers[] = 'Content-Length:'; | |
593 | } | |
594 | ||
595 | // CURL defaults to setting this to Expect: 100-Continue which Twitter rejects | |
596 | $this->headers[] = 'Expect:'; | |
597 | ||
598 | if ( ! empty($this->headers)) | |
599 | curl_setopt($c, CURLOPT_HTTPHEADER, $this->headers); | |
600 | ||
601 | // do it! | |
602 | $response = curl_exec($c); | |
603 | $code = curl_getinfo($c, CURLINFO_HTTP_CODE); | |
604 | $info = curl_getinfo($c); | |
605 | curl_close($c); | |
606 | ||
607 | // store the response | |
608 | $this->response['code'] = $code; | |
609 | $this->response['response'] = $response; | |
610 | $this->response['info'] = $info; | |
611 | return $code; | |
612 | } | |
613 | ||
614 | /** | |
615 | * Debug function for printing the content of an object | |
616 | * | |
617 | * @param mixes $obj | |
618 | */ | |
619 | function pr($obj) { | |
620 | $cli = (PHP_SAPI == 'cli' && empty($_SERVER['REMOTE_ADDR'])); | |
621 | if (!$cli) | |
622 | echo '<pre style="word-wrap: break-word">'; | |
623 | if ( is_object($obj) ) | |
624 | print_r($obj); | |
625 | elseif ( is_array($obj) ) | |
626 | print_r($obj); | |
627 | else | |
628 | echo $obj; | |
629 | if (!$cli) | |
630 | echo '</pre>'; | |
631 | } | |
632 | ||
633 | /** | |
634 | * Returns the current URL. This is instead of PHP_SELF which is unsafe | |
635 | * | |
636 | * @param bool $dropqs whether to drop the querystring or not. Default true | |
637 | * @return string the current URL | |
638 | */ | |
639 | function php_self($dropqs=true) { | |
640 | $url = sprintf('%s://%s%s', | |
641 | empty($_SERVER['HTTPS']) ? 'http' : 'https', | |
642 | $_SERVER['SERVER_NAME'], | |
643 | $_SERVER['REQUEST_URI'] | |
644 | ); | |
645 | ||
646 | $parts = parse_url($url); | |
647 | ||
648 | $port = $_SERVER['SERVER_PORT']; | |
649 | $scheme = $parts['scheme']; | |
650 | $host = $parts['host']; | |
651 | $path = @$parts['path']; | |
652 | $qs = @$parts['query']; | |
653 | ||
654 | $port or $port = ($scheme == 'https') ? '443' : '80'; | |
655 | ||
656 | if (($scheme == 'https' && $port != '443') | |
657 | || ($scheme == 'http' && $port != '80')) { | |
658 | $host = "$host:$port"; | |
659 | } | |
660 | $url = "$scheme://$host$path"; | |
661 | if ( ! $dropqs) | |
662 | return "{$url}?{$qs}"; | |
663 | else | |
664 | return $url; | |
665 | } | |
666 | ||
667 | /** | |
668 | * Entifies the tweet using the given entities element | |
669 | * | |
670 | * @param array $tweet the json converted to normalised array | |
671 | * @return the tweet text with entities replaced with hyperlinks | |
672 | */ | |
673 | function entify($tweet) { | |
674 | $keys = array(); | |
675 | $replacements = array(); | |
676 | $is_retweet = false; | |
677 | ||
678 | if (isset($tweet['retweeted_status'])) { | |
679 | $tweet = $tweet['retweeted_status']; | |
680 | $is_retweet = true; | |
681 | } | |
682 | ||
683 | if (!isset($tweet['entities'])) { | |
684 | return $tweet['text']; | |
685 | } | |
686 | ||
687 | // prepare the entities | |
688 | foreach ($tweet['entities'] as $type => $things) { | |
689 | foreach ($things as $entity => $value) { | |
690 | $tweet_link = "<a href=\"http://twitter.com/{$value['screen_name']}/statuses/{$tweet['id']}\">{$tweet['created_at']}</a>"; | |
691 | ||
692 | switch ($type) { | |
693 | case 'hashtags': | |
694 | $href = "<a href=\"http://search.twitter.com/search?q=%23{$value['text']}\">#{$value['text']}</a>"; | |
695 | break; | |
696 | case 'user_mentions': | |
697 | $href = "@<a href=\"http://twitter.com/{$value['screen_name']}\" title=\"{$value['name']}\">{$value['screen_name']}</a>"; | |
698 | break; | |
699 | case 'urls': | |
700 | $url = empty($value['expanded_url']) ? $value['url'] : $value['expanded_url']; | |
701 | $display = isset($value['display_url']) ? $value['display_url'] : str_replace('http://', '', $url); | |
702 | // Not all pages are served in UTF-8 so you may need to do this ... | |
703 | $display = urldecode(str_replace('%E2%80%A6', '…', urlencode($display))); | |
704 | $href = "<a href=\"{$value['url']}\">{$display}</a>"; | |
705 | break; | |
706 | } | |
707 | $keys[$value['indices']['0']] = substr( | |
708 | $tweet['text'], | |
709 | $value['indices']['0'], | |
710 | $value['indices']['1'] - $value['indices']['0'] | |
711 | ); | |
712 | $replacements[$value['indices']['0']] = $href; | |
713 | } | |
714 | } | |
715 | ||
716 | ksort($replacements); | |
717 | $replacements = array_reverse($replacements, true); | |
718 | $entified_tweet = $tweet['text']; | |
719 | foreach ($replacements as $k => $v) { | |
720 | $entified_tweet = substr_replace($entified_tweet, $v, $k, strlen($keys[$k])); | |
721 | } | |
722 | return $entified_tweet; | |
723 | } | |
724 | } | |
725 | ||
726 | ?> |