summaryrefslogtreecommitdiff
path: root/lib/tmhoauth/tmhOAuth.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tmhoauth/tmhOAuth.php')
-rw-r--r--lib/tmhoauth/tmhOAuth.php726
1 files changed, 726 insertions, 0 deletions
diff --git a/lib/tmhoauth/tmhOAuth.php b/lib/tmhoauth/tmhOAuth.php
new file mode 100644
index 000000000..643ad09e7
--- /dev/null
+++ b/lib/tmhoauth/tmhOAuth.php
@@ -0,0 +1,726 @@
+<?php
+/**
+ * tmhOAuth
+ *
+ * An OAuth 1.0A library written in PHP.
+ * The library supports file uploading using multipart/form as well as general
+ * REST requests. OAuth authentication is sent using the an Authorization Header.
+ *
+ * @author themattharris
+ * @version 0.4
+ *
+ * 03 March 2011
+ */
+class tmhOAuth {
+ /**
+ * Creates a new tmhOAuth object
+ *
+ * @param string $config, the configuration to use for this request
+ */
+ function __construct($config) {
+ $this->params = array();
+ $this->auto_fixed_time = false;
+
+ // default configuration options
+ $this->config = array_merge(
+ array(
+ 'consumer_key' => '',
+ 'consumer_secret' => '',
+ 'user_token' => '',
+ 'user_secret' => '',
+ 'use_ssl' => true,
+ 'host' => 'api.twitter.com',
+ 'debug' => false,
+ 'force_nonce' => false,
+ 'nonce' => false, // used for checking signatures. leave as false for auto
+ 'force_timestamp' => false,
+ 'timestamp' => false, // used for checking signatures. leave as false for auto
+ 'oauth_version' => '1.0',
+
+ // you probably don't want to change any of these curl values
+ 'curl_connecttimeout' => 30,
+ 'curl_timeout' => 10,
+ // for security you may want to set this to TRUE. If you do you need
+ // to install the servers certificate in your local certificate store.
+ 'curl_ssl_verifypeer' => false,
+ 'curl_followlocation' => false, // whether to follow redirects or not
+ // support for proxy servers
+ 'curl_proxy' => false, // really you don't want to use this if you are using streaming
+ 'curl_proxyuserpwd' => false, // format username:password for proxy, if required
+
+ // streaming API
+ 'is_streaming' => false,
+ 'streaming_eol' => "\r\n",
+ 'streaming_metrics_interval' => 60,
+ ),
+ $config
+ );
+ }
+
+ /**
+ * Generates a random OAuth nonce.
+ * If 'force_nonce' is true a nonce is not generated and the value in the configuration will be retained.
+ *
+ * @param string $length how many characters the nonce should be before MD5 hashing. default 12
+ * @param string $include_time whether to include time at the beginning of the nonce. default true
+ * @return void
+ */
+ private function create_nonce($length=12, $include_time=true) {
+ if ($this->config['force_nonce'] == false) {
+ $sequence = array_merge(range(0,9), range('A','Z'), range('a','z'));
+ $length = $length > count($sequence) ? count($sequence) : $length;
+ shuffle($sequence);
+ $this->config['nonce'] = md5(substr(microtime() . implode($sequence), 0, $length));
+ }
+ }
+
+ /**
+ * Generates a timestamp.
+ * If 'force_timestamp' is true a nonce is not generated and the value in the configuration will be retained.
+ *
+ * @return void
+ */
+ private function create_timestamp() {
+ $this->config['timestamp'] = ($this->config['force_timestamp'] == false ? time() : $this->config['timestamp']);
+ }
+
+ /**
+ * Encodes the string or array passed in a way compatible with OAuth.
+ * If an array is passed each array value will will be encoded.
+ *
+ * @param mixed $data the scalar or array to encode
+ * @return $data encoded in a way compatible with OAuth
+ */
+ private function safe_encode($data) {
+ if (is_array($data)) {
+ return array_map(array($this, 'safe_encode'), $data);
+ } else if (is_scalar($data)) {
+ return str_ireplace(
+ array('+', '%7E'),
+ array(' ', '~'),
+ rawurlencode($data)
+ );
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Decodes the string or array from it's URL encoded form
+ * If an array is passed each array value will will be decoded.
+ *
+ * @param mixed $data the scalar or array to decode
+ * @return $data decoded from the URL encoded form
+ */
+ private function safe_decode($data) {
+ if (is_array($data)) {
+ return array_map(array($this, 'safe_decode'), $data);
+ } else if (is_scalar($data)) {
+ return rawurldecode($data);
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Returns an array of the standard OAuth parameters.
+ *
+ * @return array all required OAuth parameters, safely encoded
+ */
+ private function get_defaults() {
+ $defaults = array(
+ 'oauth_version' => $this->config['oauth_version'],
+ 'oauth_nonce' => $this->config['nonce'],
+ 'oauth_timestamp' => $this->config['timestamp'],
+ 'oauth_consumer_key' => $this->config['consumer_key'],
+ 'oauth_signature_method' => 'HMAC-SHA1',
+ );
+
+ // include the user token if it exists
+ if ( $this->config['user_token'] )
+ $defaults['oauth_token'] = $this->config['user_token'];
+
+ // safely encode
+ foreach ($defaults as $k => $v) {
+ $_defaults[$this->safe_encode($k)] = $this->safe_encode($v);
+ }
+
+ return $_defaults;
+ }
+
+ /**
+ * Extracts and decodes OAuth parameters from the passed string
+ *
+ * @param string $body the response body from an OAuth flow method
+ * @return array the response body safely decoded to an array of key => values
+ */
+ function extract_params($body) {
+ $kvs = explode('&', $body);
+ $decoded = array();
+ foreach ($kvs as $kv) {
+ $kv = explode('=', $kv, 2);
+ $kv[0] = $this->safe_decode($kv[0]);
+ $kv[1] = $this->safe_decode($kv[1]);
+ $decoded[$kv[0]] = $kv[1];
+ }
+ return $decoded;
+ }
+
+ /**
+ * Prepares the HTTP method for use in the base string by converting it to
+ * uppercase.
+ *
+ * @param string $method an HTTP method such as GET or POST
+ * @return void value is stored to a class variable
+ * @author themattharris
+ */
+ private function prepare_method($method) {
+ $this->method = strtoupper($method);
+ }
+
+ /**
+ * Prepares the URL for use in the base string by ripping it apart and
+ * reconstructing it.
+ *
+ * @param string $url the request URL
+ * @return void value is stored to a class variable
+ * @author themattharris
+ */
+ private function prepare_url($url) {
+ $parts = parse_url($url);
+
+ $port = @$parts['port'];
+ $scheme = $parts['scheme'];
+ $host = $parts['host'];
+ $path = @$parts['path'];
+
+ $port or $port = ($scheme == 'https') ? '443' : '80';
+
+ if (($scheme == 'https' && $port != '443')
+ || ($scheme == 'http' && $port != '80')) {
+ $host = "$host:$port";
+ }
+ $this->url = "$scheme://$host$path";
+ }
+
+ /**
+ * Prepares all parameters for the base string and request.
+ * Multipart parameters are ignored as they are not defined in the specification,
+ * all other types of parameter are encoded for compatibility with OAuth.
+ *
+ * @param array $params the parameters for the request
+ * @return void prepared values are stored in class variables
+ */
+ private function prepare_params($params) {
+ // do not encode multipart parameters, leave them alone
+ if ($this->config['multipart']) {
+ $this->request_params = $params;
+ $params = array();
+ }
+
+ // signing parameters are request parameters + OAuth default parameters
+ $this->signing_params = array_merge($this->get_defaults(), (array)$params);
+
+ // Remove oauth_signature if present
+ // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
+ if (isset($this->signing_params['oauth_signature'])) {
+ unset($this->signing_params['oauth_signature']);
+ }
+
+ // Parameters are sorted by name, using lexicographical byte value ordering.
+ // Ref: Spec: 9.1.1 (1)
+ uksort($this->signing_params, 'strcmp');
+
+ // encode. Also sort the signed parameters from the POST parameters
+ foreach ($this->signing_params as $k => $v) {
+ $k = $this->safe_encode($k);
+ $v = $this->safe_encode($v);
+ $_signing_params[$k] = $v;
+ $kv[] = "{$k}={$v}";
+ }
+
+ // auth params = the default oauth params which are present in our collection of signing params
+ $this->auth_params = array_intersect_key($this->get_defaults(), $_signing_params);
+ if (isset($_signing_params['oauth_callback'])) {
+ $this->auth_params['oauth_callback'] = $_signing_params['oauth_callback'];
+ unset($_signing_params['oauth_callback']);
+ }
+
+ // request_params is already set if we're doing multipart, if not we need to set them now
+ if ( ! $this->config['multipart'])
+ $this->request_params = array_diff_key($_signing_params, $this->get_defaults());
+
+ // create the parameter part of the base string
+ $this->signing_params = implode('&', $kv);
+ }
+
+ /**
+ * Prepares the OAuth signing key
+ *
+ * @return void prepared signing key is stored in a class variables
+ */
+ private function prepare_signing_key() {
+ $this->signing_key = $this->safe_encode($this->config['consumer_secret']) . '&' . $this->safe_encode($this->config['user_secret']);
+ }
+
+ /**
+ * Prepare the base string.
+ * Ref: Spec: 9.1.3 ("Concatenate Request Elements")
+ *
+ * @return void prepared base string is stored in a class variables
+ */
+ private function prepare_base_string() {
+ $base = array(
+ $this->method,
+ $this->url,
+ $this->signing_params
+ );
+ $this->base_string = implode('&', $this->safe_encode($base));
+ }
+
+ /**
+ * Prepares the Authorization header
+ *
+ * @return void prepared authorization header is stored in a class variables
+ */
+ private function prepare_auth_header() {
+ $this->headers = array();
+ uksort($this->auth_params, 'strcmp');
+ foreach ($this->auth_params as $k => $v) {
+ $kv[] = "{$k}=\"{$v}\"";
+ }
+ $this->auth_header = 'OAuth ' . implode(', ', $kv);
+ $this->headers[] = 'Authorization: ' . $this->auth_header;
+ }
+
+ /**
+ * Signs the request and adds the OAuth signature. This runs all the request
+ * parameter preparation methods.
+ *
+ * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
+ * @param string $url the request URL without query string parameters
+ * @param array $params the request parameters as an array of key=value pairs
+ * @param string $useauth whether to use authentication when making the request.
+ */
+ private function sign($method, $url, $params, $useauth) {
+ $this->prepare_method($method);
+ $this->prepare_url($url);
+ $this->prepare_params($params);
+
+ // we don't sign anything is we're not using auth
+ if ($useauth) {
+ $this->prepare_base_string();
+ $this->prepare_signing_key();
+
+ $this->auth_params['oauth_signature'] = $this->safe_encode(
+ base64_encode(
+ hash_hmac(
+ 'sha1', $this->base_string, $this->signing_key, true
+ )));
+
+ $this->prepare_auth_header();
+ }
+ }
+
+ /**
+ * Make an HTTP request using this library. This method doesn't return anything.
+ * Instead the response should be inspected directly.
+ *
+ * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
+ * @param string $url the request URL without query string parameters
+ * @param array $params the request parameters as an array of key=value pairs
+ * @param string $useauth whether to use authentication when making the request. Default true.
+ * @param string $multipart whether this request contains multipart data. Default false
+ */
+ function request($method, $url, $params=array(), $useauth=true, $multipart=false) {
+ $this->config['multipart'] = $multipart;
+
+ $this->create_nonce();
+ $this->create_timestamp();
+
+ $this->sign($method, $url, $params, $useauth);
+ return $this->curlit($multipart);
+ }
+
+ /**
+ * Make an HTTP request using this library. This method is different to 'request'
+ * because on a 401 error it will retry the request.
+ *
+ * When a 401 error is returned it is possible the timestamp of the client is
+ * too different to that of the API server. In this situation it is recommended
+ * the request is retried with the OAuth timestamp set to the same as the API
+ * server. This method will automatically try that technique.
+ *
+ * This method doesn't return anything. Instead the response should be
+ * inspected directly.
+ *
+ * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
+ * @param string $url the request URL without query string parameters
+ * @param array $params the request parameters as an array of key=value pairs
+ * @param string $useauth whether to use authentication when making the request. Default true.
+ * @param string $multipart whether this request contains multipart data. Default false
+ */
+ function auto_fix_time_request($method, $url, $params=array(), $useauth=true, $multipart=false) {
+ $this->request($method, $url, $params, $useauth, $multipart);
+
+ // if we're not doing auth the timestamp isn't important
+ if ( ! $useauth)
+ return;
+
+ // some error that isn't a 401
+ if ($this->response['code'] != 401)
+ return;
+
+ // some error that is a 401 but isn't because the OAuth token and signature are incorrect
+ // TODO: this check is horrid but helps avoid requesting twice when the username and password are wrong
+ if (stripos($this->response['response'], 'password') !== false)
+ return;
+
+ // force the timestamp to be the same as the Twitter servers, and re-request
+ $this->auto_fixed_time = true;
+ $this->config['force_timestamp'] = true;
+ $this->config['timestamp'] = strtotime($this->response['headers']['date']);
+ $this->request($method, $url, $params, $useauth, $multipart);
+ }
+
+ /**
+ * Make a long poll HTTP request using this library. This method is
+ * different to the other request methods as it isn't supposed to disconnect
+ *
+ * Using this method expects a callback which will receive the streaming
+ * responses.
+ *
+ * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
+ * @param string $url the request URL without query string parameters
+ * @param array $params the request parameters as an array of key=value pairs
+ * @param string $callback the callback function to stream the buffer to.
+ */
+ function streaming_request($method, $url, $params=array(), $callback='') {
+ if ( ! empty($callback) ) {
+ if ( ! function_exists($callback) ) {
+ return false;
+ }
+ $this->config['streaming_callback'] = $callback;
+ }
+ $this->metrics['start'] = time();
+ $this->metrics['interval_start'] = $this->metrics['start'];
+ $this->metrics['tweets'] = 0;
+ $this->metrics['last_tweets'] = 0;
+ $this->metrics['bytes'] = 0;
+ $this->metrics['last_bytes'] = 0;
+ $this->config['is_streaming'] = true;
+ $this->request($method, $url, $params);
+ }
+
+ /**
+ * Handles the updating of the current Streaming API metrics.
+ */
+ function update_metrics() {
+ $now = time();
+ if (($this->metrics['interval_start'] + $this->config['streaming_metrics_interval']) > $now)
+ return false;
+
+ $this->metrics['tps'] = round( ($this->metrics['tweets'] - $this->metrics['last_tweets']) / $this->config['streaming_metrics_interval'], 2);
+ $this->metrics['bps'] = round( ($this->metrics['bytes'] - $this->metrics['last_bytes']) / $this->config['streaming_metrics_interval'], 2);
+
+ $this->metrics['last_bytes'] = $this->metrics['bytes'];
+ $this->metrics['last_tweets'] = $this->metrics['tweets'];
+ $this->metrics['interval_start'] = $now;
+ return $this->metrics;
+ }
+
+ /**
+ * Utility function to create the request URL in the requested format
+ *
+ * @param string $request the API method without extension
+ * @param string $format the format of the response. Default json. Set to an empty string to exclude the format
+ * @return string the concatenation of the host, API version, API method and format
+ */
+ function url($request, $format='json') {
+ $format = strlen($format) > 0 ? ".$format" : '';
+ $proto = $this->config['use_ssl'] ? 'https:/' : 'http:/';
+
+ // backwards compatibility with v0.1
+ if (isset($this->config['v']))
+ $this->config['host'] = $this->config['host'] . '/' . $this->config['v'];
+
+ return implode('/', array(
+ $proto,
+ $this->config['host'],
+ $request . $format
+ ));
+ }
+
+ /**
+ * Utility function to parse the returned curl headers and store them in the
+ * class array variable.
+ *
+ * @param object $ch curl handle
+ * @param string $header the response headers
+ * @return the string length of the header
+ */
+ private function curlHeader($ch, $header) {
+ $i = strpos($header, ':');
+ if ( ! empty($i) ) {
+ $key = str_replace('-', '_', strtolower(substr($header, 0, $i)));
+ $value = trim(substr($header, $i + 2));
+ $this->response['headers'][$key] = $value;
+ }
+ return strlen($header);
+ }
+
+ /**
+ * Utility function to parse the returned curl buffer and store them until
+ * an EOL is found. The buffer for curl is an undefined size so we need
+ * to collect the content until an EOL is found.
+ *
+ * This function calls the previously defined streaming callback method.
+ *
+ * @param object $ch curl handle
+ * @param string $data the current curl buffer
+ */
+ private function curlWrite($ch, $data) {
+ $l = strlen($data);
+ if (strpos($data, $this->config['streaming_eol']) === false) {
+ $this->buffer .= $data;
+ return $l;
+ }
+
+ $buffered = explode($this->config['streaming_eol'], $data);
+ $content = $this->buffer . $buffered[0];
+
+ $this->metrics['tweets']++;
+ $this->metrics['bytes'] += strlen($content);
+
+ if ( ! function_exists($this->config['streaming_callback']))
+ return 0;
+
+ $metrics = $this->update_metrics();
+ $stop = call_user_func(
+ $this->config['streaming_callback'],
+ $content,
+ strlen($content),
+ $metrics
+ );
+ $this->buffer = $buffered[1];
+ if ($stop)
+ return 0;
+
+ return $l;
+ }
+
+ /**
+ * Makes a curl request. Takes no parameters as all should have been prepared
+ * by the request method
+ *
+ * @return void response data is stored in the class variable 'response'
+ */
+ private function curlit() {
+ // method handling
+ switch ($this->method) {
+ case 'POST':
+ break;
+ default:
+ // GET, DELETE request so convert the parameters to a querystring
+ if ( ! empty($this->request_params)) {
+ foreach ($this->request_params as $k => $v) {
+ // Multipart params haven't been encoded yet.
+ // Not sure why you would do a multipart GET but anyway, here's the support for it
+ if ($this->config['multipart']) {
+ $params[] = $this->safe_encode($k) . '=' . $this->safe_encode($v);
+ } else {
+ $params[] = $k . '=' . $v;
+ }
+ }
+ $qs = implode('&', $params);
+ $this->url = strlen($qs) > 0 ? $this->url . '?' . $qs : $this->url;
+ $this->request_params = array();
+ }
+ break;
+ }
+
+ if (@$this->config['prevent_request'])
+ return;
+
+ // configure curl
+ $c = curl_init();
+ curl_setopt($c, CURLOPT_USERAGENT, "themattharris' HTTP Client");
+ curl_setopt($c, CURLOPT_CONNECTTIMEOUT, $this->config['curl_connecttimeout']);
+ curl_setopt($c, CURLOPT_TIMEOUT, $this->config['curl_timeout']);
+ curl_setopt($c, CURLOPT_RETURNTRANSFER, TRUE);
+ curl_setopt($c, CURLOPT_SSL_VERIFYPEER, $this->config['curl_ssl_verifypeer']);
+ curl_setopt($c, CURLOPT_FOLLOWLOCATION, $this->config['curl_followlocation']);
+ curl_setopt($c, CURLOPT_PROXY, $this->config['curl_proxy']);
+ curl_setopt($c, CURLOPT_URL, $this->url);
+ // process the headers
+ curl_setopt($c, CURLOPT_HEADERFUNCTION, array($this, 'curlHeader'));
+ curl_setopt($c, CURLOPT_HEADER, FALSE);
+ curl_setopt($c, CURLINFO_HEADER_OUT, true);
+
+ if ($this->config['curl_proxyuserpwd'] !== false)
+ curl_setopt($c, CURLOPT_PROXYUSERPWD, $this->config['curl_proxyuserpwd']);
+
+ if ($this->config['is_streaming']) {
+ // process the body
+ $this->response['content-length'] = 0;
+ curl_setopt($c, CURLOPT_TIMEOUT, 0);
+ curl_setopt($c, CURLOPT_WRITEFUNCTION, array($this, 'curlWrite'));
+ }
+
+ switch ($this->method) {
+ case 'GET':
+ break;
+ case 'POST':
+ curl_setopt($c, CURLOPT_POST, TRUE);
+ break;
+ default:
+ curl_setopt($c, CURLOPT_CUSTOMREQUEST, $this->method);
+ }
+
+ if ( ! empty($this->request_params) ) {
+ // if not doing multipart we need to implode the parameters
+ if ( ! $this->config['multipart'] ) {
+ foreach ($this->request_params as $k => $v) {
+ $ps[] = "{$k}={$v}";
+ }
+ $this->request_params = implode('&', $ps);
+ }
+ curl_setopt($c, CURLOPT_POSTFIELDS, $this->request_params);
+ } else {
+ // CURL will set length to -1 when there is no data, which breaks Twitter
+ $this->headers[] = 'Content-Type:';
+ $this->headers[] = 'Content-Length:';
+ }
+
+ // CURL defaults to setting this to Expect: 100-Continue which Twitter rejects
+ $this->headers[] = 'Expect:';
+
+ if ( ! empty($this->headers))
+ curl_setopt($c, CURLOPT_HTTPHEADER, $this->headers);
+
+ // do it!
+ $response = curl_exec($c);
+ $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
+ $info = curl_getinfo($c);
+ curl_close($c);
+
+ // store the response
+ $this->response['code'] = $code;
+ $this->response['response'] = $response;
+ $this->response['info'] = $info;
+ return $code;
+ }
+
+ /**
+ * Debug function for printing the content of an object
+ *
+ * @param mixes $obj
+ */
+ function pr($obj) {
+ $cli = (PHP_SAPI == 'cli' && empty($_SERVER['REMOTE_ADDR']));
+ if (!$cli)
+ echo '<pre style="word-wrap: break-word">';
+ if ( is_object($obj) )
+ print_r($obj);
+ elseif ( is_array($obj) )
+ print_r($obj);
+ else
+ echo $obj;
+ if (!$cli)
+ echo '</pre>';
+ }
+
+ /**
+ * Returns the current URL. This is instead of PHP_SELF which is unsafe
+ *
+ * @param bool $dropqs whether to drop the querystring or not. Default true
+ * @return string the current URL
+ */
+ function php_self($dropqs=true) {
+ $url = sprintf('%s://%s%s',
+ empty($_SERVER['HTTPS']) ? 'http' : 'https',
+ $_SERVER['SERVER_NAME'],
+ $_SERVER['REQUEST_URI']
+ );
+
+ $parts = parse_url($url);
+
+ $port = $_SERVER['SERVER_PORT'];
+ $scheme = $parts['scheme'];
+ $host = $parts['host'];
+ $path = @$parts['path'];
+ $qs = @$parts['query'];
+
+ $port or $port = ($scheme == 'https') ? '443' : '80';
+
+ if (($scheme == 'https' && $port != '443')
+ || ($scheme == 'http' && $port != '80')) {
+ $host = "$host:$port";
+ }
+ $url = "$scheme://$host$path";
+ if ( ! $dropqs)
+ return "{$url}?{$qs}";
+ else
+ return $url;
+ }
+
+ /**
+ * Entifies the tweet using the given entities element
+ *
+ * @param array $tweet the json converted to normalised array
+ * @return the tweet text with entities replaced with hyperlinks
+ */
+ function entify($tweet) {
+ $keys = array();
+ $replacements = array();
+ $is_retweet = false;
+
+ if (isset($tweet['retweeted_status'])) {
+ $tweet = $tweet['retweeted_status'];
+ $is_retweet = true;
+ }
+
+ if (!isset($tweet['entities'])) {
+ return $tweet['text'];
+ }
+
+ // prepare the entities
+ foreach ($tweet['entities'] as $type => $things) {
+ foreach ($things as $entity => $value) {
+ $tweet_link = "<a href=\"http://twitter.com/{$value['screen_name']}/statuses/{$tweet['id']}\">{$tweet['created_at']}</a>";
+
+ switch ($type) {
+ case 'hashtags':
+ $href = "<a href=\"http://search.twitter.com/search?q=%23{$value['text']}\">#{$value['text']}</a>";
+ break;
+ case 'user_mentions':
+ $href = "@<a href=\"http://twitter.com/{$value['screen_name']}\" title=\"{$value['name']}\">{$value['screen_name']}</a>";
+ break;
+ case 'urls':
+ $url = empty($value['expanded_url']) ? $value['url'] : $value['expanded_url'];
+ $display = isset($value['display_url']) ? $value['display_url'] : str_replace('http://', '', $url);
+ // Not all pages are served in UTF-8 so you may need to do this ...
+ $display = urldecode(str_replace('%E2%80%A6', '&hellip;', urlencode($display)));
+ $href = "<a href=\"{$value['url']}\">{$display}</a>";
+ break;
+ }
+ $keys[$value['indices']['0']] = substr(
+ $tweet['text'],
+ $value['indices']['0'],
+ $value['indices']['1'] - $value['indices']['0']
+ );
+ $replacements[$value['indices']['0']] = $href;
+ }
+ }
+
+ ksort($replacements);
+ $replacements = array_reverse($replacements, true);
+ $entified_tweet = $tweet['text'];
+ foreach ($replacements as $k => $v) {
+ $entified_tweet = substr_replace($entified_tweet, $v, $k, strlen($keys[$k]));
+ }
+ return $entified_tweet;
+ }
+}
+
+?> \ No newline at end of file