Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
trigger_action_update_twitter_status.inc
1 <?php
17 require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
18 require_once SQ_CORE_PACKAGE_PATH.'/system/triggers/trigger_action/trigger_action.inc';
19 require_once SQ_ATTRIBUTES_PATH.'/oauth/oauth.inc';
20 
21 // Normaliser PEAR class
22 require_once 'I18N/UnicodeNormalizer.php';
23 
24 
38 {
39 
40 
52  public static function execute($settings, &$state)
53  {
54  if ($state['asset']) {
55  $asset = $state['asset'];
56  } else if ($state['assetid']) {
57  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($state['assetid']);
58  }
59 
60  if (empty($settings['access_token']) === TRUE) {
61  // No access token
62  trigger_localised_error('CORE0289', E_USER_WARNING);
63  return FALSE;
64  }
65 
66  // Get our keyword replacements
67  $tweet_format = $settings['tweet_contents'];
68  $asset_keywords = retrieve_keywords_replacements($tweet_format);
69  $replacements = Array();
70  foreach ($asset_keywords as $keyword) {
71  $additional_rep = self::getAdditionalKeywordReplacement($asset, $keyword);
72  if ($additional_rep !== NULL) {
73  $replacements[$keyword] = $additional_rep;
74  } else {
75  $replacements[$keyword] = $state['asset']->getKeywordReplacement($keyword);
76  }
77  }//end foreach
78  replace_keywords($tweet_format, $replacements);
79  replace_global_keywords($tweet_format);
80 
81  if (strlen($tweet_format) === 0) {
82  trigger_localised_error('CORE0290', E_USER_WARNING);
83  return FALSE;
84  }
85 
86  // Tweets need to be UTF-8 encoded, no matter what
87  if (mb_detect_encoding($tweet_format) !== 'UTF-8') {
88  $tweet_format = mb_convert_encoding($tweet_format, 'UTF-8');
89  }
90 
91  // Move
92  $tweet_format = I18N_UnicodeNormalizer::toNFC($tweet_format);
93  $tweet_length = mb_strlen($tweet_format, 'UTF-8');
94 
95  // If the normalised size of the tweet is more than 140, it's too long
96  switch ($settings['if_tweet_too_long']) {
97  case 'fail':
98  if ($tweet_length > 140) {
99  trigger_localised_error('CORE0291', E_USER_WARNING);
100  return FALSE;
101  }
102 
103  break;
104 
105  case 'abbrev':
106  $tweet_format = self::abbreviateStatus($tweet_format);
107  $tweet_length = mb_strlen($tweet_format, 'UTF-8');
108 
109  // It's still too long?
110  if ($tweet_length > 140) {
111  trigger_localised_error('CORE0292', E_USER_WARNING);
112  return FALSE;
113  }
114 
115  break;
116 
117  }//end switch
118 
119  $settings['tweet_contents'] = $tweet_format;
120  $return = self::processUpdateStatus($settings);
121 
122  $result = json_decode_array($return);
123 
124  if (isset($result['errors'])) {
125  trigger_localised_error('CORE0293', E_USER_WARNING, (string)$result['errors'][0]['message']);
126  return FALSE;
127  } else {
128  // Get the time that Twitter reported it as
129  $tweet_time = strtotime((string) $result['created_at']);
130  }
131 
132  return Array(
133  'status' => $tweet_format,
134  'time' => $tweet_time,
135  );
136 
137  }//end execute()
138 
139 
150  public static function getInterface($settings, $prefix, $write_access=FALSE, Trigger $trigger=NULL, $action_id=NULL)
151  {
152 
153  $settings['tweet_contents'] = array_get_index($settings, 'tweet_contents', '');
154  $settings['if_tweet_too_long'] = array_get_index($settings, 'if_tweet_too_long', 'abbrev');
155 
156  $settings['consumer_key'] = array_get_index($settings, 'consumer_key');
157  $settings['consumer_secret'] = array_get_index($settings, 'consumer_secret');
158  $settings['request_token'] = array_get_index($settings, 'request_token');
159  $settings['request_token_secret'] = array_get_index($settings, 'request_token_secret');
160  $settings['access_token'] = array_get_index($settings, 'access_token');
161  $settings['access_token_secret'] = array_get_index($settings, 'access_token_secret');
162  $settings['access_token_acct_for'] = array_get_index($settings, 'access_token_acct_for');
163 
164  if ((empty($_REQUEST['oauth_token']) === FALSE) && (empty($_REQUEST['oauth_verifier']) === FALSE)) {
165  // Check that this is the request for this token (and therefore,
166  // for this trigger action where there is more than one)
167  if ($settings['request_token'] === $_REQUEST['oauth_token']) {
168  if ($write_access === TRUE) {
169  $trigger_actions = $trigger->attr('actions');
170  self::processCallback($settings, $_REQUEST['oauth_verifier']);
171  $trigger_actions[$action_id]['data'] = $settings;
172  $trigger->setAttrValue('actions', $trigger_actions);
173  $trigger->saveAttributes();
174  }
175  }
176  }
177 
178  if ((empty($_REQUEST['denied']) === FALSE)) {
179  // Our request token was denied (!) for some reason. Twitter
180  // returns a "denied" param in the link it gives to callback (it
181  // does not callback automatically).
182  if ($settings['request_token'] === $_REQUEST['denied']) {
183  trigger_localised_error('CORE0294', E_USER_WARNING);
184  }
185  }
186 
187  // If we have an access token, ensure that we can still actually post
188  // with it!
189  // But calls to verifying credentials comes off our rate limit, so cache
190  // it momentarily (for 1 minute) so it doesn't use too many hits.
191  if (empty($settings['access_token']) === FALSE) {
192  $cache_mgr = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('cache_manager');
193  $cache_key = strtolower(__CLASS__).'_creds_verified_'.$settings['access_token'];
194 
195  $result = $cache_mgr->loadFromCache($trigger->id, 'trigger', $cache_key, FALSE);
196 
197  if ($result !== '1') {
198  $creds_response = self::processVerify($settings);
199  $response = json_decode_array($creds_response);
200 
201 
202  if (isset($response['error'])) {
203  // No longer authenticated!
204  $cache_mgr->saveToCache($trigger->id, 'trigger', $cache_key, '0', FALSE, 1);
205 
206  // Authentication issue? We've probably been revoked.
207  if (((string)$response['error']) === 'Could not authenticate you.') {
208  $settings['access_token'] = NULL;
209  $settings['access_token_secret'] = NULL;
210  trigger_localised_error('CORE0295', E_USER_NOTICE);
211  } else {
212  // Think it's a temporary issue
213  trigger_localised_error('CORE0296', E_USER_NOTICE, (string)$response['error']);
214  }
215  } else {
216  $cache_mgr->saveToCache($trigger->id, 'trigger', $cache_key, '1', FALSE, 60);
217  }
218 
219  }
220  }
221 
222  $credentials_provided = ((empty($settings['consumer_key']) === FALSE) && (empty($settings['consumer_secret']) === FALSE)) ? TRUE : FALSE;
223  $consumer_details_locked = ((empty($settings['access_token']) === TRUE) && (empty($_REQUEST['oauth_verifier']) === TRUE)) ? FALSE : TRUE;
224 
225  // If the client credentials are there and we aren't in the middle of
226  // authentication (or completed), refresh the request token
227  if (($credentials_provided === TRUE) && ($consumer_details_locked === FALSE)) {
228  if ($write_access === TRUE) {
229  $trigger_actions = $trigger->attr('actions');
230  self::processRequestToken($settings);
231  $trigger_actions[$action_id]['data'] = $settings;
232  $trigger->setAttrValue('actions', $trigger_actions);
233  $trigger->saveAttributes();
234  }
235  }
236 
237 
238  $authorised = FALSE;
239 
240  ob_start();
241 
242  hidden_field($prefix.'[request_token]', $settings['request_token']);
243  hidden_field($prefix.'[request_token_secret]', $settings['request_token_secret']);
244  hidden_field($prefix.'[access_token]', $settings['access_token']);
245  hidden_field($prefix.'[access_token_secret]', $settings['access_token_secret']);
246  hidden_field($prefix.'[access_token_acct_for]', $settings['access_token_acct_for']);
247  ?>
248  <div style="padding: 10px">
249  <p class="sq-backend-section-subheading"><?php echo translate('trigger_action_update_twitter_status_register_system_header') ?></p>
250 
251  <?php echo translate('trigger_action_update_twitter_status_register_system_note') ?>
252  </div>
253 
254  <div style="padding: 10px">
255  <p class="sq-backend-section-subheading"><?php echo translate('trigger_action_update_twitter_status_client_credentials_header') ?></p>
256 
257  <table class="sq-backend-table">
258  <thead>
259  <colgroup>
260  <col width="20%" />
261  <col width="80%" />
262  </colgroup>
263  </thead>
264  <tbody>
265  <tr>
266  <th><?php echo translate('trigger_action_update_twitter_status_client_credentials_key') ?></th>
267  <td><?php
268  if (($write_access === TRUE) && ($consumer_details_locked === FALSE)) {
269  text_box($prefix.'[consumer_key]', $settings['consumer_key'], 30, 0);
270  } else {
271  echo $settings['consumer_key'];
272  hidden_field($prefix.'[consumer_key]', $settings['consumer_key']);
273  }
274  ?></td>
275  </tr>
276  <tr>
277  <th><?php echo translate('trigger_action_update_twitter_status_client_credentials_secret') ?></th>
278  <td><?php
279  if (($write_access === TRUE) && ($consumer_details_locked === FALSE)) {
280  text_box($prefix.'[consumer_secret]', $settings['consumer_secret'], 30, 0);
281  } else {
282  echo $settings['consumer_secret'];
283  hidden_field($prefix.'[consumer_secret]', $settings['consumer_secret']);
284  }
285  ?></td>
286  </tr>
287  <tr>
288  <th><?php echo translate('trigger_action_update_twitter_status_client_credentials_status') ?></th>
289  <td><?php
290  if ((empty($settings['consumer_key']) === TRUE) || (empty($settings['consumer_secret']) === TRUE)) {
291  echo translate('trigger_action_update_twitter_status_client_credentials_status_no_consumer');
292  } else {
293  // We have a consumer key
294  if (empty($settings['access_token']) === FALSE) {
295  echo translate('trigger_action_update_twitter_status_client_credentials_status_yes', $settings['access_token_acct_for']);
296  echo '<br/>';
297  check_box($prefix.'[deauthorise]');
298  ?> <label for="<?php echo $prefix ?>[deauthorise]"><?php
299  echo translate('trigger_action_update_twitter_status_client_credentials_deauthorise');
300  } else if (empty($settings['request_token']) === FALSE) {
301  if ($write_access === TRUE) {
302  $url = 'http://api.twitter.com/oauth/authenticate?oauth_token='.$settings['request_token'].'&force_login=true';
303  echo translate('trigger_action_update_twitter_status_client_credentials_status_no', $url);
304  } else {
305  echo translate('trigger_action_update_twitter_status_client_credentials_status_no_unlocked');
306  }
307  } else {
308  ?><strong style="color: #f00">Error: failed to gain request token</strong><?php
309  foreach ($settings['request_token_errors'] as $error) {
310  ?><br/><?php echo $error ?><?php
311  }
312  ?></em><?php
313  }
314  }
315  ?></td>
316  </tr>
317  </tbody>
318  </table>
319  </div>
320  <div style="padding: 10px">
321  <?php echo translate('trigger_action_update_twitter_status_client_credentials_note') ?>
322  </div>
323 
324  <div style="padding: 10px">
325  <p class="sq-backend-section-subheading"><?php echo translate('trigger_action_update_twitter_status_tweet_format_header') ?></p>
326 
327  <table class="sq-backend-table">
328  <thead>
329  <colgroup>
330  <col width="20%" />
331  <col width="80%" />
332  </colgroup>
333  </thead>
334  <tbody>
335  <tr>
336  <th><?php echo translate('trigger_action_update_twitter_status_tweet_format_format') ?></th>
337  <td><?php
338  if ($write_access === TRUE) {
339  text_box($prefix.'[tweet_contents]', $settings['tweet_contents'], 70, 0, FALSE, 'onkeyup="document.getElementById(\''.$prefix.'_tweet_length\').innerHTML = (140 - this.value.length).toString(); return true;"');
340  } else {
341  echo $settings['tweet_contents'];
342  hidden_field($prefix.'[tweet_contents]', $settings['tweet_contents']);
343  }?> <?php echo translate('trigger_action_update_twitter_status_tweet_format_char_count', $prefix, 140 - strlen($settings['tweet_contents'])); ?></td>
344  </tr>
345  <tr>
346  <th><?php echo translate('trigger_action_update_twitter_status_tweet_format_iftoolong') ?></th>
347  <td><?php
348  $options = Array(
349  'abbrev' => translate('trigger_action_update_twitter_status_tweet_format_iftoolong_option_abbrev'),
350  'fail' => translate('trigger_action_update_twitter_status_tweet_format_iftoolong_option_fail'),
351  );
352  if ($write_access === TRUE) {
353  combo_box($prefix.'[if_tweet_too_long]', $options, FALSE, $settings['if_tweet_too_long']);
354  } else {
355  echo $options[$settings['if_tweet_too_long']];
356  hidden_field($prefix.'[if_tweet_too_long]', $settings['if_tweet_too_long']);
357  }?></td>
358  </tr>
359  </tbody>
360  </table>
361  </div>
362  <div style="padding: 10px">
363  <?php echo translate('trigger_action_update_twitter_status_tweet_format_note') ?>
364  </div>
365  <?php
366 
367  $contents = ob_get_clean();
368 
369  return $contents;
370 
371  }//end getInterface()
372 
373 
385  public static function processInterface(&$settings, $request_data)
386  {
387  $settings['tweet_contents'] = array_get_index($request_data, 'tweet_contents');
388  $settings['if_tweet_too_long'] = array_get_index($request_data, 'if_tweet_too_long');
389  $settings['consumer_key'] = array_get_index($request_data, 'consumer_key');
390  $settings['consumer_secret'] = array_get_index($request_data, 'consumer_secret');
391  $settings['request_token'] = array_get_index($request_data, 'request_token');
392  $settings['request_token_secret'] = array_get_index($request_data, 'request_token_secret');
393  $settings['access_token'] = array_get_index($request_data, 'access_token');
394  $settings['access_token_secret'] = array_get_index($request_data, 'access_token_secret');
395  $settings['access_token_acct_for'] = array_get_index($request_data, 'access_token_acct_for');
396 
397  $oauth_verifier = array_get_index($request_data, 'oauth_verifier');
398  $deauthorise = (boolean)array_get_index($request_data, 'deauthorise', FALSE);
399 
400  if ($deauthorise === TRUE) {
401  // Blank out current tokens
402  $settings['request_token'] = NULL;
403  $settings['request_token_secret'] = NULL;
404  $settings['request_token_errors'] = NULL;
405 
406  $settings['access_token'] = NULL;
407  $settings['access_token_secret'] = NULL;
408  $settings['access_token_errors'] = NULL;
409  }
410 
411  return FALSE;
412 
413  }//end processInterface()
414 
415 
426  public static function processRequestToken(&$settings)
427  {
428  // Set the new consumer settings
429  $callback_url = replace_query_string_vars(Array(), NULL, NULL, TRUE);
430  $additional_params = 'SQ_BACKEND_PAGE=frames';
431  if (strpos($callback_url, '?') === FALSE) {
432  $callback_url .= '?'.$additional_params;
433  } else {
434  $callback_url .= '&'.$additional_params;
435  }
436 
437  $oauth_attr = new Asset_Attribute_OAuth();
438  $oauth_attr_values = Array(
439  'consumer_key' => $settings['consumer_key'],
440  'consumer_secret' => $settings['consumer_secret'],
441  'request_token_url' => 'http://api.twitter.com/oauth/request_token',
442  'callback_url' => $callback_url,
443  'method' => 'GET',
444  'signature_method' => 'HMAC-SHA1',
445  'request_headers' => Array(),
446  'request_body' => '',
447  'timeout' => 30,
448  'cache_options' => 'NEVER',
449  'follow_redirect' => FALSE,
450  );
451  $oauth_attr->setValue($oauth_attr_values);
452 
453  $settings['request_token'] = NULL;
454  $settings['request_token_secret'] = NULL;
455  $settings['request_token_errors'] = NULL;
456 
457  $settings['access_token'] = NULL;
458  $settings['access_token_secret'] = NULL;
459  $settings['access_token_errors'] = NULL;
460 
461  $result = $oauth_attr->getRequestToken();
462 
463  if ($result === NULL) {
464  $settings['request_token_errors'] = $oauth_attr->getErrors();
465  } else {
466  $settings['request_token'] = $result['request_token']['oauth_token'];
467  $settings['request_token_secret'] = $result['request_token']['oauth_token_secret'];
468  }
469 
470  return;
471 
472  }//end processRequestToken()
473 
474 
485  public static function processCallback(&$settings, $oauth_verifier)
486  {
487  // Did we get back the token we were expecting?
488  $oauth_attr = new Asset_Attribute_OAuth();
489  $oauth_attr_values = Array(
490  'consumer_key' => $settings['consumer_key'],
491  'consumer_secret' => $settings['consumer_secret'],
492  'access_token_url' => 'http://api.twitter.com/oauth/access_token',
493  'method' => 'GET',
494  'signature_method' => 'HMAC-SHA1',
495  'request_headers' => Array(),
496  'request_body' => '',
497  'timeout' => 30,
498  'cache_options' => 'NEVER',
499  'follow_redirect' => FALSE,
500  );
501  $oauth_attr->setValue($oauth_attr_values);
502 
503  // Blank out current request token settings
504  $settings['access_token'] = NULL;
505  $settings['access_token_secret'] = NULL;
506  $settings['access_token_errors'] = NULL;
507 
508  // Grab a new request token
509  $result = $oauth_attr->getAccessToken($settings['request_token'], $oauth_verifier, $settings['request_token_secret']);
510  if ($result === NULL) {
511  $settings['access_token_errors'] = $oauth_attr->getErrors();
512  } else {
513  $settings['access_token'] = $result['access_token']['oauth_token'];
514  $settings['access_token_secret'] = $result['access_token']['oauth_token_secret'];
515  $settings['access_token_acct_for'] = $result['access_token']['screen_name'];
516  }
517 
518  return FALSE;
519 
520  }//end processCallback()
521 
522 
531  public static function processVerify($settings)
532  {
533  // Did we get back the token we were expecting?
534  $oauth_attr = new Asset_Attribute_OAuth();
535  $oauth_attr_values = Array(
536  'consumer_key' => $settings['consumer_key'],
537  'consumer_secret' => $settings['consumer_secret'],
538  'method' => 'GET',
539  'signature_method' => 'HMAC-SHA1',
540  'request_headers' => Array(),
541  'request_body' => '',
542  'timeout' => 30,
543  'cache_options' => 'NEVER',
544  'follow_redirect' => FALSE,
545  );
546  $oauth_attr->setValue($oauth_attr_values);
547 
548  // Grab a new request token
549  $url = 'http://api.twitter.com/1.1/account/verify_credentials.json';
550  $header = $oauth_attr->getUserDataAuthHeader($url, $settings['access_token'], $settings['access_token_secret']);
551 
552  $details = fetch_url($url, array('RETURNTRANSFER' => 1), array($header));
553 
554  return $details['response'];
555 
556  }//end processVerify()
557 
558 
569  public static function processUpdateStatus($settings)
570  {
571  // Did we get back the token we were expecting?
572  $oauth_attr = new Asset_Attribute_OAuth();
573  $oauth_attr_values = Array(
574  'consumer_key' => $settings['consumer_key'],
575  'consumer_secret' => $settings['consumer_secret'],
576  'method' => 'POST',
577  'signature_method' => 'HMAC-SHA1',
578  'request_headers' => '',
579  'request_body' => '',
580  'timeout' => 30,
581  'cache_options' => 'NEVER',
582  'follow_redirect' => FALSE,
583  );
584  $oauth_attr->setValue($oauth_attr_values);
585 
586  $oauth_attr_values = $oauth_attr->value;
587 
588  // Get the URL for status updating - but we need to hack around this,
589  // since OAuth attr only supports adding new fields through query params
590  // so get it to sign using that
591  $settings['tweet_contents'] = str_replace('+', ' ', str_replace('%7E', '~', rawurlencode($settings['tweet_contents'])));
592  $url = 'http://api.twitter.com/1.1/statuses/update.json';
593  $url_for_signing = $url.'?status='.$settings['tweet_contents'];
594 
595  // It also doesn't seem to like POST. So we will get the header as
596  // usual, then break it apart and turn it into POST data.
597  $header = $oauth_attr->getUserDataAuthHeader($url_for_signing, $settings['access_token'], $settings['access_token_secret'], FALSE);
598  $header = substr_replace($header, '', 0, strlen('Authorization: OAuth '));
599 
600  $post_items = Array();
601  $post_bits = Array();
602 
603  $found = preg_match_all('/([^,=]*)="([^"]*)"/U', $header, $matches, PREG_SET_ORDER);
604  foreach ($matches as $match) {
605  $key = $match[1];
606  $value = $match[2];
607  $post_items[$key] = $value;
608  }
609 
610  $post_items['status'] = $settings['tweet_contents'];
611 
612  foreach ($post_items as $key => $value) {
613  $post_bits[] = $key.'='.$value;
614  }
615 
616  $post_data = implode('&', $post_bits);
617 
618  $curl_options = array(
619  'RETURNTRANSFER' => 1,
620  'POST' => 1,
621  'POSTFIELDS' => $post_data,
622  );
623 
624  $details = fetch_url($url, $curl_options);
625 
626  return $details['response'];
627 
628  }//end processUpdateStatus()
629 
630 
641  public static function getAdditionalKeywordReplacement(Asset $asset, $keyword)
642  {
643  $replacement = NULL;
644 
645  switch ($keyword)
646  {
647  // This keyword will get the shortest URL to the
648  case 'asset_short_url':
649  $urls = Array();
650 
651  // Get the current (closest) URL of the firing asset
652  $current_url = $asset->getUrl();
653 
654  if (empty($current_url) === FALSE) {
655  $urls[] = $current_url;
656 
657  // Then find URLs that remap to this URL
658  $remap_mgr = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('remap_manager');
659  $remapped_urls = $remap_mgr->getRemapURLs($asset);
660 
661  foreach ($remapped_urls as $url_item) {
662  if ($url_item['remap_url'] === $current_url) {
663  $urls[] = $url_item['url'];
664  }
665  }
666 
667  usort($urls, create_function('$a,$b', 'return strlen($a) - strlen($b);'));
668 
669  $replacement = reset($urls);
670  }
671 
672  break;
673 
674  }//end switch
675 
676  return $replacement;
677 
678  }//end getAdditionalKeywordReplacement()
679 
680 
701  public static function abbreviateStatus($status)
702  {
703  // Link detection. The pattern is taken from valid_url() in Fudge's
704  // general/www.inc, but without the start/end-string restriction
705  $pattern = '/\s+(([A-Za-z0-9\-\.\+]+):\/\/[a-z0-9]+(([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,})?((:[0-9]{1,5})?\/[^\s]*)?)\s+/i';
706  $search_status = ' '.$status.' ';
707 
708  $string_bits = Array();
709 
710  $found = preg_match($pattern, $search_status, $matches, PREG_OFFSET_CAPTURE);
711  $last_offset = 1;
712 
713  while ($found > 0) {
714  $start_pos = $matches[1][1];
715  $length = mb_strlen($matches[1][0]);
716 
717  if ($start_pos > 0) {
718  // normal text before the link
719  $string_bits[] = Array(
720  'text' => trim(mb_substr($search_status, 0, $start_pos)),
721  );
722  }
723 
724  // the link itself
725  $string_bits[] = Array(
726  'link' => mb_substr($search_status, $start_pos, $length),
727  );
728 
729  // Get the next link (if any)
730  $search_status = mb_substr($search_status, $start_pos + $length);
731  $found = preg_match($pattern, $search_status, $matches, PREG_OFFSET_CAPTURE);
732 
733  }
734 
735  // Is there anything else after?
736  $search_status = trim($search_status);
737  if (mb_strlen($search_status) > 0) {
738  $string_bits[] = Array(
739  'text' => $search_status,
740  );
741  }
742 
743  // Calculate char count, and work out the size of each text bit so
744  // we know which one to shorten first
745  $char_count = count($string_bits) - 1;
746  $text_bits_lengths = Array();
747 
748  foreach ($string_bits as $string_key => $string_bit) {
749  reset($string_bit);
750 
751  switch (key($string_bit)) {
752  case 'text':
753  $text_bits_lengths[$string_key] = mb_strlen($string_bit['text']);
754  // deliberate fall-through...
755 
756  case 'link':
757  $char_count += mb_strlen($string_bit[key($string_bit)]);
758  break;
759  }
760  }
761 
762  // Are there any text bits at all?
763  if (count($text_bits_lengths) > 0) {
764 
765  arsort($text_bits_lengths);
766  reset($text_bits_lengths);
767  $excess = $char_count - 140;
768  $modified = FALSE;
769 
770  while ($excess > 0) {
771  // Sort the text bits by their length, descending, so we have the
772  // largest text bits first
773  $key = key($text_bits_lengths);
774  $size = current($text_bits_lengths);
775 
776  // Try to make it the largest of: 10 characters, half current size,
777  // or whatever we are over.
778  $new_size = max(10, max($size / 2, $size - $excess));
779 
780  if ($new_size < $size) {
781  $string_bits[$key]['text'] = mb_substr($string_bits[$key]['text'], 0, $new_size - 3).'...';
782  $modified = TRUE;
783 
784  $excess -= ($size - $new_size);
785  $text_bits_lengths[$key] = $new_size;
786  }
787 
788  next($text_bits_lengths);
789 
790  // Go around again if we've hit the smallest bit, unless the
791  // string didn't get any shorter - in that case, give up :(
792  if (key($text_bits_lengths) === NULL) {
793  if ($modified === FALSE) break;
794  $modified = FALSE;
795 
796  // Re-sort the lengths
797  arsort($text_bits_lengths);
798  reset($text_bits_lengths);
799  }
800 
801  }//end while
802 
803  }
804 
805  // Put it all back together again, whether we've abbreviated or not
806  foreach ($string_bits as &$string_bit) {
807  $string_bit = $string_bit[key($string_bit)];
808  }
809  unset($string_bit);
810 
811  return implode(' ', $string_bits);
812 
813  }//end abbreviateStatus()
814 
815 
816 }//end class
817 
818 ?>