├── .gitignore
├── LICENSE
├── README.md
├── TweetPHP.php
└── lib
├── tmhOAuth
├── LICENSE
├── README.md
├── cacert.pem
├── composer.json
└── tmhOAuth.php
└── twitter-text-php
├── .gitmodules
├── LICENSE
├── README
├── README.markdown
├── TODO
├── lib
└── Twitter
│ ├── Autolink.php
│ ├── Extractor.php
│ ├── HitHighlighter.php
│ └── Regex.php
├── phpunit.xml
└── tests
├── Twitter
├── AutolinkTest.php
├── ExtractorTest.php
└── HitHighlighterTest.php
├── bootstrap.php
├── example.php
└── runtests.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Jonathan Nicol
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TweetPHP
2 |
3 | A PHP class for querying the Twitter API and rendering tweets as an HTML list.
4 |
5 | ## Features
6 |
7 | - Works with Twitter API v1.1
8 | - Tweets are cached to avoid exceeding Twitter’s API request rate limits
9 | - A fallback is provided in case the API request fails
10 | - A configuration parameter allows you to specify how many tweets are displayed
11 | - Dates can optionally be displayed in “Twitter style”, e.g. “12 minutes ago”
12 | - You can customize the HTML that wraps your tweets, tweet status and meta information
13 |
14 | ## Authentication
15 |
16 | To interact with Twitter's API you will need to create a Twitter application at: https://dev.twitter.com/apps
17 |
18 | After creating your app you will need to take note of following API values: "Consumer key", "Consumer secret", "Access token", "Access token secret"
19 |
20 | ## Usage
21 |
22 | Your API credentials can be passed as options to the class constructor, along with the any other configuration options:
23 |
24 | $TweetPHP = new TweetPHP(array(
25 | 'consumer_key' => 'xxxxxxxxxxxxxxxxxxxxx',
26 | 'consumer_secret' => 'xxxxxxxxxxxxxxxxxxxxx',
27 | 'access_token' => 'xxxxxxxxxxxxxxxxxxxxx',
28 | 'access_token_secret' => 'xxxxxxxxxxxxxxxxxxxxx',
29 | 'api_params' => array('screen_name' => 'twitteruser')
30 | ));
31 |
32 | Then you can display the results like so:
33 |
34 | echo $TweetPHP->get_tweet_list();
35 |
36 | You can also retreive the raw data received from Twitter:
37 |
38 | $tweet_array = $TweetPHP->get_tweet_array();
39 |
40 | ## Options
41 |
42 | Options can be overridden by passing an array of key/value pairs to the class constructor. At a minimum you must set the `consumer_key`, `consumer_secret`, `access_token`, `access_token_secret` options, as shown above.
43 |
44 | You should also set an `api_endpoint` and `api_params`, an array of parameters to include with the call to the Twitter API.
45 |
46 | Here is a full list of options, and their default values:
47 |
48 | 'consumer_key' => '',
49 | 'consumer_secret' => '',
50 | 'access_token' => '',
51 | 'access_token_secret' => '',
52 | 'api_endpoint' => 'statuses/user_timeline',
53 | 'api_params' => array(),
54 | 'enable_cache' => true,
55 | 'cache_dir' => dirname(__FILE__) . '/cache/', // Where on the server to save cached tweets
56 | 'cachetime' => 60 * 60, // Seconds to cache feed (1 hour).
57 | 'tweets_to_retrieve' => 25, // Specifies the number of tweets to try and fetch, up to a maximum of 200
58 | 'tweets_to_display' => 10, // Number of tweets to display
59 | 'twitter_style_dates' => false, // Use twitter style dates e.g. 2 hours ago
60 | 'twitter_date_text' => array('seconds', 'minutes', 'about', 'hour', 'ago'),
61 | 'date_format' => '%I:%M %p %b %e%O', // The defult date format e.g. 12:08 PM Jun 12th. See: http://php.net/manual/en/function.strftime.php
62 | 'date_lang' => null, // Language for date e.g. 'fr_FR'. See: http://php.net/manual/en/function.setlocale.php
63 | 'twitter_template' => '
Latest tweets
',
64 | 'tweet_template' => '{tweet}{date}',
65 | 'error_template' => 'Our twitter feed is unavailable right now. Follow us on Twitter',
66 | 'nofollow_links' => false, // Add rel="nofollow" attribute to links
67 | 'debug' => false
68 |
69 | ### Deprecated options
70 |
71 | The following options have been deprecated. You should use `api_params` to set API parameters instead.
72 |
73 | 'twitter_screen_name' => ''
74 | 'ignore_replies' => true
75 | 'ignore_retweets' => true
76 |
77 | ## API endpoints
78 |
79 | Since TweetPHP uses Twitter's [Application-only](https://developer.twitter.com/en/docs/basics/authentication/overview/application-only) API authentication model, it can only access certain GET endpoints.
80 |
81 | It has been tested with the statuses/user_timeline endpoint (its default) and the search/tweets endpoint.
82 |
83 | ## Examples
84 |
85 | ### Fetch a user's timeline
86 |
87 | 'xxxxxxxxxxxxxxxxxxxxx',
92 | 'consumer_secret' => 'xxxxxxxxxxxxxxxxxxxxx',
93 | 'access_token' => 'xxxxxxxxxxxxxxxxxxxxx',
94 | 'access_token_secret' => 'xxxxxxxxxxxxxxxxxxxxx',
95 | 'api_endpoint' => 'statuses/user_timeline',
96 | 'api_params' => array('screen_name' => 'twitteruser')
97 | ));
98 |
99 | echo $TweetPHP->get_tweet_list();
100 | ?>
101 |
102 | Note that the `api_endpoint` option could be omitted in this case, since 'statuses/user_timeline' is its default value.
103 |
104 | ### Search for a hashtag
105 |
106 | 'xxxxxxxxxxxxxxxxxxxxx',
111 | 'consumer_secret' => 'xxxxxxxxxxxxxxxxxxxxx',
112 | 'access_token' => 'xxxxxxxxxxxxxxxxxxxxx',
113 | 'access_token_secret' => 'xxxxxxxxxxxxxxxxxxxxx',
114 | 'api_endpoint' => 'search/tweets',
115 | 'api_params' => array('q' => '#php', 'result_type'=>'latest')
116 | ));
117 |
118 | echo $TweetPHP->get_tweet_list();
119 | ?>
120 |
121 | ## Caching
122 |
123 | Caching is employed because Twitter rate limits how many times their feeds can be accessed per hour.
124 |
125 | When the user timeline is first loaded, the resultant HTML list is saved as a text file on your web server. The default location for this file is: `cache/twitter.txt`
126 |
127 | The raw Twitter response is saved as a serialized array in: `cache/twitter-array.txt`
128 |
129 | You can change these file paths using the `cache_dir` option. For example, to set a path from your root public directory try:
130 |
131 | $_SERVER['DOCUMENT_ROOT'] . '/path/to/my/cache/dir/'
132 |
133 | ## Debugging
134 |
135 | If you are experiencing problems using the script please set the `debug` option to `true`. This will set PHP's error reporting level to `E_ALL`, and will also display a debugging report.
136 |
137 | You can also fetch the debugging report as an array or HTML list, even when the `debug` option is set to `false`:
138 |
139 | echo $TweetPHP->get_debug_list();
140 | $debug_array = $TweetPHP->get_debug_array();
141 |
142 | ## Helper methods
143 |
144 | ### autolink
145 |
146 | Pass raw tweet text to `autolink()` and it will convert all usernames, hashtags and URLs to HTML links.
147 |
148 | $autolinked_tweet = $TweetPHP->autolink($tweet);
149 |
150 | This might be handy if you want to process tweets yourself, using the array returned by `get_tweet_array()`.
151 |
152 | ## Credits
153 |
154 | - Feed parsing uses Matt Harris' [tmhOAuth](https://github.com/themattharris/tmhOAuth)
155 | - Hashtag/username parsing uses Mike Cochrane's [twitter-text-php](https://github.com/mikenz/twitter-text-php)
156 | - Other contributors: [Matt Pugh](https://github.com/mattpugh), [Dario Bauer](https://github.com/dariobauer), [Lee Collings](https://github.com/leecollings), [Dom Abbott](https://github.com/domabbott92)
157 |
--------------------------------------------------------------------------------
/TweetPHP.php:
--------------------------------------------------------------------------------
1 | options = array_merge(
45 | array(
46 | 'consumer_key' => '',
47 | 'consumer_secret' => '',
48 | 'access_token' => '',
49 | 'access_token_secret' => '',
50 | 'api_endpoint' => 'statuses/user_timeline',
51 | 'api_params' => array(),
52 | 'enable_cache' => true,
53 | 'cache_dir' => dirname(__FILE__) . '/cache/', // Where on the server to save cached tweets
54 | 'cachetime' => 60 * 60, // Seconds to cache feed (1 hour).
55 | 'tweets_to_retrieve' => 25, // Specifies the number of tweets to try and fetch, up to a maximum of 200
56 | 'tweets_to_display' => 10, // Number of tweets to display
57 | 'twitter_style_dates' => false, // Use twitter style dates e.g. 2 hours ago
58 | 'twitter_date_text' => array('seconds', 'minutes', 'about', 'hour', 'ago'),
59 | 'date_format' => '%I:%M %p %b %e%O', // The defult date format e.g. 12:08 PM Jun 12th. See: http://php.net/manual/en/function.strftime.php
60 | 'date_lang' => null, // Language for date e.g. 'fr_FR'. See: http://php.net/manual/en/function.setlocale.php
61 | 'twitter_template' => 'Latest tweets
',
62 | 'tweet_template' => '{tweet} {date}',
63 | 'error_template' => 'Our twitter feed is unavailable right now. Follow us on Twitter',
64 | 'nofollow_links' => false, // Add rel="nofollow" attribute to links
65 | 'debug' => false,
66 | 'twitter_screen_name' => '', // Deprecated. Use api_params.
67 | 'ignore_replies' => true, // Deprecated. Use api_params.
68 | 'ignore_retweets' => true // Deprecated. Use api_params.
69 | ),
70 | $options
71 | );
72 |
73 | if ($this->options['debug']) {
74 | error_reporting(E_ALL);
75 | }
76 |
77 | // Check for Windows to find and replace the %e modifier with %#d, since Windows' %e implementation is broken
78 | if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
79 | $this->options['date_format'] = str_replace('%e', '%#d', $this->options['date_format']);
80 | }
81 |
82 | if ($this->options['date_lang']) {
83 | setlocale(LC_ALL, $this->options['date_lang']);
84 | }
85 |
86 | if ($this->options['enable_cache']) {
87 | if (!file_exists($this->options['cache_dir'])) {
88 | mkdir($this->options['cache_dir'], 0755, true);
89 | }
90 | $this->cache_file = $this->options['cache_dir'] . 'twitter.txt';
91 | $this->cache_file_raw = $this->options['cache_dir'] . 'twitter-array.txt';
92 | $cache_file_timestamp = ((file_exists($this->cache_file))) ? filemtime($this->cache_file) : 0;
93 | $this->add_debug_item('Cache expiration timestamp: ' . (time() - $this->options['cachetime']));
94 | $this->add_debug_item('Cache file timestamp: ' . $cache_file_timestamp);
95 |
96 | // Show file from cache if still valid.
97 | if (time() - $this->options['cachetime'] < $cache_file_timestamp) {
98 | $this->tweet_found = true;
99 | $this->add_debug_item('Cache file is newer than cachetime.');
100 | $this->tweet_list = file_get_contents($this->cache_file);
101 | $this->tweet_array = unserialize(file_get_contents($this->cache_file_raw));
102 | } else {
103 | $this->add_debug_item("Cache file doesn't exist or is older than cachetime.");
104 | $this->fetch_tweets();
105 | }
106 | } else {
107 | $this->add_debug_item('Caching is disabled.');
108 | $this->fetch_tweets();
109 | }
110 |
111 | // In case the feed did not parse or load correctly, show a link to the Twitter account.
112 | if (!$this->tweet_found) {
113 | $this->add_debug_item('No tweets were found. error_message will be displayed.');
114 | $html = str_replace('{link}', 'http://twitter.com/' . $this->options['twitter_screen_name'], $this->options['error_template']);
115 | $this->tweet_list = str_replace('{tweets}', $html, $this->options['twitter_template']);
116 | $this->tweet_array = array('Error fetching or loading tweets');
117 | }
118 | }
119 |
120 | /**
121 | * Fetch tweets using Twitter API
122 | */
123 | private function fetch_tweets () {
124 | $this->add_debug_item('Fetching fresh tweets using Twitter API.');
125 |
126 | require_once(dirname(__FILE__) . '/lib/tmhOAuth/tmhOAuth.php');
127 |
128 | // Creates a tmhOAuth object.
129 | $this->tmhOAuth = new tmhOAuth(array(
130 | 'consumer_key' => $this->options['consumer_key'],
131 | 'consumer_secret' => $this->options['consumer_secret'],
132 | 'token' => $this->options['access_token'],
133 | 'secret' => $this->options['access_token_secret']
134 | ));
135 |
136 | // Set Twitter API parameters
137 | $params = $this->options['api_params'];
138 | $params['count'] = $this->options['tweets_to_retrieve'];
139 | // Legacy param options for backwards compatibility.
140 | if (!empty($this->options['twitter_screen_name'])) {
141 | $params['screen_name'] = $this->options['twitter_screen_name'];
142 | }
143 | $params['include_rts'] = $this->options['ignore_retweets'] ? 'false' : 'true';
144 | $params['exclude_replies'] = $this->options['ignore_replies'] ? 'true' : 'false';
145 |
146 | // Request Twitter timeline.
147 | $response_code = $this->tmhOAuth->request('GET', $this->tmhOAuth->url('1.1/' . $this->options['api_endpoint'] . '.json'), $params);
148 |
149 | $this->add_debug_item('tmhOAuth response code: ' . $response_code);
150 |
151 | if ($response_code == 200) {
152 | $response = json_decode($this->tmhOAuth->response['response'], true);
153 |
154 | // Some twitter endpoints (e.g. search/tweets) store tweets in a `statuses` array.
155 | $data = array_key_exists('statuses', $response) ? $response['statuses'] : $response;
156 |
157 | $tweets_html = '';
158 |
159 | // Iterate over tweets.
160 | foreach($data as $tweet) {
161 | $tweets_html .= $this->parse_tweet($tweet);
162 | // If we have processed enough tweets, stop.
163 | if ($this->tweet_count >= $this->options['tweets_to_display']){
164 | break;
165 | }
166 | }
167 |
168 | // Close the twitter wrapping element.
169 | $html = str_replace('{tweets}', $tweets_html, $this->options['twitter_template']);
170 |
171 | if ($this->options['enable_cache']) {
172 | // Save the formatted tweet list to a file.
173 | $file = fopen($this->cache_file, 'w');
174 | fwrite($file, $html);
175 | fclose($file);
176 |
177 | // Save the raw data array to a file.
178 | $file = fopen($this->cache_file_raw, 'w');
179 | fwrite($file, serialize($data));
180 | fclose($file);
181 | }
182 |
183 | $this->tweet_list = $html;
184 | $this->tweet_array = $data;
185 | } else {
186 | $this->add_debug_item('Bad tmhOAuth response code.');
187 | }
188 | }
189 |
190 | /**
191 | * Parse an individual tweet
192 | */
193 | private function parse_tweet ($tweet) {
194 | $this->tweet_found = true;
195 | $this->tweet_count++;
196 |
197 | // Format tweet text
198 | $tweet_text_raw = $tweet['text'];
199 | $tweet_text = $this->autolink($tweet_text_raw);
200 |
201 | // Tweet date is in GMT. Convert to UNIX timestamp in the local time of the tweeter.
202 | $utc_offset = $tweet['user']['utc_offset'];
203 | $tweet_time = strtotime($tweet['created_at']) + $utc_offset;
204 |
205 | if ($this->options['twitter_style_dates']){
206 | // Convert tweet timestamp into Twitter style date ("About 2 hours ago")
207 | $current_time = time();
208 | $time_diff = abs($current_time - $tweet_time);
209 | switch ($time_diff) {
210 | case ($time_diff < 60):
211 | $display_time = $time_diff . ' ' . $this->options['twitter_date_text'][0] . ' ' . $this->options['twitter_date_text'][4];
212 | break;
213 | case ($time_diff >= 60 && $time_diff < 3600):
214 | $min = floor($time_diff/60);
215 | $display_time = $min . ' ' . $this->options['twitter_date_text'][1] . ' ' . $this->options['twitter_date_text'][4];
216 | break;
217 | case ($time_diff >= 3600 && $time_diff < 86400):
218 | $hour = floor($time_diff/3600);
219 | $display_time = $this->options['twitter_date_text'][2] . ' ' . $hour . ' ' . $this->options['twitter_date_text'][3];
220 | if ($hour > 1){ $display_time .= 's'; }
221 | $display_time .= ' ' . $this->options['twitter_date_text'][4];
222 | break;
223 | default:
224 | $format = str_replace('%O', date('S', $tweet_time), $this->options['date_format']);
225 | $display_time = strftime($format, $tweet_time);
226 | break;
227 | }
228 | } else {
229 | $format = str_replace('%O', date('S', $tweet_time), $this->options['date_format']);
230 | $display_time = strftime($format, $tweet_time);
231 | }
232 |
233 | $href = 'http://twitter.com/' . $tweet['user']['screen_name'] . '/status/' . $tweet['id_str'];
234 | $output = str_replace('{tweet}', $tweet_text, $this->options['tweet_template']);
235 | $output = str_replace('{link}', $href, $output);
236 | $output = str_replace('{date}', $display_time, $output);
237 |
238 | return $output;
239 | }
240 |
241 | /**
242 | * Add a debugging item.
243 | */
244 | private function add_debug_item ($msg) {
245 | array_push($this->debug_report, $msg);
246 | }
247 |
248 | /**
249 | * Get debugging information as an HTML list.
250 | */
251 | public function get_debug_list () {
252 | $debug_list = '';
253 | foreach($this->debug_report as $debug_item) {
254 | $debug_list .= '- ' . $debug_item . '
';
255 | }
256 | $debug_list .= '
';
257 | return $debug_list;
258 | }
259 |
260 | /**
261 | * Get debugging information as an array.
262 | */
263 | public function get_debug_array () {
264 | return $this->debug_report;
265 | }
266 |
267 | /**
268 | * Helper function to convert usernames, hashtags and URLs
269 | * in a tweet to HTML links.
270 | */
271 | public function autolink ($tweet) {
272 | require_once(dirname(__FILE__) . '/lib/twitter-text-php/lib/Twitter/Autolink.php');
273 |
274 | $autolinked_tweet = Twitter_Autolink::create($tweet, false)
275 | ->setNoFollow($this->options['nofollow_links'])
276 | ->setExternal(false)
277 | ->setTarget('')
278 | ->setUsernameClass('')
279 | ->setHashtagClass('')
280 | ->setURLClass('')
281 | ->addLinks();
282 |
283 | return $autolinked_tweet;
284 | }
285 |
286 | /**
287 | * Get tweets as HTML list
288 | */
289 | public function get_tweet_list () {
290 | if ($this->options['debug']) {
291 | return $this->get_debug_list() . $this->tweet_list;
292 | } else {
293 | return $this->tweet_list;
294 | }
295 | }
296 |
297 | /**
298 | * Get tweets as an array
299 | */
300 | public function get_tweet_array () {
301 | return $this->tweet_array;
302 | }
303 |
304 | }
305 |
--------------------------------------------------------------------------------
/lib/tmhOAuth/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/lib/tmhOAuth/README.md:
--------------------------------------------------------------------------------
1 | # tmhOAuth
2 |
3 | An OAuth library written in PHP by @themattharris.
4 |
5 | **Disclaimer**: This project is a work in progress. Please use the issue tracker
6 | to report any enhancements or issues you encounter.
7 |
8 | ## Goals
9 |
10 | - Support OAuth 1.0A
11 | - Use Authorisation headers instead of query string or POST parameters
12 | - Allow uploading of images
13 | - Provide enough information to assist with debugging
14 |
15 | ## Dependencies
16 |
17 | The library has been tested with PHP 5.3+ and relies on CURL and hash_hmac. The
18 | vast majority of hosting providers include these libraries and run with PHP 5.1+.
19 |
20 | The code makes use of hash_hmac, which was introduced in PHP 5.1.2. If your version
21 | of PHP is lower than this you should ask your hosting provider for an update.
22 |
23 | ## A note about security and SSL
24 |
25 | Version 0.60 hardened the security of the library and defaulted `curl_ssl_verifypeer` to `true`.
26 | As some hosting providers do not provide the most current certificate root file
27 | it is now included in this repository. If the version is out of date OR you prefer
28 | to download the certificate roots yourself, you can get them
29 | from: http://curl.haxx.se/ca/cacert.pem
30 |
31 | If you are getting http code 0 responses inspect `$tmhOAuth->response['error']` to see what the
32 | problem is. usually code 0 means cacert.pem cannot be found, and it can be fixed by putting cacert.pem
33 | in the location tmhOAuth is looking for it (indicated in the `$tmhOAuth->response['error']` message), or
34 | by setting `$tmhOAuth->config['curl_cainfo']` and `$tmhOAuth->config['curl_capath']` values. setting
35 | `$tmhOAuth->config['use_ssl']` to false *IS NOT* the way to solve this problem.
36 |
37 | ## Usage
38 |
39 | This will be built out later but for the moment review the examples repository
40 | for ways the library can be
41 | used. Each example contains instructions on how to use it.
42 |
43 | For guidance on how to use [composer](http://getcomposer.org) to install tmhOAuth see the
44 | [tmhOAuthExamples](https://github.com/themattharris/tmhOAuthExamples) project.
45 |
46 | ## Notes for users of previous versions
47 |
48 | As of version 0.8.0 tmhUtilities is no longer included. If you found them useful open an issue against me
49 | and i'll create a new repository for them. version 0.8.0 also ignores `$tmhOAuth->config['v']`. if you used
50 | this before you should instead specify the API version in the path you pass to `$tmhOAuth->url`
51 |
52 | Versions prior to 0.7.3 collapsed headers with the same value into one
53 | `$tmhOAuth->response['headers']` key. Since 0.7.3 headers with the same key will use an array
54 | to store their values.
55 |
56 | If you previously used version 0.4 be aware the utility functions
57 | have now been broken into their own file. Before you use version 0.5+ in your app
58 | test locally to ensure your code doesn't need tmhUtilities included.
59 |
60 | If you used custom HTTP request headers when they were defined as `'key: value'` strings
61 | you should now define them as `'key' => 'value'` pairs.
62 |
63 | ## Change History
64 |
65 | This is now published on the tmhOAuth wiki
66 |
67 | ## Community
68 |
69 | License: Apache 2 (see [included LICENSE file](https://github.com/themattharris/tmhOAuth/blob/master/LICENSE))
70 |
71 | Follow [@tmhOAuth](https://twitter.com/intent/follow?screen_name=tmhOAuth) to receive updates on releases, or ask for support
72 | Follow me on Twitter: [@themattharris](https://twitter.com/intent/follow?screen_name=themattharris)
73 | Check out the Twitter Developer Resources:
--------------------------------------------------------------------------------
/lib/tmhOAuth/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "themattharris/tmhoauth",
3 | "description": "An OAuth library written in PHP by @themattharris",
4 | "license": "Apache-2.0",
5 | "authors": [
6 | {
7 | "name": "themattharris",
8 | "email": "matt@themattharris.com",
9 | "role": "Developer"
10 | }
11 | ],
12 | "keywords": [
13 | "twitter",
14 | "oauth"
15 | ],
16 | "support": {
17 | "issues": "https://github.com/themattharris/tmhOAuth/issues"
18 | },
19 | "require": {
20 | "php": ">=5.3.0",
21 | "ext-curl": "*"
22 | },
23 | "autoload": {
24 | "psr-0": { "tmhOAuth": "" }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/tmhOAuth/tmhOAuth.php:
--------------------------------------------------------------------------------
1 | buffer = null;
27 | $this->reconfigure($config);
28 | $this->reset_request_settings();
29 | $this->set_user_agent();
30 | }
31 |
32 | public function reconfigure($config=array()) {
33 | // default configuration options
34 | $this->config = array_merge(
35 | array(
36 | // leave 'user_agent' blank for default, otherwise set this to
37 | // something that clearly identifies your app
38 | 'user_agent' => '',
39 | 'host' => 'api.twitter.com',
40 |
41 | 'consumer_key' => '',
42 | 'consumer_secret' => '',
43 | 'token' => '',
44 | 'secret' => '',
45 |
46 | // OAuth2 bearer token. This should already be URL encoded
47 | 'bearer' => '',
48 |
49 | // oauth signing variables that are not dynamic
50 | 'oauth_version' => '1.0',
51 | 'oauth_signature_method' => 'HMAC-SHA1',
52 |
53 | // you probably don't want to change any of these curl values
54 | 'curl_connecttimeout' => 30,
55 | 'curl_timeout' => 10,
56 |
57 | // for security this should always be set to 2.
58 | 'curl_ssl_verifyhost' => 2,
59 | // for security this should always be set to true.
60 | 'curl_ssl_verifypeer' => true,
61 | // for security this should always be set to true.
62 | 'use_ssl' => true,
63 |
64 | // you can get the latest cacert.pem from here http://curl.haxx.se/ca/cacert.pem
65 | // if you're getting HTTP 0 responses, check cacert.pem exists and is readable
66 | // without it curl won't be able to create an SSL connection
67 | 'curl_cainfo' => __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem',
68 | 'curl_capath' => __DIR__,
69 |
70 | 'curl_followlocation' => false, // whether to follow redirects or not
71 |
72 | // support for proxy servers
73 | 'curl_proxy' => false, // really you don't want to use this if you are using streaming
74 | 'curl_proxyuserpwd' => false, // format username:password for proxy, if required
75 | 'curl_encoding' => '', // leave blank for all supported formats, else use gzip, deflate, identity etc
76 |
77 | // streaming API configuration
78 | 'is_streaming' => false,
79 | 'streaming_eol' => "\r\n",
80 | 'streaming_metrics_interval' => 10,
81 |
82 | // header or querystring. You should always use header!
83 | // this is just to help me debug other developers implementations
84 | 'as_header' => true,
85 | 'force_nonce' => false, // used for checking signatures. leave as false for auto
86 | 'force_timestamp' => false, // used for checking signatures. leave as false for auto
87 | ),
88 | $config
89 | );
90 | }
91 |
92 | private function reset_request_settings($options=array()) {
93 | $this->request_settings = array(
94 | 'params' => array(),
95 | 'headers' => array(),
96 | 'with_user' => true,
97 | 'multipart' => false,
98 | );
99 |
100 | if (!empty($options))
101 | $this->request_settings = array_merge($this->request_settings, $options);
102 | }
103 |
104 | /**
105 | * Sets the useragent for PHP to use
106 | * If '$this->config['user_agent']' already has a value it is used instead of one
107 | * being generated.
108 | *
109 | * @return void value is stored to the config array class variable
110 | */
111 | private function set_user_agent() {
112 | if (!empty($this->config['user_agent']))
113 | return;
114 |
115 | $ssl = ($this->config['curl_ssl_verifyhost'] && $this->config['curl_ssl_verifypeer'] && $this->config['use_ssl']) ? '+' : '-';
116 | $ua = 'tmhOAuth ' . self::VERSION . $ssl . 'SSL - //github.com/themattharris/tmhOAuth';
117 | $this->config['user_agent'] = $ua;
118 | }
119 |
120 | /**
121 | * Generates a random OAuth nonce.
122 | * If 'force_nonce' is false a nonce will be generated, otherwise the value of '$this->config['force_nonce']' will be used.
123 | *
124 | * @param string $length how many characters the nonce should be before MD5 hashing. default 12
125 | * @param string $include_time whether to include time at the beginning of the nonce. default true
126 | * @return $nonce as a string
127 | */
128 | private function nonce($length=12, $include_time=true) {
129 | if ($this->config['force_nonce'] === false) {
130 | $prefix = $include_time ? microtime() : '';
131 | return md5(substr($prefix . uniqid(), 0, $length));
132 | } else {
133 | return $this->config['force_nonce'];
134 | }
135 | }
136 |
137 | /**
138 | * Generates a timestamp.
139 | * If 'force_timestamp' is false a timestamp will be generated, otherwise the value of '$this->config['force_timestamp']' will be used.
140 | *
141 | * @return $time as a string
142 | */
143 | private function timestamp() {
144 | if ($this->config['force_timestamp'] === false) {
145 | $time = time();
146 | } else {
147 | $time = $this->config['force_timestamp'];
148 | }
149 | return (string) $time;
150 | }
151 |
152 | /**
153 | * Encodes the string or array passed in a way compatible with OAuth.
154 | * If an array is passed each array value will will be encoded.
155 | *
156 | * @param mixed $data the scalar or array to encode
157 | * @return $data encoded in a way compatible with OAuth
158 | */
159 | private function safe_encode($data) {
160 | if (is_array($data)) {
161 | return array_map(array($this, 'safe_encode'), $data);
162 | } else if (is_scalar($data)) {
163 | return str_ireplace(
164 | array('+', '%7E'),
165 | array(' ', '~'),
166 | rawurlencode($data)
167 | );
168 | } else {
169 | return '';
170 | }
171 | }
172 |
173 | /**
174 | * Decodes the string or array from it's URL encoded form
175 | * If an array is passed each array value will will be decoded.
176 | *
177 | * @param mixed $data the scalar or array to decode
178 | * @return string $data decoded from the URL encoded form
179 | */
180 | private function safe_decode($data) {
181 | if (is_array($data)) {
182 | return array_map(array($this, 'safe_decode'), $data);
183 | } else if (is_scalar($data)) {
184 | return rawurldecode($data);
185 | } else {
186 | return '';
187 | }
188 | }
189 |
190 | /**
191 | * Prepares OAuth1 signing parameters.
192 | *
193 | * @return void all required OAuth parameters, safely encoded, are stored to the class variable '$this->request_settings['oauth1_params']'
194 | */
195 | private function prepare_oauth1_params() {
196 | $defaults = array(
197 | 'oauth_nonce' => $this->nonce(),
198 | 'oauth_timestamp' => $this->timestamp(),
199 | 'oauth_version' => $this->config['oauth_version'],
200 | 'oauth_consumer_key' => $this->config['consumer_key'],
201 | 'oauth_signature_method' => $this->config['oauth_signature_method'],
202 | );
203 |
204 | // include the user token if it exists
205 | if ( $oauth_token = $this->token() )
206 | $defaults['oauth_token'] = $oauth_token;
207 |
208 | $this->request_settings['oauth1_params'] = array();
209 |
210 | // safely encode
211 | foreach ($defaults as $k => $v) {
212 | $this->request_settings['oauth1_params'][$this->safe_encode($k)] = $this->safe_encode($v);
213 | }
214 | }
215 |
216 | private function token() {
217 | if ( $this->request_settings['with_user'] ) {
218 | if (isset($this->config['token']) && !empty($this->config['token'])) return $this->config['token'];
219 | elseif (isset($this->config['user_token'])) return $this->config['user_token'];
220 | }
221 | return '';
222 | }
223 |
224 | private function secret() {
225 | if ( $this->request_settings['with_user'] ) {
226 | if (isset($this->config['secret']) && !empty($this->config['secret'])) return $this->config['secret'];
227 | elseif (isset($this->config['user_secret'])) return $this->config['user_secret'];
228 | }
229 | return '';
230 | }
231 |
232 | /**
233 | * Extracts and decodes OAuth parameters from the passed string
234 | *
235 | * @param string $body the response body from an OAuth flow method
236 | * @return array the response body safely decoded to an array of key => values
237 | */
238 | public function extract_params($body) {
239 | $kvs = explode('&', $body);
240 | $decoded = array();
241 | foreach ($kvs as $kv) {
242 | $kv = explode('=', $kv, 2);
243 | $kv[0] = $this->safe_decode($kv[0]);
244 | $kv[1] = $this->safe_decode($kv[1]);
245 | $decoded[$kv[0]] = $kv[1];
246 | }
247 | return $decoded;
248 | }
249 |
250 | /**
251 | * Prepares the HTTP method for use in the base string by converting it to
252 | * uppercase.
253 | *
254 | * @return void value is stored to the class variable '$this->request_settings['method']'
255 | */
256 | private function prepare_method() {
257 | $this->request_settings['method'] = strtoupper($this->request_settings['method']);
258 | }
259 |
260 | /**
261 | * Prepares the URL for use in the base string by ripping it apart and
262 | * reconstructing it.
263 | *
264 | * Ref: 3.4.1.2
265 | *
266 | * @return void value is stored to the class array variable '$this->request_settings['url']'
267 | */
268 | private function prepare_url() {
269 | $parts = parse_url($this->request_settings['url']);
270 |
271 | $port = isset($parts['port']) ? $parts['port'] : false;
272 | $scheme = $parts['scheme'];
273 | $host = $parts['host'];
274 | $path = isset($parts['path']) ? $parts['path'] : false;
275 |
276 | $port or $port = ($scheme == 'https') ? '443' : '80';
277 |
278 | if (($scheme == 'https' && $port != '443') || ($scheme == 'http' && $port != '80')) {
279 | $host = "$host:$port";
280 | }
281 |
282 | // the scheme and host MUST be lowercase
283 | $this->request_settings['url'] = strtolower("$scheme://$host");
284 | // but not the path
285 | $this->request_settings['url'] .= $path;
286 | }
287 |
288 | /**
289 | * Prepares all parameters for the base string and request.
290 | * Multipart parameters are ignored as they are not defined in the specification,
291 | * all other types of parameter are encoded for compatibility with OAuth.
292 | *
293 | * @param array $params the parameters for the request
294 | * @return void prepared values are stored in the class array variable '$this->request_settings'
295 | */
296 | private function prepare_params() {
297 | $doing_oauth1 = false;
298 | $this->request_settings['prepared_params'] = array();
299 | $prepared = &$this->request_settings['prepared_params'];
300 | $prepared_pairs = array();
301 | $prepared_pairs_with_oauth = array();
302 |
303 | if (isset($this->request_settings['oauth1_params'])) {
304 | $oauth1 = &$this->request_settings['oauth1_params'];
305 | $doing_oauth1 = true;
306 | $params = array_merge($oauth1, $this->request_settings['params']);
307 |
308 | // Remove oauth_signature if present
309 | // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
310 | unset($params['oauth_signature']);
311 |
312 | // empty the oauth1 array. we reset these values later in this method
313 | $oauth1 = array();
314 | } else {
315 | $params = $this->request_settings['params'];
316 | }
317 |
318 | // Parameters are sorted by name, using lexicographical byte value ordering.
319 | // Ref: Spec: 9.1.1 (1)
320 | uksort($params, 'strcmp');
321 |
322 | // encode params unless we're doing multipart
323 | foreach ($params as $k => $v) {
324 | $k = $this->request_settings['multipart'] ? $k : $this->safe_encode($k);
325 |
326 | if (is_array($v))
327 | $v = implode(',', $v);
328 |
329 | $v = $this->request_settings['multipart'] ? $v : $this->safe_encode($v);
330 |
331 | // split parameters for the basestring and authorization header, and recreate the oauth1 array
332 | if ($doing_oauth1) {
333 | // if we're doing multipart, only store the oauth_* params, ignore the users request params
334 | if ((strpos($k, 'oauth') === 0) || !$this->request_settings['multipart'])
335 | $prepared_pairs_with_oauth[] = "{$k}={$v}";
336 |
337 | if (strpos($k, 'oauth') === 0) {
338 | $oauth1[$k] = $v;
339 | continue;
340 | }
341 | }
342 | $prepared[$k] = $v;
343 | $prepared_pairs[] = "{$k}={$v}";
344 | }
345 |
346 | if ($doing_oauth1) {
347 | $this->request_settings['basestring_params'] = implode('&', $prepared_pairs_with_oauth);
348 | }
349 |
350 | // setup params for GET/POST method handling
351 | if (!empty($prepared_pairs)) {
352 | $content = implode('&', $prepared_pairs);
353 |
354 | switch ($this->request_settings['method']) {
355 | case 'POST':
356 | $this->request_settings['postfields'] = $this->request_settings['multipart'] ? $prepared : $content;
357 | break;
358 | default:
359 | $this->request_settings['querystring'] = $content;
360 | break;
361 | }
362 | }
363 | }
364 |
365 | /**
366 | * Prepares the OAuth signing key
367 | *
368 | * @return void prepared signing key is stored in the class variable 'signing_key'
369 | */
370 | private function prepare_signing_key() {
371 | $left = $this->safe_encode($this->config['consumer_secret']);
372 | $right = $this->safe_encode($this->secret());
373 | $this->request_settings['signing_key'] = $left . '&' . $right;
374 | }
375 |
376 | /**
377 | * Prepare the base string.
378 | * Ref: Spec: 9.1.3 ("Concatenate Request Elements")
379 | *
380 | * @return void prepared base string is stored in the class variable 'base_string'
381 | */
382 | private function prepare_base_string() {
383 | $url = $this->request_settings['url'];
384 |
385 | # if the host header is set we need to rewrite the basestring to use
386 | # that, instead of the request host. otherwise the signature won't match
387 | # on the server side
388 | if (!empty($this->request_settings['headers']['Host'])) {
389 | $url = str_ireplace(
390 | $this->config['host'],
391 | $this->request_settings['headers']['Host'],
392 | $url
393 | );
394 | }
395 |
396 | $base = array(
397 | $this->request_settings['method'],
398 | $url,
399 | $this->request_settings['basestring_params']
400 | );
401 | $this->request_settings['basestring'] = implode('&', $this->safe_encode($base));
402 | }
403 |
404 | /**
405 | * Signs the OAuth 1 request
406 | *
407 | * @return void oauth_signature is added to the parameters in the class array variable '$this->request_settings'
408 | */
409 | private function prepare_oauth_signature() {
410 | $this->request_settings['oauth1_params']['oauth_signature'] = $this->safe_encode(
411 | base64_encode(
412 | hash_hmac(
413 | 'sha1', $this->request_settings['basestring'], $this->request_settings['signing_key'], true
414 | )));
415 | }
416 |
417 | /**
418 | * Prepares the Authorization header
419 | *
420 | * @return void prepared authorization header is stored in the class variable headers['Authorization']
421 | */
422 | private function prepare_auth_header() {
423 | if (!$this->config['as_header'])
424 | return;
425 |
426 | // oauth1
427 | if (isset($this->request_settings['oauth1_params'])) {
428 | // sort again as oauth_signature was added post param preparation
429 | uksort($this->request_settings['oauth1_params'], 'strcmp');
430 | $encoded_quoted_pairs = array();
431 | foreach ($this->request_settings['oauth1_params'] as $k => $v) {
432 | $encoded_quoted_pairs[] = "{$k}=\"{$v}\"";
433 | }
434 | $header = 'OAuth ' . implode(', ', $encoded_quoted_pairs);
435 | } elseif (!empty($this->config['bearer'])) {
436 | $header = 'Bearer ' . $this->config['bearer'];
437 | }
438 |
439 | if (isset($header))
440 | $this->request_settings['headers']['Authorization'] = $header;
441 | }
442 |
443 | /**
444 | * Create the bearer token for OAuth2 requests from the consumer_key and consumer_secret.
445 | *
446 | * @return string the bearer token
447 | */
448 | public function bearer_token_credentials() {
449 | $credentials = implode(':', array(
450 | $this->safe_encode($this->config['consumer_key']),
451 | $this->safe_encode($this->config['consumer_secret'])
452 | ));
453 | return base64_encode($credentials);
454 | }
455 |
456 | /**
457 | * Make an HTTP request using this library. This method doesn't return anything.
458 | * Instead the response should be inspected directly.
459 | *
460 | * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
461 | * @param string $url the request URL without query string parameters
462 | * @param array $params the request parameters as an array of key=value pairs. Default empty array
463 | * @param string $useauth whether to use authentication when making the request. Default true
464 | * @param string $multipart whether this request contains multipart data. Default false
465 | * @param array $headers any custom headers to send with the request. Default empty array
466 | * @return int the http response code for the request. 0 is returned if a connection could not be made
467 | */
468 | public function request($method, $url, $params=array(), $useauth=true, $multipart=false, $headers=array()) {
469 | $options = array(
470 | 'method' => $method,
471 | 'url' => $url,
472 | 'params' => $params,
473 | 'with_user' => true,
474 | 'multipart' => $multipart,
475 | 'headers' => $headers
476 | );
477 | $options = array_merge($this->default_options(), $options);
478 |
479 | if ($useauth) {
480 | return $this->user_request($options);
481 | } else {
482 | return $this->unauthenticated_request($options);
483 | }
484 | }
485 |
486 | public function apponly_request($options=array()) {
487 | $options = array_merge($this->default_options(), $options, array(
488 | 'with_user' => false,
489 | ));
490 | $this->reset_request_settings($options);
491 | if ($options['without_bearer']) {
492 | return $this->oauth1_request();
493 | } else {
494 | $this->prepare_method();
495 | $this->prepare_url();
496 | $this->prepare_params();
497 | $this->prepare_auth_header();
498 | return $this->curlit();
499 | }
500 | }
501 |
502 | public function user_request($options=array()) {
503 | $options = array_merge($this->default_options(), $options, array(
504 | 'with_user' => true,
505 | ));
506 | $this->reset_request_settings($options);
507 | return $this->oauth1_request();
508 | }
509 |
510 | public function unauthenticated_request($options=array()) {
511 | $options = array_merge($this->default_options(), $options, array(
512 | 'with_user' => false,
513 | ));
514 | $this->reset_request_settings($options);
515 | $this->prepare_method();
516 | $this->prepare_url();
517 | $this->prepare_params();
518 | return $this->curlit();
519 | }
520 |
521 | /**
522 | * Signs the request and adds the OAuth signature. This runs all the request
523 | * parameter preparation methods.
524 | *
525 | * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
526 | * @param string $url the request URL without query string parameters
527 | * @param array $params the request parameters as an array of key=value pairs
528 | * @param boolean $with_user whether to include the user credentials when making the request.
529 | * @return void
530 | */
531 | private function oauth1_request() {
532 | $this->prepare_oauth1_params();
533 | $this->prepare_method();
534 | $this->prepare_url();
535 | $this->prepare_params();
536 | $this->prepare_base_string();
537 | $this->prepare_signing_key();
538 | $this->prepare_oauth_signature();
539 | $this->prepare_auth_header();
540 | return $this->curlit();
541 | }
542 |
543 | private function default_options() {
544 | return array(
545 | 'method' => 'GET',
546 | 'params' => array(),
547 | 'with_user' => true,
548 | 'multipart' => false,
549 | 'headers' => array(),
550 | 'without_bearer' => false,
551 | );
552 | }
553 |
554 | /**
555 | * Make a long poll HTTP request using this library. This method is
556 | * different to the other request methods as it isn't supposed to disconnect
557 | *
558 | * Using this method expects a callback which will receive the streaming
559 | * responses.
560 | *
561 | * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
562 | * @param string $url the request URL without query string parameters
563 | * @param array $params the request parameters as an array of key=value pairs
564 | * @param string $callback the callback function to stream the buffer to.
565 | * @return void
566 | */
567 | public function streaming_request($method, $url, $params=array(), $callback='') {
568 | if ( ! empty($callback) ) {
569 | if ( ! is_callable($callback) ) {
570 | return false;
571 | }
572 | $this->config['streaming_callback'] = $callback;
573 | }
574 | $this->metrics['start'] = time();
575 | $this->metrics['interval_start'] = $this->metrics['start'];
576 | $this->metrics['messages'] = 0;
577 | $this->metrics['last_messages'] = 0;
578 | $this->metrics['bytes'] = 0;
579 | $this->metrics['last_bytes'] = 0;
580 | $this->config['is_streaming'] = true;
581 | $this->request($method, $url, $params);
582 | }
583 |
584 | /**
585 | * Handles the updating of the current Streaming API metrics.
586 | *
587 | * @return array the metrics for the streaming api connection
588 | */
589 | private function update_metrics() {
590 | $now = time();
591 | if (($this->metrics['interval_start'] + $this->config['streaming_metrics_interval']) > $now)
592 | return null;
593 |
594 | $this->metrics['mps'] = round( ($this->metrics['messages'] - $this->metrics['last_messages']) / $this->config['streaming_metrics_interval'], 2);
595 | $this->metrics['bps'] = round( ($this->metrics['bytes'] - $this->metrics['last_bytes']) / $this->config['streaming_metrics_interval'], 2);
596 |
597 | $this->metrics['last_bytes'] = $this->metrics['bytes'];
598 | $this->metrics['last_messages'] = $this->metrics['messages'];
599 | $this->metrics['interval_start'] = $now;
600 | return $this->metrics;
601 | }
602 |
603 | /**
604 | * Utility function to create the request URL in the requested format.
605 | * If a fully-qualified URI is provided, it will be returned.
606 | * Any multi-slashes (except for the protocol) will be replaced with a single slash.
607 | *
608 | *
609 | * @param string $request the API method without extension
610 | * @param string $extension the format of the response. Default json. Set to an empty string to exclude the format
611 | * @return string the concatenation of the host, API version, API method and format, or $request if it begins with http
612 | */
613 | public function url($request, $extension='json') {
614 | // remove multi-slashes
615 | $request = preg_replace('$([^:])//+$', '$1/', $request);
616 |
617 | if (stripos($request, 'http') === 0 || stripos($request, '//') === 0) {
618 | return $request;
619 | }
620 |
621 | $extension = strlen($extension) > 0 ? ".$extension" : '';
622 | $proto = $this->config['use_ssl'] ? 'https:/' : 'http:/';
623 |
624 | // trim trailing slash
625 | $request = ltrim($request, '/');
626 |
627 | $pos = strlen($request) - strlen($extension);
628 | if (substr($request, $pos) === $extension)
629 | $request = substr_replace($request, '', $pos);
630 |
631 | return implode('/', array(
632 | $proto,
633 | $this->config['host'],
634 | $request . $extension
635 | ));
636 | }
637 |
638 | /**
639 | * Public access to the private safe decode/encode methods
640 | *
641 | * @param string $text the text to transform
642 | * @param string $mode the transformation mode. either encode or decode
643 | * @return string $text transformed by the given $mode
644 | */
645 | public function transformText($text, $mode='encode') {
646 | return $this->{"safe_$mode"}($text);
647 | }
648 |
649 | /**
650 | * Utility function to parse the returned curl headers and store them in the
651 | * class array variable.
652 | *
653 | * @param object $ch curl handle
654 | * @param string $header the response headers
655 | * @return string the length of the header
656 | */
657 | private function curlHeader($ch, $header) {
658 | $this->response['raw'] .= $header;
659 |
660 | list($key, $value) = array_pad(explode(':', $header, 2), 2, null);
661 |
662 | $key = trim($key);
663 | $value = trim($value);
664 |
665 | if ( ! isset($this->response['headers'][$key])) {
666 | $this->response['headers'][$key] = $value;
667 | } else {
668 | if (!is_array($this->response['headers'][$key])) {
669 | $this->response['headers'][$key] = array($this->response['headers'][$key]);
670 | }
671 | $this->response['headers'][$key][] = $value;
672 | }
673 |
674 | return strlen($header);
675 | }
676 |
677 | /**
678 | * Utility function to parse the returned curl buffer and store them until
679 | * an EOL is found. The buffer for curl is an undefined size so we need
680 | * to collect the content until an EOL is found.
681 | *
682 | * This function calls the previously defined streaming callback method.
683 | *
684 | * @param object $ch curl handle
685 | * @param string $data the current curl buffer
686 | * @return int the length of the data string processed in this function
687 | */
688 | private function curlWrite($ch, $data) {
689 | $l = strlen($data);
690 | if (strpos($data, $this->config['streaming_eol']) === false) {
691 | $this->buffer .= $data;
692 | return $l;
693 | }
694 |
695 | $buffered = explode($this->config['streaming_eol'], $data);
696 | $content = $this->buffer . $buffered[0];
697 |
698 | $this->metrics['messages']++;
699 | $this->metrics['bytes'] += strlen($content);
700 |
701 | if ( ! is_callable($this->config['streaming_callback']))
702 | return 0;
703 |
704 | $metrics = $this->update_metrics();
705 | $stop = call_user_func(
706 | $this->config['streaming_callback'],
707 | $content,
708 | strlen($content),
709 | $metrics
710 | );
711 | $this->buffer = $buffered[1];
712 | if ($stop)
713 | return 0;
714 |
715 | return $l;
716 | }
717 |
718 | /**
719 | * Makes a curl request. Takes no parameters as all should have been prepared
720 | * by the request method
721 | *
722 | * the response data is stored in the class variable 'response'
723 | *
724 | * @return int the http response code for the request. 0 is returned if a connection could not be made
725 | */
726 | private function curlit() {
727 | $this->response = array(
728 | 'raw' => ''
729 | );
730 |
731 | // configure curl
732 | $c = curl_init();
733 | switch ($this->request_settings['method']) {
734 | case 'GET':
735 | if (isset($this->request_settings['querystring']))
736 | $this->request_settings['url'] = $this->request_settings['url'] . '?' . $this->request_settings['querystring'];
737 | break;
738 | case 'POST':
739 | curl_setopt($c, CURLOPT_POST, true);
740 | if (isset($this->request_settings['postfields']))
741 | $postfields = $this->request_settings['postfields'];
742 | else
743 | $postfields = array();
744 |
745 | curl_setopt($c, CURLOPT_POSTFIELDS, $postfields);
746 | break;
747 | default:
748 | if (isset($this->request_settings['postfields']))
749 | curl_setopt($c, CURLOPT_CUSTOMREQUEST, $this->request_settings['postfields']);
750 | }
751 |
752 | curl_setopt_array($c, array(
753 | CURLOPT_USERAGENT => $this->config['user_agent'],
754 | CURLOPT_CONNECTTIMEOUT => $this->config['curl_connecttimeout'],
755 | CURLOPT_TIMEOUT => $this->config['curl_timeout'],
756 | CURLOPT_RETURNTRANSFER => true,
757 | CURLOPT_SSL_VERIFYPEER => $this->config['curl_ssl_verifypeer'],
758 | CURLOPT_SSL_VERIFYHOST => $this->config['curl_ssl_verifyhost'],
759 |
760 | CURLOPT_FOLLOWLOCATION => $this->config['curl_followlocation'],
761 | CURLOPT_PROXY => $this->config['curl_proxy'],
762 | CURLOPT_ENCODING => $this->config['curl_encoding'],
763 | CURLOPT_URL => $this->request_settings['url'],
764 | // process the headers
765 | CURLOPT_HEADERFUNCTION => array($this, 'curlHeader'),
766 | CURLOPT_HEADER => false,
767 | CURLINFO_HEADER_OUT => true,
768 | ));
769 |
770 | if ($this->config['curl_cainfo'] !== false)
771 | curl_setopt($c, CURLOPT_CAINFO, $this->config['curl_cainfo']);
772 |
773 | if ($this->config['curl_capath'] !== false)
774 | curl_setopt($c, CURLOPT_CAPATH, $this->config['curl_capath']);
775 |
776 | if ($this->config['curl_proxyuserpwd'] !== false)
777 | curl_setopt($c, CURLOPT_PROXYUSERPWD, $this->config['curl_proxyuserpwd']);
778 |
779 | if ($this->config['is_streaming']) {
780 | // process the body
781 | $this->response['content-length'] = 0;
782 | curl_setopt($c, CURLOPT_TIMEOUT, 0);
783 | curl_setopt($c, CURLOPT_WRITEFUNCTION, array($this, 'curlWrite'));
784 | }
785 |
786 | if ( ! empty($this->request_settings['headers'])) {
787 | foreach ($this->request_settings['headers'] as $k => $v) {
788 | $headers[] = trim($k . ': ' . $v);
789 | }
790 | curl_setopt($c, CURLOPT_HTTPHEADER, $headers);
791 | }
792 |
793 | if (isset($this->config['block']) && (true === $this->config['block']))
794 | return 0;
795 |
796 | // do it!
797 | $response = curl_exec($c);
798 | $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
799 | $info = curl_getinfo($c);
800 | $error = curl_error($c);
801 | $errno = curl_errno($c);
802 | curl_close($c);
803 |
804 | // store the response
805 | $this->response['code'] = $code;
806 | $this->response['response'] = $response;
807 | $this->response['info'] = $info;
808 | $this->response['error'] = $error;
809 | $this->response['errno'] = $errno;
810 |
811 | if (!isset($this->response['raw'])) {
812 | $this->response['raw'] = '';
813 | }
814 | $this->response['raw'] .= $response;
815 |
816 | return $code;
817 | }
818 | }
--------------------------------------------------------------------------------
/lib/twitter-text-php/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "tests/data/twitter-text-conformance"]
2 | path = tests/data/twitter-text-conformance
3 | url = git://github.com/mzsanford/twitter-text-conformance.git
4 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2010 Mike Cochrane
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 | use this file except in compliance with the License. You may obtain a copy of
5 | the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 | License for the specific language governing permissions and limitations under
13 | the License.
14 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/README:
--------------------------------------------------------------------------------
1 | # Twitter Text (PHP Edition) #
2 |
3 | A library of PHP classes that provide auto-linking and extraction of usernames,
4 | lists, hashtags and URLs from tweets. Originally created from twitter-text-rb
5 | and twitter-text-java projects by Matt Sanford and ported to PHP by Mike
6 | Cochrane, this library has been improved and made more complete by Nick Pope.
7 |
8 | ## Features ##
9 |
10 | ### Autolink ##
11 |
12 | - Add links to all matching Twitter usernames (no account verification).
13 | - Add links to all user lists (of the form @username/list-name).
14 | - Add links to all valid hashtags.
15 | - Add links to all URLs.
16 | - Support for international character sets.
17 |
18 | ### Extractor ###
19 |
20 | - Extract mentioned Twitter usernames (from anywhere in the tweet).
21 | - Extract replied to Twitter usernames (from start of the tweet).
22 | - Extract all user lists (of the form @username/list-name).
23 | - Extract all valid hashtags.
24 | - Extract all URLs.
25 | - Support for international character sets.
26 |
27 | ### Hit Highlighter ###
28 |
29 | - Highlight text specifed by a range by surrounding with a tag.
30 | - Support for highlighting when tweet has already been autolinked.
31 | - Support for international character sets.
32 |
33 | ## Examples ##
34 |
35 | For examples, please see `tests/example.php` which you can view in a browser or
36 | run from the command line.
37 |
38 | ## Conformance ##
39 |
40 | You'll need the test data which is in YAML format from the following
41 | repository:
42 |
43 | http://github.com/mzsanford/twitter-text-conformance
44 |
45 | It has already been added as a git submodule so you should just need to run:
46 |
47 | git submodule init
48 | git submodule update
49 |
50 | As PHP has no native support for YAML you'll need to checkout spyc from svn
51 | into `tests/spyc`:
52 |
53 | svn checkout http://spyc.googlecode.com/svn/trunk/ tests/spyc
54 |
55 | There are a couple of options for testing conformance:
56 |
57 | 1. Run `phpunit` in from the root folder of the project.
58 | 2. Run `tests/runtests.php` from the command line.
59 | 3. Make `tests/runtests.php` accessible on a web server and view it in your
60 | browser.
61 |
62 | ## Thanks & Contributions ##
63 |
64 | The bulk of this library is from the heroic efforts of:
65 |
66 | - Matt Sanford (https://github.com/mzsanford): For the orignal Ruby and Java implementions.
67 | - Mike Cochrane (https://github.com/mikenz): For the initial PHP code.
68 | - Nick Pope (https://github.com/ngnpope): For the bulk of the maintenance work to date.
69 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/README.markdown:
--------------------------------------------------------------------------------
1 | # Twitter Text (PHP Edition) #
2 |
3 | A library of PHP classes that provide auto-linking and extraction of usernames,
4 | lists, hashtags and URLs from tweets. Originally created from twitter-text-rb
5 | and twitter-text-java projects by Matt Sanford and ported to PHP by Mike
6 | Cochrane, this library has been improved and made more complete by Nick Pope.
7 |
8 | ## Features ##
9 |
10 | ### Autolink ##
11 |
12 | - Add links to all matching Twitter usernames (no account verification).
13 | - Add links to all user lists (of the form @username/list-name).
14 | - Add links to all valid hashtags.
15 | - Add links to all URLs.
16 | - Support for international character sets.
17 |
18 | ### Extractor ###
19 |
20 | - Extract mentioned Twitter usernames (from anywhere in the tweet).
21 | - Extract replied to Twitter usernames (from start of the tweet).
22 | - Extract all user lists (of the form @username/list-name).
23 | - Extract all valid hashtags.
24 | - Extract all URLs.
25 | - Support for international character sets.
26 |
27 | ### Hit Highlighter ###
28 |
29 | - Highlight text specifed by a range by surrounding with a tag.
30 | - Support for highlighting when tweet has already been autolinked.
31 | - Support for international character sets.
32 |
33 | ## Examples ##
34 |
35 | For examples, please see `tests/example.php` which you can view in a browser or
36 | run from the command line.
37 |
38 | ## Conformance ##
39 |
40 | You'll need the test data which is in YAML format from the following
41 | repository:
42 |
43 | http://github.com/mzsanford/twitter-text-conformance
44 |
45 | It has already been added as a git submodule so you should just need to run:
46 |
47 | git submodule init
48 | git submodule update
49 |
50 | As PHP has no native support for YAML you'll need to checkout spyc from svn
51 | into `tests/spyc`:
52 |
53 | svn checkout http://spyc.googlecode.com/svn/trunk/ tests/spyc
54 |
55 | There are a couple of options for testing conformance:
56 |
57 | 1. Run `phpunit` in from the root folder of the project.
58 | 2. Run `tests/runtests.php` from the command line.
59 | 3. Make `tests/runtests.php` accessible on a web server and view it in your
60 | browser.
61 |
62 | ## Thanks & Contributions ##
63 |
64 | The bulk of this library is from the heroic efforts of:
65 |
66 | - Matt Sanford (https://github.com/mzsanford): For the orignal Ruby and Java implementions.
67 | - Mike Cochrane (https://github.com/mikenz): For the initial PHP code.
68 | - Nick Pope (https://github.com/ngnpope): For the bulk of the maintenance work to date.
69 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/TODO:
--------------------------------------------------------------------------------
1 | - Generate PHP Documentation
2 | - Add a phing build.xml file for generating documentation and running tests.
3 | - Tidy up regular expressions:
4 | - Modularise repeated sections.
5 | - Remove unnecessary capturing.
6 | - Tidy up tests/runtests.php to refactor repeated sections.
7 | - Check unicode/mbstring handling...
8 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/lib/Twitter/Autolink.php:
--------------------------------------------------------------------------------
1 |
4 | * @author Nick Pope
5 | * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
6 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
7 | * @package Twitter
8 | */
9 |
10 | require_once 'Regex.php';
11 |
12 | /**
13 | * Twitter Autolink Class
14 | *
15 | * Parses tweets and generates HTML anchor tags around URLs, usernames,
16 | * username/list pairs and hashtags.
17 | *
18 | * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
19 | * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
20 | * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
21 | *
22 | * @author Mike Cochrane
23 | * @author Nick Pope
24 | * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
25 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
26 | * @package Twitter
27 | */
28 | class Twitter_Autolink extends Twitter_Regex {
29 |
30 | /**
31 | * CSS class for auto-linked URLs.
32 | *
33 | * @var string
34 | */
35 | protected $class_url = 'url';
36 |
37 | /**
38 | * CSS class for auto-linked username URLs.
39 | *
40 | * @var string
41 | */
42 | protected $class_user = 'username';
43 |
44 | /**
45 | * CSS class for auto-linked list URLs.
46 | *
47 | * @var string
48 | */
49 | protected $class_list = 'list';
50 |
51 | /**
52 | * CSS class for auto-linked hashtag URLs.
53 | *
54 | * @var string
55 | */
56 | protected $class_hash = 'hashtag';
57 |
58 | /**
59 | * URL base for username links (the username without the @ will be appended).
60 | *
61 | * @var string
62 | */
63 | protected $url_base_user = 'http://twitter.com/';
64 |
65 | /**
66 | * URL base for list links (the username/list without the @ will be appended).
67 | *
68 | * @var string
69 | */
70 | protected $url_base_list = 'http://twitter.com/';
71 |
72 | /**
73 | * URL base for hashtag links (the hashtag without the # will be appended).
74 | *
75 | * @var string
76 | */
77 | protected $url_base_hash = 'http://twitter.com/search?q=%23';
78 |
79 | /**
80 | * Whether to include the value 'nofollow' in the 'rel' attribute.
81 | *
82 | * @var bool
83 | */
84 | protected $nofollow = true;
85 |
86 | /**
87 | * Whether to include the value 'external' in the 'rel' attribute.
88 | *
89 | * Often this is used to be matched on in JavaScript for dynamically adding
90 | * the 'target' attribute which is deprecated in HTML 4.01. In HTML 5 it has
91 | * been undeprecated and thus the 'target' attribute can be used. If this is
92 | * set to false then the 'target' attribute will be output.
93 | *
94 | * @var bool
95 | */
96 | protected $external = true;
97 |
98 | /**
99 | * The scope to open the link in.
100 | *
101 | * Support for the 'target' attribute was deprecated in HTML 4.01 but has
102 | * since been reinstated in HTML 5. To output the 'target' attribute you
103 | * must disable the adding of the string 'external' to the 'rel' attribute.
104 | *
105 | * @var string
106 | */
107 | protected $target = '_blank';
108 |
109 | /**
110 | * Provides fluent method chaining.
111 | *
112 | * @param string $tweet The tweet to be converted.
113 | * @param bool $full_encode Whether to encode all special characters.
114 | *
115 | * @see __construct()
116 | *
117 | * @return Twitter_Autolink
118 | */
119 | public static function create($tweet, $full_encode = false) {
120 | return new self($tweet, $full_encode);
121 | }
122 |
123 | /**
124 | * Reads in a tweet to be parsed and converted to contain links.
125 | *
126 | * As the intent is to produce links and output the modified tweet to the
127 | * user, we take this opportunity to ensure that we escape user input.
128 | *
129 | * @see htmlspecialchars()
130 | *
131 | * @param string $tweet The tweet to be converted.
132 | * @param bool $escape Whether to escape the tweet (default: true).
133 | * @param bool $full_encode Whether to encode all special characters.
134 | */
135 | public function __construct($tweet, $escape = true, $full_encode = false) {
136 | if ($escape) {
137 | if ($full_encode) {
138 | parent::__construct(htmlentities($tweet, ENT_QUOTES, 'UTF-8', false));
139 | } else {
140 | parent::__construct(htmlspecialchars($tweet, ENT_QUOTES, 'UTF-8', false));
141 | }
142 | } else {
143 | parent::__construct($tweet);
144 | }
145 | }
146 |
147 | /**
148 | * CSS class for auto-linked URLs.
149 | *
150 | * @return string CSS class for URL links.
151 | */
152 | public function getURLClass() {
153 | return $this->class_url;
154 | }
155 |
156 | /**
157 | * CSS class for auto-linked URLs.
158 | *
159 | * @param string $v CSS class for URL links.
160 | *
161 | * @return Twitter_Autolink Fluid method chaining.
162 | */
163 | public function setURLClass($v) {
164 | $this->class_url = trim($v);
165 | return $this;
166 | }
167 |
168 | /**
169 | * CSS class for auto-linked username URLs.
170 | *
171 | * @return string CSS class for username links.
172 | */
173 | public function getUsernameClass() {
174 | return $this->class_user;
175 | }
176 |
177 | /**
178 | * CSS class for auto-linked username URLs.
179 | *
180 | * @param string $v CSS class for username links.
181 | *
182 | * @return Twitter_Autolink Fluid method chaining.
183 | */
184 | public function setUsernameClass($v) {
185 | $this->class_user = trim($v);
186 | return $this;
187 | }
188 |
189 | /**
190 | * CSS class for auto-linked username/list URLs.
191 | *
192 | * @return string CSS class for username/list links.
193 | */
194 | public function getListClass() {
195 | return $this->class_list;
196 | }
197 |
198 | /**
199 | * CSS class for auto-linked username/list URLs.
200 | *
201 | * @param string $v CSS class for username/list links.
202 | *
203 | * @return Twitter_Autolink Fluid method chaining.
204 | */
205 | public function setListClass($v) {
206 | $this->class_list = trim($v);
207 | return $this;
208 | }
209 |
210 | /**
211 | * CSS class for auto-linked hashtag URLs.
212 | *
213 | * @return string CSS class for hashtag links.
214 | */
215 | public function getHashtagClass() {
216 | return $this->class_hash;
217 | }
218 |
219 | /**
220 | * CSS class for auto-linked hashtag URLs.
221 | *
222 | * @param string $v CSS class for hashtag links.
223 | *
224 | * @return Twitter_Autolink Fluid method chaining.
225 | */
226 | public function setHashtagClass($v) {
227 | $this->class_hash = trim($v);
228 | return $this;
229 | }
230 |
231 | /**
232 | * Whether to include the value 'nofollow' in the 'rel' attribute.
233 | *
234 | * @return bool Whether to add 'nofollow' to the 'rel' attribute.
235 | */
236 | public function getNoFollow() {
237 | return $this->nofollow;
238 | }
239 |
240 | /**
241 | * Whether to include the value 'nofollow' in the 'rel' attribute.
242 | *
243 | * @param bool $v The value to add to the 'target' attribute.
244 | *
245 | * @return Twitter_Autolink Fluid method chaining.
246 | */
247 | public function setNoFollow($v) {
248 | $this->nofollow = $v;
249 | return $this;
250 | }
251 |
252 | /**
253 | * Whether to include the value 'external' in the 'rel' attribute.
254 | *
255 | * Often this is used to be matched on in JavaScript for dynamically adding
256 | * the 'target' attribute which is deprecated in HTML 4.01. In HTML 5 it has
257 | * been undeprecated and thus the 'target' attribute can be used. If this is
258 | * set to false then the 'target' attribute will be output.
259 | *
260 | * @return bool Whether to add 'external' to the 'rel' attribute.
261 | */
262 | public function getExternal() {
263 | return $this->external;
264 | }
265 |
266 | /**
267 | * Whether to include the value 'external' in the 'rel' attribute.
268 | *
269 | * Often this is used to be matched on in JavaScript for dynamically adding
270 | * the 'target' attribute which is deprecated in HTML 4.01. In HTML 5 it has
271 | * been undeprecated and thus the 'target' attribute can be used. If this is
272 | * set to false then the 'target' attribute will be output.
273 | *
274 | * @param bool $v The value to add to the 'target' attribute.
275 | *
276 | * @return Twitter_Autolink Fluid method chaining.
277 | */
278 | public function setExternal($v) {
279 | $this->external = $v;
280 | return $this;
281 | }
282 |
283 | /**
284 | * The scope to open the link in.
285 | *
286 | * Support for the 'target' attribute was deprecated in HTML 4.01 but has
287 | * since been reinstated in HTML 5. To output the 'target' attribute you
288 | * must disable the adding of the string 'external' to the 'rel' attribute.
289 | *
290 | * @return string The value to add to the 'target' attribute.
291 | */
292 | public function getTarget() {
293 | return $this->target;
294 | }
295 |
296 | /**
297 | * The scope to open the link in.
298 | *
299 | * Support for the 'target' attribute was deprecated in HTML 4.01 but has
300 | * since been reinstated in HTML 5. To output the 'target' attribute you
301 | * must disable the adding of the string 'external' to the 'rel' attribute.
302 | *
303 | * @param string $v The value to add to the 'target' attribute.
304 | *
305 | * @return Twitter_Autolink Fluid method chaining.
306 | */
307 | public function setTarget($v) {
308 | $this->target = trim($v);
309 | return $this;
310 | }
311 |
312 | /**
313 | * Adds links to all elements in the tweet.
314 | *
315 | * @return string The modified tweet.
316 | */
317 | public function addLinks() {
318 | $original = $this->tweet;
319 | $this->tweet = $this->addLinksToURLs();
320 | $this->tweet = $this->addLinksToHashtags();
321 | $this->tweet = $this->addLinksToUsernamesAndLists();
322 | $modified = $this->tweet;
323 | $this->tweet = $original;
324 | return $modified;
325 | }
326 |
327 | /**
328 | * Adds links to hashtag elements in the tweet.
329 | *
330 | * @return string The modified tweet.
331 | */
332 | public function addLinksToHashtags() {
333 | return preg_replace_callback(
334 | self::REGEX_HASHTAG,
335 | array($this, '_addLinksToHashtags'),
336 | $this->tweet);
337 | }
338 |
339 | /**
340 | * Adds links to URL elements in the tweet.
341 | *
342 | * @return string The modified tweet.
343 | */
344 | public function addLinksToURLs() {
345 | return preg_replace_callback(
346 | self::$REGEX_VALID_URL,
347 | array($this, '_addLinksToURLs'),
348 | $this->tweet);
349 | }
350 |
351 | /**
352 | * Adds links to username/list elements in the tweet.
353 | *
354 | * @return string The modified tweet.
355 | */
356 | public function addLinksToUsernamesAndLists() {
357 | return preg_replace_callback(
358 | self::REGEX_USERNAME_LIST,
359 | array($this, '_addLinksToUsernamesAndLists'),
360 | $this->tweet);
361 | }
362 |
363 | /**
364 | * Wraps a tweet element in an HTML anchor tag using the provided URL.
365 | *
366 | * This is a helper function to perform the generation of the link.
367 | *
368 | * @param string $url The URL to use as the href.
369 | * @param string $class The CSS class(es) to apply (space separated).
370 | * @param string $element The tweet element to wrap.
371 | *
372 | * @return string The tweet element with a link applied.
373 | */
374 | protected function wrap($url, $class, $element) {
375 | $link = 'external) $rel[] = 'external';
380 | if ($this->nofollow) $rel[] = 'nofollow';
381 | if (!empty($rel)) $link .= ' rel="'.implode(' ', $rel).'"';
382 | if ($this->target) $link .= ' target="'.$this->target.'"';
383 | $link .= '>'.$element.'';
384 | return $link;
385 | }
386 |
387 | /**
388 | * Callback used by the method that adds links to hashtags.
389 | *
390 | * @see addLinksToHashtags()
391 | *
392 | * @param array $matches The regular expression matches.
393 | *
394 | * @return string The link-wrapped hashtag.
395 | */
396 | protected function _addLinksToHashtags($matches) {
397 | $replacement = $matches[1];
398 | $element = $matches[2] . $matches[3];
399 | $url = $this->url_base_hash . $matches[3];
400 | $replacement .= $this->wrap($url, $this->class_hash, $element);
401 | return $replacement;
402 | }
403 |
404 | /**
405 | * Callback used by the method that adds links to URLs.
406 | *
407 | * @see addLinksToURLs()
408 | *
409 | * @param array $matches The regular expression matches.
410 | *
411 | * @return string The link-wrapped URL.
412 | */
413 | protected function _addLinksToURLs($matches) {
414 | list($all, $before, $url, $protocol, $domain, $path, $query) = array_pad($matches, 7, '');
415 | $url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8', false);
416 | if (!$protocol) return $all;
417 | return $before . $this->wrap($url, $this->class_url, $url);
418 | }
419 |
420 | /**
421 | * Callback used by the method that adds links to username/list pairs.
422 | *
423 | * @see addLinksToUsernamesAndLists()
424 | *
425 | * @param array $matches The regular expression matches.
426 | *
427 | * @return string The link-wrapped username/list pair.
428 | */
429 | protected function _addLinksToUsernamesAndLists($matches) {
430 | list($all, $before, $at, $username, $slash_listname, $after) = array_pad($matches, 6, '');
431 | # If $after is not empty, there is an invalid character.
432 | if (!empty($after)) return $all;
433 | if (!empty($slash_listname)) {
434 | # Replace the list and username
435 | $element = $username . substr($slash_listname, 0, 26);
436 | $class = $this->class_list;
437 | $url = $this->url_base_list . $element;
438 | $postfix = substr($slash_listname, 26);
439 | } else {
440 | # Replace the username
441 | $element = $username;
442 | $class = $this->class_user;
443 | $url = $this->url_base_user . $element;
444 | $postfix = '';
445 | }
446 | return $before . $this->wrap($url, $class, $at . $element) . $postfix . $after;
447 | }
448 |
449 | }
450 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/lib/Twitter/Extractor.php:
--------------------------------------------------------------------------------
1 |
4 | * @author Nick Pope
5 | * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
6 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
7 | * @package Twitter
8 | */
9 |
10 | require_once 'Regex.php';
11 |
12 | /**
13 | * Twitter Extractor Class
14 | *
15 | * Parses tweets and extracts URLs, usernames, username/list pairs and
16 | * hashtags.
17 | *
18 | * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
19 | * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
20 | * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
21 | *
22 | * @author Mike Cochrane
23 | * @author Nick Pope
24 | * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
25 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
26 | * @package Twitter
27 | */
28 | class Twitter_Extractor extends Twitter_Regex {
29 |
30 | /**
31 | * Provides fluent method chaining.
32 | *
33 | * @param string $tweet The tweet to be converted.
34 | *
35 | * @see __construct()
36 | *
37 | * @return Twitter_Extractor
38 | */
39 | public static function create($tweet) {
40 | return new self($tweet);
41 | }
42 |
43 | /**
44 | * Reads in a tweet to be parsed and extracts elements from it.
45 | *
46 | * Extracts various parts of a tweet including URLs, usernames, hashtags...
47 | *
48 | * @param string $tweet The tweet to extract.
49 | */
50 | public function __construct($tweet) {
51 | parent::__construct($tweet);
52 | }
53 |
54 | /**
55 | * Extracts all parts of a tweet and returns an associative array containing
56 | * the extracted elements.
57 | *
58 | * @return array The elements in the tweet.
59 | */
60 | public function extract() {
61 | return array(
62 | 'hashtags' => $this->extractHashtags(),
63 | 'urls' => $this->extractURLs(),
64 | 'mentions' => $this->extractMentionedUsernames(),
65 | 'replyto' => $this->extractRepliedUsernames(),
66 | 'hashtags_with_indices' => $this->extractHashtagsWithIndices(),
67 | 'urls_with_indices' => $this->extractURLsWithIndices(),
68 | 'mentions_with_indices' => $this->extractMentionedUsernamesWithIndices(),
69 | );
70 | }
71 |
72 | /**
73 | * Extracts all the hashtags from the tweet.
74 | *
75 | * @return array The hashtag elements in the tweet.
76 | */
77 | public function extractHashtags() {
78 | preg_match_all(self::REGEX_HASHTAG, $this->tweet, $matches);
79 | return $matches[3];
80 | }
81 |
82 | /**
83 | * Extracts all the URLs from the tweet.
84 | *
85 | * @return array The URL elements in the tweet.
86 | */
87 | public function extractURLs() {
88 | preg_match_all(self::$REGEX_VALID_URL, $this->tweet, $matches);
89 | list($all, $before, $url, $protocol, $domain, $path, $query) = array_pad($matches, 7, '');
90 | return $url;
91 | }
92 |
93 | /**
94 | * Extract all the usernames from the tweet.
95 | *
96 | * A mention is an occurrence of a username anywhere in a tweet.
97 | *
98 | * @return array The usernames elements in the tweet.
99 | */
100 | public function extractMentionedUsernames() {
101 | preg_match_all(self::REGEX_USERNAME_MENTION, $this->tweet, $matches);
102 | list($all, $before, $username, $after) = array_pad($matches, 4, '');
103 | $usernames = array();
104 | for ($i = 0; $i < count($username); $i ++) {
105 | # If $after is not empty, there is an invalid character.
106 | if (!empty($after[$i])) continue;
107 | array_push($usernames, $username[$i]);
108 | }
109 | return $usernames;
110 | }
111 |
112 | /**
113 | * Extract all the usernames replied to from the tweet.
114 | *
115 | * A reply is an occurrence of a username at the beginning of a tweet.
116 | *
117 | * @return array The usernames replied to in a tweet.
118 | */
119 | public function extractRepliedUsernames() {
120 | preg_match(self::$REGEX_REPLY_USERNAME, $this->tweet, $matches);
121 | return isset($matches[2]) ? $matches[2] : '';
122 | }
123 |
124 | /**
125 | * Extracts all the hashtags and the indices they occur at from the tweet.
126 | *
127 | * @return array The hashtag elements in the tweet.
128 | */
129 | public function extractHashtagsWithIndices() {
130 | preg_match_all(self::REGEX_HASHTAG, $this->tweet, $matches, PREG_OFFSET_CAPTURE);
131 | $m = &$matches[3];
132 | for ($i = 0; $i < count($m); $i++) {
133 | $m[$i] = array_combine(array('hashtag', 'indices'), $m[$i]);
134 | # XXX: Fix for PREG_OFFSET_CAPTURE returning byte offsets...
135 | $start = mb_strlen(substr($this->tweet, 0, $matches[1][$i][1]));
136 | $start += mb_strlen($matches[1][$i][0]);
137 | $length = mb_strlen($m[$i]['hashtag']);
138 | $m[$i]['indices'] = array($start, $start + $length + 1);
139 | }
140 | return $m;
141 | }
142 |
143 | /**
144 | * Extracts all the URLs and the indices they occur at from the tweet.
145 | *
146 | * @return array The URLs elements in the tweet.
147 | */
148 | public function extractURLsWithIndices() {
149 | preg_match_all(self::$REGEX_VALID_URL, $this->tweet, $matches, PREG_OFFSET_CAPTURE);
150 | $m = &$matches[2];
151 | for ($i = 0; $i < count($m); $i++) {
152 | $m[$i] = array_combine(array('url', 'indices'), $m[$i]);
153 | # XXX: Fix for PREG_OFFSET_CAPTURE returning byte offsets...
154 | $start = mb_strlen(substr($this->tweet, 0, $matches[1][$i][1]));
155 | $start += mb_strlen($matches[1][$i][0]);
156 | $length = mb_strlen($m[$i]['url']);
157 | $m[$i]['indices'] = array($start, $start + $length);
158 | }
159 | return $m;
160 | }
161 |
162 | /**
163 | * Extracts all the usernames and the indices they occur at from the tweet.
164 | *
165 | * @return array The username elements in the tweet.
166 | */
167 | public function extractMentionedUsernamesWithIndices() {
168 | preg_match_all(self::REGEX_USERNAME_MENTION, $this->tweet, $matches, PREG_OFFSET_CAPTURE);
169 | $m = &$matches[2];
170 | for ($i = 0; $i < count($m); $i++) {
171 | $m[$i] = array_combine(array('screen_name', 'indices'), $m[$i]);
172 | # XXX: Fix for PREG_OFFSET_CAPTURE returning byte offsets...
173 | $start = mb_strlen(substr($this->tweet, 0, $matches[1][$i][1]));
174 | $start += mb_strlen($matches[1][$i][0]);
175 | $length = mb_strlen($m[$i]['screen_name']);
176 | $m[$i]['indices'] = array($start, $start + $length + 1);
177 | }
178 | return $m;
179 | }
180 |
181 | }
182 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/lib/Twitter/HitHighlighter.php:
--------------------------------------------------------------------------------
1 |
4 | * @copyright Copyright © 2010, Nick Pope
5 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
6 | * @package Twitter
7 | */
8 |
9 | require_once 'Regex.php';
10 |
11 | /**
12 | * Twitter HitHighlighter Class
13 | *
14 | * Performs "hit highlighting" on tweets that have been auto-linked already.
15 | * Useful with the results returned from the search API.
16 | *
17 | * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
18 | * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
19 | * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
20 | *
21 | * @author Nick Pope
22 | * @copyright Copyright © 2010, Nick Pope
23 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
24 | * @package Twitter
25 | */
26 | class Twitter_HitHighlighter extends Twitter_Regex {
27 |
28 | /**
29 | * The tag to surround hits with.
30 | *
31 | * @var string
32 | */
33 | protected $tag = 'em';
34 |
35 | /**
36 | * Provides fluent method chaining.
37 | *
38 | * @param string $tweet The tweet to be hit highlighted.
39 | * @param bool $full_encode Whether to encode all special characters.
40 | *
41 | * @see __construct()
42 | *
43 | * @return Twitter_HitHighlighter
44 | */
45 | public static function create($tweet, $full_encode = false) {
46 | return new self($tweet, $full_encode);
47 | }
48 |
49 | /**
50 | * Reads in a tweet to be parsed and hit highlighted.
51 | *
52 | * We take this opportunity to ensure that we escape user input.
53 | *
54 | * @see htmlspecialchars()
55 | *
56 | * @param string $tweet The tweet to be hit highlighted.
57 | * @param bool $escape Whether to escape the tweet (default: true).
58 | * @param bool $full_encode Whether to encode all special characters.
59 | */
60 | public function __construct($tweet, $escape = true, $full_encode = false) {
61 | if ($escape) {
62 | if ($full_encode) {
63 | parent::__construct(htmlentities($tweet, ENT_QUOTES, 'UTF-8', false));
64 | } else {
65 | parent::__construct(htmlspecialchars($tweet, ENT_QUOTES, 'UTF-8', false));
66 | }
67 | } else {
68 | parent::__construct($tweet);
69 | }
70 | }
71 |
72 | /**
73 | * Set the highlighting tag to surround hits with. The default tag is 'em'.
74 | *
75 | * @return string The tag name.
76 | */
77 | public function getTag() {
78 | return $this->tag;
79 | }
80 |
81 | /**
82 | * Set the highlighting tag to surround hits with. The default tag is 'em'.
83 | *
84 | * @param string $v The tag name.
85 | *
86 | * @return Twitter_HitHighlighter Fluid method chaining.
87 | */
88 | public function setTag($v) {
89 | $this->tag = $v;
90 | return $this;
91 | }
92 |
93 | /**
94 | * Hit highlights the tweet.
95 | *
96 | * @param array $hits An array containing the start and end index pairs
97 | * for the highlighting.
98 | *
99 | * @return string The hit highlighted tweet.
100 | */
101 | public function addHitHighlighting(array $hits) {
102 | if (empty($hits)) return $this->tweet;
103 | $tweet = '';
104 | $tags = array('<'.$this->tag.'>', ''.$this->tag.'>');
105 | # Check whether we can simply replace or whether we need to chunk...
106 | if (strpos($this->tweet, '<') === false) {
107 | $ti = 0; // tag increment (for added tags)
108 | $tweet = $this->tweet;
109 | foreach ($hits as $hit) {
110 | $tweet = self::mb_substr_replace($tweet, $tags[0], $hit[0] + $ti, 0);
111 | $ti += mb_strlen($tags[0]);
112 | $tweet = self::mb_substr_replace($tweet, $tags[1], $hit[1] + $ti, 0);
113 | $ti += mb_strlen($tags[1]);
114 | }
115 | } else {
116 | $chunks = preg_split('/[<>]/iu', $this->tweet);
117 | $chunk = $chunks[0];
118 | $chunk_index = 0;
119 | $chunk_cursor = 0;
120 | $offset = 0;
121 | $start_in_chunk = false;
122 | # Flatten the multidimensional hits array:
123 | $hits_flat = array();
124 | foreach ($hits as $hit) $hits_flat = array_merge($hits_flat, $hit);
125 | # Loop over the hit indices:
126 | for ($index = 0; $index < count($hits_flat); $index++) {
127 | $hit = $hits_flat[$index];
128 | $tag = $tags[$index % 2];
129 | $placed = false;
130 | while ($chunk !== null && $hit >= ($i = $offset + mb_strlen($chunk))) {
131 | $tweet .= mb_substr($chunk, $chunk_cursor);
132 | if ($start_in_chunk && $hit === $i) {
133 | $tweet .= $tag;
134 | $placed = true;
135 | }
136 | if (isset($chunks[$chunk_index+1])) $tweet .= '<' . $chunks[$chunk_index+1] . '>';
137 | $offset += mb_strlen($chunk);
138 | $chunk_cursor = 0;
139 | $chunk_index += 2;
140 | $chunk = (isset($chunks[$chunk_index]) ? $chunks[$chunk_index] : null);
141 | $start_in_chunk = false;
142 | }
143 | if (!$placed && $chunk !== null) {
144 | $hit_spot = $hit - $offset;
145 | $tweet .= mb_substr($chunk, $chunk_cursor, $hit_spot - $chunk_cursor) . $tag;
146 | $chunk_cursor = $hit_spot;
147 | $start_in_chunk = ($index % 2 === 0);
148 | $placed = true;
149 | }
150 | # Ultimate fallback - hits that run off the end get a closing tag:
151 | if (!$placed) $tweet .= $tag;
152 | }
153 | if ($chunk !== null) {
154 | if ($chunk_cursor < mb_strlen($chunk)) {
155 | $tweet .= mb_substr($chunk, $chunk_cursor);
156 | }
157 | for ($index = $chunk_index + 1; $index < count($chunks); $index++) {
158 | $tweet .= ($index % 2 === 0 ? $chunks[$index] : '<' . $chunks[$index] . '>');
159 | }
160 | }
161 | }
162 | return $tweet;
163 | }
164 |
165 | /**
166 | * A multibyte-aware substring replacement function.
167 | *
168 | * @param string $string The string to modify.
169 | * @param string $replacement The replacement string.
170 | * @param int $start The start of the replacement.
171 | * @param int $length The number of characters to replace.
172 | * @param string $encoding The encoding of the string.
173 | *
174 | * @return string The modified string.
175 | *
176 | * @see http://www.php.net/manual/en/function.substr-replace.php#90146
177 | */
178 | protected static function mb_substr_replace($string, $replacement, $start, $length = null, $encoding = null) {
179 | if (extension_loaded('mbstring') === true) {
180 | $string_length = (is_null($encoding) === true) ? mb_strlen($string) : mb_strlen($string, $encoding);
181 | if ($start < 0) {
182 | $start = max(0, $string_length + $start);
183 | } else if ($start > $string_length) {
184 | $start = $string_length;
185 | }
186 | if ($length < 0) {
187 | $length = max(0, $string_length - $start + $length);
188 | } else if ((is_null($length) === true) || ($length > $string_length)) {
189 | $length = $string_length;
190 | }
191 | if (($start + $length) > $string_length) {
192 | $length = $string_length - $start;
193 | }
194 | if (is_null($encoding) === true) {
195 | return mb_substr($string, 0, $start) . $replacement . mb_substr($string, $start + $length, $string_length - $start - $length);
196 | }
197 | return mb_substr($string, 0, $start, $encoding) . $replacement . mb_substr($string, $start + $length, $string_length - $start - $length, $encoding);
198 | }
199 | return (is_null($length) === true) ? substr_replace($string, $replacement, $start) : substr_replace($string, $replacement, $start, $length);
200 | }
201 |
202 | }
203 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/lib/Twitter/Regex.php:
--------------------------------------------------------------------------------
1 |
4 | * @author Nick Pope
5 | * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
6 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
7 | * @package Twitter
8 | */
9 |
10 | /**
11 | * Twitter Regex Abstract Class
12 | *
13 | * Used by subclasses that need to parse tweets.
14 | *
15 | * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
16 | * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
17 | * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
18 | *
19 | * @author Mike Cochrane
20 | * @author Nick Pope
21 | * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
22 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
23 | * @package Twitter
24 | */
25 | abstract class Twitter_Regex {
26 |
27 | /**
28 | * Expression to at sign characters
29 | *
30 | * @var string
31 | */
32 | const REGEX_AT_SIGNS = '[@@]';
33 |
34 | /**
35 | * Expression to match characters that may come before a URL.
36 | *
37 | * @var string
38 | */
39 | const REGEX_URL_CHARS_BEFORE = '(?:[^-\\/"\':!=a-z0-9_@@]|^|\\:)';
40 |
41 | /**
42 | * Expression to match the domain portion of a URL.
43 | *
44 | * @var string
45 | */
46 | const REGEX_URL_DOMAIN = '(?:[^\\p{P}\\p{Lo}\\s][\\.-](?=[^\\p{P}\\p{Lo}\\s])|[^\\p{P}\\p{Lo}\\s])+\\.[a-z]{2,}(?::[0-9]+)?';
47 |
48 | /**
49 | * Expression to match characters that may come in the URL path.
50 | *
51 | * @var string
52 | */
53 | const REGEX_URL_CHARS_PATH = '(?:(?:\\([a-z0-9!\\*\';:=\\+\\$\\/%#\\[\\]\\-_,~]+\\))|@[a-z0-9!\\*\';:=\\+\\$\\/%#\\[\\]\\-_,~]+\\/|[\\.\\,]?(?:[a-z0-9!\\*\';:=\\+\\$\\/%#\\[\\]\\-_~]|,(?!\s)))';
54 |
55 | /**
56 | * Expression to match characters that may come at the end of the URL path.
57 | *
58 | * @var string
59 | */
60 | const REGEX_URL_CHARS_PATH_END = '[a-z0-9=#\\/]';
61 |
62 | /**
63 | * Expression to match characters that may come in the URL query string.
64 | *
65 | * @var string
66 | */
67 | const REGEX_URL_CHARS_QUERY = '[a-z0-9!\\*\'\\(\\);:&=\\+\\$\\/%#\\[\\]\\-_\\.,~]';
68 |
69 | /**
70 | * Expression to match characters that may come at the end of the URL query
71 | * string.
72 | *
73 | * @var string
74 | */
75 | const REGEX_URL_CHARS_QUERY_END = '[a-z0-9_&=#\\/]';
76 |
77 | /**
78 | * Expression to match a username followed by a list.
79 | *
80 | * @var string
81 | */
82 | const REGEX_USERNAME_LIST = '/([^a-z0-9_\/]|^|RT:?)([@@]+)([a-z0-9_]{1,20})(\/[a-z][-_a-z0-9\x80-\xFF]{0,24})?([@@\xC0-\xD6\xD8-\xF6\xF8-\xFF]?)/iu';
83 |
84 | /**
85 | * Expression to match a username mentioned anywhere in a tweet.
86 | *
87 | * @var string
88 | */
89 | const REGEX_USERNAME_MENTION = '/(^|[^a-z0-9_])[@@]([a-z0-9_]{1,20})([@@\xC0-\xD6\xD8-\xF6\xF8-\xFF]?)/iu';
90 |
91 | /**
92 | * Expression to match a hashtag.
93 | *
94 | * @var string
95 | */
96 | const REGEX_HASHTAG = '/(^|[^0-9A-Z&\/\?]+)([##]+)([0-9A-Z_]*[A-Z_]+[a-z0-9_üÀ-ÖØ-öø-ÿ]*)/iu';
97 |
98 | /**
99 | * Expression to match whitespace.
100 | *
101 | * Single byte whitespace characters
102 | * 0x0009-0x000D White_Space # Cc # ..
103 | * 0x0020 White_Space # Zs # SPACE
104 | * 0x0085 White_Space # Cc #
105 | * 0x00A0 White_Space # Zs # NO-BREAK SPACE
106 | * Multi byte whitespace characters
107 | * 0x1680 White_Space # Zs # OGHAM SPACE MARK
108 | * 0x180E White_Space # Zs # MONGOLIAN VOWEL SEPARATOR
109 | * 0x2000-0x200A White_Space # Zs # EN QUAD..HAIR SPACE
110 | * 0x2028 White_Space # Zl # LINE SEPARATOR
111 | * 0x2029 White_Space # Zp # PARAGRAPH SEPARATOR
112 | * 0x202F White_Space # Zs # NARROW NO-BREAK SPACE
113 | * 0x205F White_Space # Zs # MEDIUM MATHEMATICAL SPACE
114 | * 0x3000 White_Space # Zs # IDEOGRAPHIC SPACE
115 | *
116 | * @var string
117 | */
118 | const REGEX_WHITESPACE = '[\x09-\x0D\x20\x85\xA0]|\xe1\x9a\x80|\xe1\xa0\x8e|\xe2\x80[\x80-\x8a,\xa8,\xa9,\xaf\xdf]|\xe3\x80\x80';
119 |
120 | /**
121 | * Contains the complete valid URL pattern string.
122 | *
123 | * This should be generated the first time the constructor is called.
124 | *
125 | * @var string The regex pattern for a valid URL.
126 | */
127 | protected static $REGEX_VALID_URL = null;
128 |
129 | /**
130 | * Contains the reply username pattern string.
131 | *
132 | * This should be generated the first time the constructor is called.
133 | *
134 | * @var string The regex pattern for a reply username.
135 | */
136 | protected static $REGEX_REPLY_USERNAME = null;
137 |
138 | /**
139 | * The tweet to be used in parsing. This should be populated by the
140 | * constructor of all subclasses.
141 | *
142 | * @var string
143 | */
144 | protected $tweet = '';
145 |
146 | /**
147 | * This constructor is used to populate some variables.
148 | *
149 | * @param string $tweet The tweet to parse.
150 | */
151 | protected function __construct($tweet) {
152 | if (is_null(self::$REGEX_VALID_URL)) {
153 | self::$REGEX_VALID_URL = '/(?:' # $1 Complete match (preg_match already matches everything.)
154 | . '('.self::REGEX_URL_CHARS_BEFORE.')' # $2 Preceding character
155 | . '(' # $3 Complete URL
156 | . '(https?:\\/\\/)' # $4 Protocol (or www)
157 | . '('.self::REGEX_URL_DOMAIN.')' # $5 Domain(s) (and port)
158 | . '(\\/'.self::REGEX_URL_CHARS_PATH.'*' # $6 URL Path
159 | . self::REGEX_URL_CHARS_PATH_END.'?)?'
160 | . '(\\?'.self::REGEX_URL_CHARS_QUERY.'*' # $7 Query String
161 | . self::REGEX_URL_CHARS_QUERY_END.')?'
162 | . ')'
163 | . ')/iux';
164 | }
165 | if (is_null(self::$REGEX_REPLY_USERNAME)) {
166 | self::$REGEX_REPLY_USERNAME = '/^('.self::REGEX_WHITESPACE.')*[@@]([a-zA-Z0-9_]{1,20})/';
167 | }
168 | $this->tweet = $tweet;
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/phpunit.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 | tests/Twitter
12 |
13 |
14 |
15 |
16 |
17 | lib/Twitter
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/tests/Twitter/AutolinkTest.php:
--------------------------------------------------------------------------------
1 |
4 | * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
5 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
6 | * @package Twitter
7 | */
8 |
9 | /**
10 | * Twitter Autolink Class Unit Tests
11 | *
12 | * @author Nick Pope
13 | * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
14 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
15 | * @package Twitter
16 | */
17 | class Twitter_AutolinkTest extends PHPUnit_Framework_TestCase {
18 |
19 | /**
20 | * A helper function for providers.
21 | *
22 | * @param string $test The test to fetch data for.
23 | *
24 | * @return array The test data to provide.
25 | */
26 | protected function providerHelper($test) {
27 | $data = Spyc::YAMLLoad(DATA.'/autolink.yml');
28 | return isset($data['tests'][$test]) ? $data['tests'][$test] : array();
29 | }
30 |
31 | /**
32 | * @dataProvider addLinksToUsernamesProvider
33 | */
34 | public function testAddLinksToUsernames($description, $text, $expected) {
35 | $linked = Twitter_Autolink::create($text, false)
36 | ->setNoFollow(false)->setExternal(false)->setTarget('')
37 | ->setUsernameClass('tweet-url username')
38 | ->setListClass('tweet-url list-slug')
39 | ->setHashtagClass('tweet-url hashtag')
40 | ->setURLClass('')
41 | ->addLinksToUsernamesAndLists();
42 | $this->assertSame($expected, $linked, $description);
43 | }
44 |
45 | /**
46 | *
47 | */
48 | public function addLinksToUsernamesProvider() {
49 | return $this->providerHelper('usernames');
50 | }
51 |
52 | /**
53 | * @dataProvider addLinksToListsProvider
54 | */
55 | public function testAddLinksToLists($description, $text, $expected) {
56 | $linked = Twitter_Autolink::create($text, false)
57 | ->setNoFollow(false)->setExternal(false)->setTarget('')
58 | ->setUsernameClass('tweet-url username')
59 | ->setListClass('tweet-url list-slug')
60 | ->setHashtagClass('tweet-url hashtag')
61 | ->setURLClass('')
62 | ->addLinksToUsernamesAndLists();
63 | $this->assertSame($expected, $linked, $description);
64 | }
65 |
66 | /**
67 | *
68 | */
69 | public function addLinksToListsProvider() {
70 | return $this->providerHelper('lists');
71 | }
72 |
73 | /**
74 | * @dataProvider addLinksToHashtagsProvider
75 | */
76 | public function testAddLinksToHashtags($description, $text, $expected) {
77 | $linked = Twitter_Autolink::create($text, false)
78 | ->setNoFollow(false)->setExternal(false)->setTarget('')
79 | ->setUsernameClass('tweet-url username')
80 | ->setListClass('tweet-url list-slug')
81 | ->setHashtagClass('tweet-url hashtag')
82 | ->setURLClass('')
83 | ->addLinksToHashtags();
84 | # XXX: Need to re-order for hashtag as it is written out differently...
85 | # We use the same wrapping function for adding links for all methods.
86 | $linked = preg_replace(array(
87 | '!([^<]*)!',
88 | '!title="#([^"]+)"!'
89 | ), array(
90 | '$3',
91 | 'title="#$1"'
92 | ), $linked);
93 | $this->assertSame($expected, $linked, $description);
94 | }
95 |
96 | /**
97 | *
98 | */
99 | public function addLinksToHashtagsProvider() {
100 | return $this->providerHelper('hashtags');
101 | }
102 |
103 | /**
104 | * @dataProvider addLinksToURLsProvider
105 | */
106 | public function testAddLinksToURLs($description, $text, $expected) {
107 | $linked = Twitter_Autolink::create($text, false)
108 | ->setNoFollow(false)->setExternal(false)->setTarget('')
109 | ->setUsernameClass('tweet-url username')
110 | ->setListClass('tweet-url list-slug')
111 | ->setHashtagClass('tweet-url hashtag')
112 | ->setURLClass('')
113 | ->addLinksToURLs();
114 | $this->assertSame($expected, $linked, $description);
115 | }
116 |
117 | /**
118 | *
119 | */
120 | public function addLinksToURLsProvider() {
121 | return $this->providerHelper('urls');
122 | }
123 |
124 | /**
125 | * @dataProvider addLinksProvider
126 | */
127 | public function testAddLinks($description, $text, $expected) {
128 | $linked = Twitter_Autolink::create($text, false)
129 | ->setNoFollow(false)->setExternal(false)->setTarget('')
130 | ->setUsernameClass('tweet-url username')
131 | ->setListClass('tweet-url list-slug')
132 | ->setHashtagClass('tweet-url hashtag')
133 | ->setURLClass('')
134 | ->addLinks();
135 | $this->assertSame($expected, $linked, $description);
136 | }
137 |
138 | /**
139 | *
140 | */
141 | public function addLinksProvider() {
142 | return $this->providerHelper('all');
143 | }
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/tests/Twitter/ExtractorTest.php:
--------------------------------------------------------------------------------
1 |
4 | * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
5 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
6 | * @package Twitter
7 | */
8 |
9 | /**
10 | * Twitter Extractor Class Unit Tests
11 | *
12 | * @author Nick Pope
13 | * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
14 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
15 | * @package Twitter
16 | */
17 | class Twitter_ExtractorTest extends PHPUnit_Framework_TestCase {
18 |
19 | /**
20 | * A helper function for providers.
21 | *
22 | * @param string $test The test to fetch data for.
23 | *
24 | * @return array The test data to provide.
25 | */
26 | protected function providerHelper($test) {
27 | $data = Spyc::YAMLLoad(DATA.'/extract.yml');
28 | return isset($data['tests'][$test]) ? $data['tests'][$test] : array();
29 | }
30 |
31 | /**
32 | * @dataProvider extractMentionedUsernamesProvider
33 | */
34 | public function testExtractMentionedUsernames($description, $text, $expected) {
35 | $extracted = Twitter_Extractor::create($text)->extractMentionedUsernames();
36 | $this->assertSame($expected, $extracted, $description);
37 | }
38 |
39 | /**
40 | *
41 | */
42 | public function extractMentionedUsernamesProvider() {
43 | return $this->providerHelper('mentions');
44 | }
45 |
46 | /**
47 | * @dataProvider extractRepliedUsernamesProvider
48 | */
49 | public function testExtractRepliedUsernames($description, $text, $expected) {
50 | $extracted = Twitter_Extractor::create($text)->extractRepliedUsernames();
51 | $this->assertSame($expected, $extracted, $description);
52 | }
53 |
54 | /**
55 | *
56 | */
57 | public function extractRepliedUsernamesProvider() {
58 | return $this->providerHelper('replies');
59 | }
60 |
61 | /**
62 | * @dataProvider extractURLsProvider
63 | */
64 | public function testExtractURLs($description, $text, $expected) {
65 | $extracted = Twitter_Extractor::create($text)->extractURLs();
66 | $this->assertSame($expected, $extracted, $description);
67 | }
68 |
69 | /**
70 | *
71 | */
72 | public function extractURLsProvider() {
73 | return $this->providerHelper('urls');
74 | }
75 |
76 | /**
77 | * @dataProvider extractHashtagsProvider
78 | */
79 | public function testExtractHashtags($description, $text, $expected) {
80 | $extracted = Twitter_Extractor::create($text)->extractHashtags();
81 | $this->assertSame($expected, $extracted, $description);
82 | }
83 |
84 | /**
85 | *
86 | */
87 | public function extractHashtagsProvider() {
88 | return $this->providerHelper('hashtags');
89 | }
90 |
91 | /**
92 | * @dataProvider extractHashtagsWithIndicesProvider
93 | */
94 | public function testExtractHashtagsWithIndices($description, $text, $expected) {
95 | $extracted = Twitter_Extractor::create($text)->extractHashtagsWithIndices();
96 | $this->assertSame($expected, $extracted, $description);
97 | }
98 |
99 | /**
100 | *
101 | */
102 | public function extractHashtagsWithIndicesProvider() {
103 | return $this->providerHelper('hashtags_with_indices');
104 | }
105 |
106 | /**
107 | * @dataProvider extractURLsWithIndicesProvider
108 | */
109 | public function testExtractURLsWithIndices($description, $text, $expected) {
110 | $extracted = Twitter_Extractor::create($text)->extractURLsWithIndices();
111 | $this->assertSame($expected, $extracted, $description);
112 | }
113 |
114 | /**
115 | *
116 | */
117 | public function extractURLsWithIndicesProvider() {
118 | return $this->providerHelper('urls_with_indices');
119 | }
120 |
121 | /**
122 | * @dataProvider extractMentionedUsernamesWithIndicesProvider
123 | */
124 | public function testExtractMentionedUsernamesWithIndices($description, $text, $expected) {
125 | $extracted = Twitter_Extractor::create($text)->extractMentionedUsernamesWithIndices();
126 | $this->assertSame($expected, $extracted, $description);
127 | }
128 |
129 | /**
130 | *
131 | */
132 | public function extractMentionedUsernamesWithIndicesProvider() {
133 | return $this->providerHelper('mentions_with_indices');
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/tests/Twitter/HitHighlighterTest.php:
--------------------------------------------------------------------------------
1 |
4 | * @copyright Copyright © 2010, Nick Pope
5 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
6 | * @package Twitter
7 | */
8 |
9 | /**
10 | * Twitter HitHighlighter Class Unit Tests
11 | *
12 | * @author Nick Pope
13 | * @copyright Copyright © 2010, Nick Pope
14 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
15 | * @package Twitter
16 | */
17 | class Twitter_HitHighlighterTest extends PHPUnit_Framework_TestCase {
18 |
19 | /**
20 | * A helper function for providers.
21 | *
22 | * @param string $test The test to fetch data for.
23 | *
24 | * @return array The test data to provide.
25 | */
26 | protected function providerHelper($test) {
27 | $data = Spyc::YAMLLoad(DATA.'/hit_highlighting.yml');
28 | return isset($data['tests'][$test]) ? $data['tests'][$test] : array();
29 | }
30 |
31 | /**
32 | * @dataProvider addHitHighlightingProvider
33 | */
34 | public function testAddHitHighlighting($description, $text, $hits, $expected) {
35 | $extracted = Twitter_HitHighlighter::create($text)->addHitHighlighting($hits);
36 | $this->assertSame($expected, $extracted, $description);
37 | }
38 |
39 | /**
40 | *
41 | */
42 | public function addHitHighlightingProvider() {
43 | return array_merge($this->providerHelper('plain_text'), $this->providerHelper('with_links'));
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
8 | * @author Nick Pope
9 | * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
10 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
11 | */
12 |
13 | if (!defined('E_DEPRECATED')) define('E_DEPRECATED', 8192);
14 | error_reporting(E_ALL | E_STRICT | E_DEPRECATED);
15 |
16 | $ROOT = dirname(dirname(__FILE__));
17 |
18 | require_once $ROOT.'/lib/Twitter/Autolink.php';
19 | require_once $ROOT.'/lib/Twitter/Extractor.php';
20 | require_once $ROOT.'/lib/Twitter/HitHighlighter.php';
21 |
22 | $browser = (PHP_SAPI != 'cli');
23 |
24 | function print_array(array $a) {
25 | $p = print_r($a, true);
26 | $p = str_replace(' ', ' ', $p);
27 | echo preg_replace(array(
28 | '!^Array\s+\(\s+!',
29 | '!=> Array\s+\(!',
30 | '! (\[\d|\))!',
31 | '!\s+\)\s*$!',
32 | ), array(
33 | ' ', '=> (', '\1', '',
34 | ), $p);
35 | }
36 |
37 | $tweet = 'Tweet mentioning @mikenz and referring to his list @mikeNZ/sports and website http://mikenz.geek.nz #awesome';
38 |
39 | if ($browser) echo <<
41 |
42 |
43 |
44 | Twitter Text (PHP Edition) Library » Examples
45 |
77 |
78 |
79 | EOHTML;
80 |
81 | if ($browser) echo '';
82 | echo 'Twitter Text (PHP Edition) Library » Examples';
83 | if ($browser) echo '
';
84 | else echo PHP_EOL, '============================================', PHP_EOL;
85 | echo PHP_EOL;
86 |
87 | if ($browser) echo '';
88 | echo 'Extraction Examples';
89 | if ($browser) echo '
';
90 | else echo PHP_EOL, '-------------------', PHP_EOL;
91 | echo PHP_EOL;
92 |
93 | $code = <<extract();
98 | print_r(\$data);
99 | EOPHP;
100 | if ($browser) {
101 | echo 'Source
', PHP_EOL;
102 | echo '';
103 | highlight_string($code);
104 | echo '
', PHP_EOL;
105 | } else {
106 | echo 'Source:', PHP_EOL, PHP_EOL;
107 | echo $code;
108 | echo PHP_EOL, PHP_EOL;
109 | }
110 |
111 | $data = Twitter_Extractor::create($tweet)
112 | ->extract();
113 |
114 | if ($browser) {
115 | echo 'Output
', PHP_EOL;
116 | echo '';
117 | print_array($data);
118 | echo '
', PHP_EOL;
119 | } else {
120 | echo 'Output:', PHP_EOL, PHP_EOL;
121 | print_array($data);
122 | echo PHP_EOL, PHP_EOL;
123 | }
124 |
125 | if ($browser) echo '';
126 | echo 'Autolink Examples';
127 | if ($browser) echo '
';
128 | else echo PHP_EOL, '-----------------', PHP_EOL;
129 | echo PHP_EOL;
130 |
131 | $code = <<setNoFollow(false)
136 | ->addLinks();
137 | echo \$html;
138 | EOPHP;
139 | if ($browser) {
140 | echo 'Source
', PHP_EOL;
141 | echo '';
142 | highlight_string($code);
143 | echo '
', PHP_EOL;
144 | } else {
145 | echo 'Source:', PHP_EOL, PHP_EOL;
146 | echo $code;
147 | echo PHP_EOL, PHP_EOL;
148 | }
149 |
150 | $html = Twitter_Autolink::create($tweet)
151 | ->setNoFollow(false)
152 | ->addLinks();
153 |
154 | if ($browser) {
155 | echo 'Markup
', PHP_EOL;
156 | echo '';
157 | echo htmlspecialchars($html, ENT_QUOTES, 'UTF-8', false);
158 | echo '
', PHP_EOL;
159 | } else {
160 | echo 'Markup:', PHP_EOL, PHP_EOL;
161 | echo wordwrap(htmlspecialchars($html, ENT_QUOTES, 'UTF-8', false));
162 | echo PHP_EOL, PHP_EOL;
163 | }
164 |
165 | if ($browser) {
166 | echo 'Output
', PHP_EOL;
167 | echo '';
168 | echo $html;
169 | echo '
', PHP_EOL;
170 | } else {
171 | echo 'Output:', PHP_EOL, PHP_EOL;
172 | echo wordwrap($html);
173 | echo PHP_EOL, PHP_EOL;
174 | }
175 |
176 | if ($browser) echo '';
177 | echo 'Hit Highlighter Examples';
178 | if ($browser) echo '
';
179 | else echo PHP_EOL, '------------------------', PHP_EOL;
180 | echo PHP_EOL;
181 |
182 | $code = <<addHitHighlighting(\$hits);
188 | echo \$html;
189 | EOPHP;
190 | if ($browser) {
191 | echo 'Source
', PHP_EOL;
192 | echo '';
193 | highlight_string($code);
194 | echo '
', PHP_EOL;
195 | } else {
196 | echo 'Source:', PHP_EOL, PHP_EOL;
197 | echo $code;
198 | echo PHP_EOL, PHP_EOL;
199 | }
200 |
201 | $html = Twitter_HitHighlighter::create($tweet)
202 | ->addHitHighlighting(array(array(70, 77), array(101, 108)));
203 |
204 | if ($browser) {
205 | echo 'Markup
', PHP_EOL;
206 | echo '';
207 | echo htmlspecialchars($html, ENT_QUOTES, 'UTF-8', false);
208 | echo '
', PHP_EOL;
209 | } else {
210 | echo 'Markup:', PHP_EOL, PHP_EOL;
211 | echo wordwrap(htmlspecialchars($html, ENT_QUOTES, 'UTF-8', false));
212 | echo PHP_EOL, PHP_EOL;
213 | }
214 |
215 | if ($browser) {
216 | echo 'Output
', PHP_EOL;
217 | echo '';
218 | echo $html;
219 | echo '
', PHP_EOL;
220 | } else {
221 | echo 'Output:', PHP_EOL, PHP_EOL;
222 | echo wordwrap($html);
223 | echo PHP_EOL, PHP_EOL;
224 | }
225 |
226 | if ($browser) echo <<
228 |
229 | EOHTML;
230 |
--------------------------------------------------------------------------------
/lib/twitter-text-php/tests/runtests.php:
--------------------------------------------------------------------------------
1 |
8 | * @author Nick Pope
9 | * @copyright Copyright © 2010, Mike Cochrane, Nick Pope
10 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2.0
11 | */
12 |
13 | require_once dirname(__FILE__).'/bootstrap.php';
14 |
15 | $browser = (PHP_SAPI != 'cli');
16 |
17 | function pretty_format($a) {
18 | return preg_replace(array(
19 | "/\n/", '/ +\[/', '/ +\)/', '/Array +\(/', '/(?
33 |
34 |
35 |
36 | Twitter Text (PHP Edition) Library » Conformance
37 |
47 |
48 |
49 | EOHTML;
50 |
51 | echo ($browser ? '' : "\033[1m");
52 | echo 'Twitter Text (PHP Edition) Library » Conformance';
53 | echo ($browser ? '
' : "\033[0m".PHP_EOL.'==============================================='.PHP_EOL);
54 | echo PHP_EOL;
55 |
56 | echo ($browser ? '' : "\033[1m");
57 | echo 'Extraction Conformance';
58 | echo ($browser ? '
' : "\033[0m".PHP_EOL.'----------------------'.PHP_EOL);
59 | echo PHP_EOL;
60 |
61 | # Load the test data.
62 | $data = Spyc::YAMLLoad($DATA.'/extract.yml');
63 |
64 | # Define the functions to be tested.
65 | $functions = array(
66 | 'hashtags' => 'extractHashtags',
67 | 'urls' => 'extractURLs',
68 | 'mentions' => 'extractMentionedUsernames',
69 | 'replies' => 'extractRepliedUsernames',
70 | 'hashtags_with_indices' => 'extractHashtagsWithIndices',
71 | 'urls_with_indices' => 'extractURLsWithIndices',
72 | 'mentions_with_indices' => 'extractMentionedUsernamesWithIndices',
73 | );
74 |
75 | # Perform testing.
76 | foreach ($data['tests'] as $group => $tests) {
77 |
78 | echo ($browser ? '' : "\033[1m");
79 | echo 'Test Group - '.ucfirst(str_replace('_', ' ', $group));
80 | echo ($browser ? '
' : ":\033[0m".PHP_EOL);
81 | echo PHP_EOL;
82 |
83 | if (!array_key_exists($group, $functions)) {
84 | echo ($browser ? '' : " \033[1;35m");
85 | echo 'Skipping Test...';
86 | echo ($browser ? '
' : "\033[0m".PHP_EOL);
87 | echo PHP_EOL;
88 | continue;
89 | }
90 | $function = $functions[$group];
91 | $pass_group = 0;
92 | $fail_group = 0;
93 | if ($browser) echo '', PHP_EOL;
94 | foreach ($tests as $test) {
95 | echo ($browser ? '- ' : ' - ');
96 | echo $test['description'], ' ... ';
97 | $extracted = Twitter_Extractor::create($test['text'])->$function();
98 | if ($test['expected'] == $extracted) {
99 | $pass_group++;
100 | echo ($browser ? 'PASS' : "\033[1;32mPASS\033[0m");
101 | } else {
102 | $fail_group++;
103 | echo ($browser ? 'FAIL' : "\033[1;31mFAIL\033[0m");
104 | if ($browser) {
105 | echo '
';
106 | echo 'Original: '.htmlspecialchars($test['text'], ENT_QUOTES, 'UTF-8', false), PHP_EOL;
107 | echo 'Expected: '.pretty_format($test['expected']), PHP_EOL;
108 | echo 'Actual: '.pretty_format($extracted);
109 | echo '
';
110 | } else {
111 | echo PHP_EOL, PHP_EOL;
112 | echo ' Original: '.$test['text'], PHP_EOL;
113 | echo ' Expected: '.pretty_format($test['expected']), PHP_EOL;
114 | echo ' Actual: '.pretty_format($extracted), PHP_EOL;
115 | }
116 | }
117 | if ($browser) echo ' ';
118 | echo PHP_EOL;
119 | }
120 | if ($browser) echo '
';
121 | echo PHP_EOL;
122 | $pass_total += $pass_group;
123 | $fail_total += $fail_group;
124 | echo ($browser ? '' : " \033[1;33m");
125 | printf('Group Results: %d passes, %d failures', $pass_group, $fail_group);
126 | echo ($browser ? '
' : "\033[0m".PHP_EOL);
127 | echo PHP_EOL;
128 | }
129 |
130 | echo ($browser ? '' : "\033[1m");
131 | echo 'Autolink Conformance';
132 | echo ($browser ? '
' : "\033[0m".PHP_EOL.'--------------------'.PHP_EOL);
133 | echo PHP_EOL;
134 |
135 | # Load the test data.
136 | $data = Spyc::YAMLLoad($DATA.'/autolink.yml');
137 |
138 | # Define the functions to be tested.
139 | $functions = array(
140 | 'usernames' => 'addLinksToUsernamesAndLists',
141 | 'lists' => 'addLinksToUsernamesAndLists',
142 | 'hashtags' => 'addLinksToHashtags',
143 | 'urls' => 'addLinksToURLs',
144 | 'all' => 'addLinks',
145 | );
146 |
147 | # Perform testing.
148 | foreach ($data['tests'] as $group => $tests) {
149 |
150 | echo ($browser ? '' : "\033[1m");
151 | echo 'Test Group - '.ucfirst(str_replace('_', ' ', $group));
152 | echo ($browser ? '
' : ":\033[0m".PHP_EOL);
153 | echo PHP_EOL;
154 |
155 | if (!array_key_exists($group, $functions)) {
156 | echo ($browser ? '' : " \033[1;35m");
157 | echo 'Skipping Test...';
158 | echo ($browser ? '
' : "\033[0m".PHP_EOL);
159 | echo PHP_EOL;
160 | continue;
161 | }
162 | $function = $functions[$group];
163 | $pass_group = 0;
164 | $fail_group = 0;
165 | if ($browser) echo '', PHP_EOL;
166 | foreach ($tests as $test) {
167 | echo ($browser ? '- ' : ' - ');
168 | echo $test['description'], ' ... ';
169 | $linked = Twitter_Autolink::create($test['text'], false)
170 | ->setNoFollow(false)->setExternal(false)->setTarget('')
171 | ->setUsernameClass('tweet-url username')
172 | ->setListClass('tweet-url list-slug')
173 | ->setHashtagClass('tweet-url hashtag')
174 | ->setURLClass('')
175 | ->$function();
176 | # XXX: Need to re-order for hashtag as it is written out differently...
177 | # We use the same wrapping function for adding links for all methods.
178 | if ($group == 'hashtags') {
179 | $linked = preg_replace(array(
180 | '!([^<]*)!',
181 | '!title="#([^"]+)"!'
182 | ), array(
183 | '$3',
184 | 'title="#$1"'
185 | ), $linked);
186 | }
187 | if ($test['expected'] == $linked) {
188 | $pass_group++;
189 | echo ($browser ? 'PASS' : "\033[1;32mPASS\033[0m");
190 | } else {
191 | $fail_group++;
192 | echo ($browser ? 'FAIL' : "\033[1;31mFAIL\033[0m");
193 | if ($browser) {
194 | echo '
';
195 | echo 'Original: '.htmlspecialchars($test['text'], ENT_QUOTES, 'UTF-8', false), PHP_EOL;
196 | echo 'Expected: '.pretty_format($test['expected']), PHP_EOL;
197 | echo 'Actual: '.pretty_format($linked);
198 | echo '
';
199 | } else {
200 | echo PHP_EOL, PHP_EOL;
201 | echo ' Original: '.$test['text'], PHP_EOL;
202 | echo ' Expected: '.pretty_format($test['expected']), PHP_EOL;
203 | echo ' Actual: '.pretty_format($linked), PHP_EOL;
204 | }
205 | }
206 | if ($browser) echo ' ';
207 | echo PHP_EOL;
208 | }
209 | if ($browser) echo '
';
210 | echo PHP_EOL;
211 | $pass_total += $pass_group;
212 | $fail_total += $fail_group;
213 | echo ($browser ? '' : " \033[1;33m");
214 | printf('Group Results: %d passes, %d failures', $pass_group, $fail_group);
215 | echo ($browser ? '
' : "\033[0m".PHP_EOL);
216 | echo PHP_EOL;
217 | }
218 |
219 | echo ($browser ? '' : "\033[1m");
220 | echo 'Hit Highlighter Conformance';
221 | echo ($browser ? '
' : "\033[0m".PHP_EOL.'---------------------------'.PHP_EOL);
222 | echo PHP_EOL;
223 |
224 | # Load the test data.
225 | $data = Spyc::YAMLLoad($DATA.'/hit_highlighting.yml');
226 |
227 | # Define the functions to be tested.
228 | $functions = array(
229 | 'plain_text' => 'addHitHighlighting',
230 | 'with_links' => 'addHitHighlighting',
231 | );
232 |
233 | # Perform testing.
234 | foreach ($data['tests'] as $group => $tests) {
235 |
236 | echo ($browser ? '' : "\033[1m");
237 | echo 'Test Group - '.ucfirst(str_replace('_', ' ', $group));
238 | echo ($browser ? '
' : ":\033[0m".PHP_EOL);
239 | echo PHP_EOL;
240 |
241 | if (!array_key_exists($group, $functions)) {
242 | echo ($browser ? '' : " \033[1;35m");
243 | echo 'Skipping Test...';
244 | echo ($browser ? '
' : "\033[0m".PHP_EOL);
245 | echo PHP_EOL;
246 | continue;
247 | }
248 | $function = $functions[$group];
249 | $pass_group = 0;
250 | $fail_group = 0;
251 | if ($browser) echo '', PHP_EOL;
252 | foreach ($tests as $test) {
253 | echo ($browser ? '- ' : ' - ');
254 | echo $test['description'], ' ... ';
255 | $highlighted = Twitter_HitHighlighter::create($test['text'])->$function($test['hits']);
256 | if ($test['expected'] == $highlighted) {
257 | $pass_group++;
258 | echo ($browser ? 'PASS' : "\033[1;32mPASS\033[0m");
259 | } else {
260 | $fail_group++;
261 | echo ($browser ? 'FAIL' : "\033[1;31mFAIL\033[0m");
262 | if ($browser) {
263 | echo '
';
264 | echo 'Original: '.htmlspecialchars($test['text'], ENT_QUOTES, 'UTF-8', false), PHP_EOL;
265 | echo 'Expected: '.pretty_format($test['expected']), PHP_EOL;
266 | echo 'Actual: '.pretty_format($highlighted);
267 | echo '
';
268 | } else {
269 | echo PHP_EOL, PHP_EOL;
270 | echo ' Original: '.$test['text'], PHP_EOL;
271 | echo ' Expected: '.pretty_format($test['expected']), PHP_EOL;
272 | echo ' Actual: '.pretty_format($highlighted), PHP_EOL;
273 | }
274 | }
275 | if ($browser) echo ' ';
276 | echo PHP_EOL;
277 | }
278 | if ($browser) echo '
';
279 | echo PHP_EOL;
280 | $pass_total += $pass_group;
281 | $fail_total += $fail_group;
282 | echo ($browser ? '' : " \033[1;33m");
283 | printf('Group Results: %d passes, %d failures', $pass_group, $fail_group);
284 | echo ($browser ? '
' : "\033[0m".PHP_EOL);
285 | echo PHP_EOL;
286 | }
287 |
288 | echo ($browser ? '' : " \033[1;36m");
289 | printf('Total Results: %d passes, %d failures', $pass_total, $fail_total);
290 | echo ($browser ? '
' : "\033[0m".PHP_EOL);
291 | echo PHP_EOL;
292 |
293 | if ($browser) echo <<
295 |
296 | EOHTML;
297 |
--------------------------------------------------------------------------------