Overview

Packages

  • application
    • commands
    • components
      • actions
      • filters
      • leftWidget
      • permissions
      • sortableWidget
      • util
      • webupdater
      • x2flow
        • actions
        • triggers
      • X2GridView
      • X2Settings
    • controllers
    • models
      • embedded
    • modules
      • accounts
        • controllers
        • models
      • actions
        • controllers
        • models
      • calendar
        • controllers
        • models
      • charts
        • models
      • contacts
        • controllers
        • models
      • docs
        • components
        • controllers
        • models
      • groups
        • controllers
        • models
      • marketing
        • components
        • controllers
        • models
      • media
        • controllers
        • models
      • mobile
        • components
      • opportunities
        • controllers
        • models
      • products
        • controllers
        • models
      • quotes
        • controllers
        • models
      • services
        • controllers
        • models
      • template
        • models
      • users
        • controllers
        • models
      • workflow
        • controllers
        • models
      • x2Leads
        • controllers
        • models
  • None
  • system
    • base
    • caching
    • console
    • db
      • ar
      • schema
    • validators
    • web
      • actions
      • auth
      • helpers
      • widgets
        • captcha
        • pagers
  • zii
    • widgets
      • grid

Classes

  • BaseDocsMassAction
  • CommonFieldsBehavior
  • Expression
  • MassAction
  • MassAddToList
  • MassCompleteAction
  • MassMoveFileSysObjToFolder
  • MassRemoveFromList
  • MassRenameFileSysObj
  • MassUncompleteAction
  • MobileRecentItems
  • ModulePanelItem
  • NewListFromSelection
  • PanelItem
  • QuickCRUDBehavior
  • RecentItemPanelItem
  • ServiceRoutingBehavior
  • SettingsPanelItem
  • X2AddressBehavior
  • X2AuthCache
  • X2BaseListViewBehavior

Exceptions

  • TwitterFeedWidgetException
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /*****************************************************************************************
  3:  * X2Engine Open Source Edition is a customer relationship management program developed by
  4:  * X2Engine, Inc. Copyright (C) 2011-2016 X2Engine Inc.
  5:  * 
  6:  * This program is free software; you can redistribute it and/or modify it under
  7:  * the terms of the GNU Affero General Public License version 3 as published by the
  8:  * Free Software Foundation with the addition of the following permission added
  9:  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 10:  * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
 11:  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 12:  * 
 13:  * This program is distributed in the hope that it will be useful, but WITHOUT
 14:  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 15:  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 16:  * details.
 17:  * 
 18:  * You should have received a copy of the GNU Affero General Public License along with
 19:  * this program; if not, see http://www.gnu.org/licenses or write to the Free
 20:  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 21:  * 02110-1301 USA.
 22:  * 
 23:  * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
 24:  * California 95067, USA. or at email address contact@x2engine.com.
 25:  * 
 26:  * The interactive user interfaces in modified source and object code versions
 27:  * of this program must display Appropriate Legal Notices, as required under
 28:  * Section 5 of the GNU Affero General Public License version 3.
 29:  * 
 30:  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
 31:  * these Appropriate Legal Notices must retain the display of the "Powered by
 32:  * X2Engine" logo. If the display of the logo is not reasonably feasible for
 33:  * technical reasons, the Appropriate Legal Notices must display the words
 34:  * "Powered by X2Engine".
 35:  *****************************************************************************************/
 36: 
 37: Yii::import ('application.components.TwitterAPI.TwitterAPIExchange');
 38: 
 39: /**
 40:  * Class for displaying contact twitter feeds
 41:  * 
 42:  * @package application.components.sortableWidget
 43:  */
 44: class TwitterFeedWidget extends SortableWidget {
 45: 
 46:     public $viewFile = '_twitterFeedWidget';
 47: 
 48:     public $model;
 49: 
 50:     public $sortableWidgetJSClass = 'TwitterFeedWidget';
 51: 
 52:     public $template = '<div class="submenu-title-bar widget-title-bar">{twitterLogo}{widgetLabel}{screenNameSelector}{closeButton}{minimizeButton}</div>{widgetContents}';
 53: 
 54:     private static $_JSONPropertiesStructure;
 55: 
 56:     private $_username;
 57: 
 58:     public static function getJSONPropertiesStructure () {
 59:         if (!isset (self::$_JSONPropertiesStructure)) {
 60:             self::$_JSONPropertiesStructure = array_merge (
 61:                 parent::getJSONPropertiesStructure (),
 62:                 array (
 63:                     'label' => 'Twitter Feed',
 64:                     'hidden' => false,
 65:                 )
 66:             );
 67:         }
 68:         return self::$_JSONPropertiesStructure;
 69:     }
 70: 
 71:     private $_modelTwitterAliases;
 72:     public function getModelTwitterAliases () {
 73:         if (!isset ($this->_modelTwitterAliases)) {
 74:             $this->_modelTwitterAliases = RecordAliases::getAliases ($this->model, 'twitter');
 75:         }
 76:         return $this->_modelTwitterAliases;
 77:     }
 78: 
 79:     public function renderTwitterLogo () {
 80:         echo '<span id="twitter-widget-top-bar-logo"></span>';
 81:     }
 82: 
 83:     public function renderScreenNameSelector () {
 84:         $options = array ();
 85:         foreach ($this->getModelTwitterAliases () as $alias) {
 86:             $options[$alias->alias] = $alias->alias;
 87:         }
 88:         echo CHtml::dropDownList ('screenName', null, $options, array (
 89:             'class' => 'x2-minimal-select',
 90:             'id' => 'screen-name-selector',
 91:         ));
 92:     }
 93: 
 94:     /**
 95:      * overrides parent method. Adds JS file necessary to run the setup script.
 96:      */
 97:     public function getPackages () {
 98:         if (!isset ($this->_packages)) {
 99:             $this->_packages = array_merge (
100:                 parent::getPackages (),
101:                 array (
102:                     'TwitterFeedWidgetJS' => array(
103:                         'baseUrl' => Yii::app()->request->baseUrl,
104:                         'js' => array (
105:                             'js/sortableWidgets/TwitterFeedWidget.js',
106:                         ),
107:                         'depends' => array ('SortableWidgetJS')
108:                     ),
109:                 )
110:             );
111:         }
112:         return $this->_packages;
113:     }
114: 
115:     public function getViewFileParams () {
116:         if (!isset ($this->_viewFileParams)) {
117:             $this->_viewFileParams = array_merge (
118:                 parent::getViewFileParams (),
119:                 array (
120:                     'username' => $this->_username,
121:                 )
122:             );
123:         }
124:         return $this->_viewFileParams;
125:     } 
126: 
127:     /**
128:      * @return string id of last tweet in data provider
129:      */
130:     public function getLastTweetId () {
131:         $tweetDP = $this->getTweetDataProvider ();
132:         if (!$tweetDP) return null;
133:         $data = $tweetDP->getData ();
134:         $lastTweetId = null;
135:         if (count ($data)) {
136:             $lastTweetId = $data[count ($data) - 1]['id_str'];
137:         }
138:         return $lastTweetId;
139:     }
140: 
141:     /**
142:      * @return string tweet cache key 
143:      */
144:     public function getCacheKey () {
145:         $username = $this->_username;
146:         return 'TwitterFeedWidget'.$username;
147:     }
148: 
149:     public function run () {
150:         $credentials = $this->getTwitterCredentials ();
151:         if (!$credentials) return '';
152:         if (!extension_loaded('curl')) {
153:             $this->addError (Yii::t('app', 'The Twitter widget requires the PHP curl extension.'));
154:             return parent::run (); 
155:         } else {
156:             $aliases = $this->getModelTwitterAliases ();
157:             if (!count ($aliases)) return '';
158:             if (isset ($_GET['twitterScreenName'])) {
159:                 $this->_username = $_GET['twitterScreenName'];
160:             } else {
161:                 $this->_username = $aliases[0]->alias;
162:             }
163:             try {
164:                 $this->getTweetDataProvider ();
165:             } catch (TwitterFeedWidgetException $e) {
166:                 $errorMessage = $e->getMessage ();
167:                 if (isset ($_GET['twitterFeedAjax'])) {
168:                     throw new CHttpException (429, $errorMessage);
169:                 } else {
170:                     $this->addError ($errorMessage);
171:                 }
172:             }
173:         }
174:         return parent::run ();
175:     }
176:     
177:     private $_credentials;
178:     public function getTwitterCredentials () {
179:         if (!isset ($this->_credentials)) {
180:             $credId = Yii::app()->settings->twitterCredentialsId;
181:             if ($credId && ($credentials = Credentials::model ()->findByPk ($credId))) {
182:                 $this->_credentials = array(
183:                     'oauth_access_token' => $credentials->auth->oauthAccessToken,
184:                     'oauth_access_token_secret' => $credentials->auth->oauthAccessTokenSecret,
185:                     'consumer_key' => $credentials->auth->consumerKey,
186:                     'consumer_secret' => $credentials->auth->consumerSecret,
187:                 );
188:             }
189:         }
190:         return $this->_credentials;
191:     }
192: 
193:     /**
194:      * Formats date accoring to Twitter display guidelines 
195:      */
196:     public function renderTimestamp (array $tweet) {
197:         $timestamp = strtotime ($tweet['created_at']);
198:         $date = getDate ($timestamp);
199:         $nowTs = time ();
200:         $now = getDate ($nowTs);
201:         $formattedTimestamp = '';
202:         if ($date['year'] !== $now['year']) { // long format
203:             $formattedTimestamp = Yii::app()->dateFormatter->format(
204:                 'd MMM yy', $timestamp);
205:         } else if ($date['yday'] !== $now['yday']) { // month day format
206:             $formattedTimestamp = Yii::app()->dateFormatter->format(
207:                 'd MMM', $timestamp);
208:         } else if ($now['hours'] - $date['hours'] > 1) { // hour format
209:             $diffTs = $nowTs - $timestamp;
210:             $formattedTimestamp = floor ($diffTs / 60 / 60).'h';
211:         } else if ($now['minutes'] - $date['minutes'] > 1) { // minute format
212:             $diffTs = $nowTs - $timestamp;
213:             $formattedTimestamp = floor ($diffTs / 60).'m';
214:         } else { // second format
215:             $diffTs = $nowTs - $timestamp;
216:             $formattedTimestamp = $diffTs.'s';
217:         }
218:         return '<a href="https://www.twitter.com/'.urlencode ($this->_username).'/status/'.
219:             $tweet['id_str'].'">'.
220:                 CHtml::encode ($formattedTimestamp).
221:             '</a>';
222:     }
223: 
224:     /**
225:      * Replace Twitter entities in tweet text with formatted versions
226:      */
227:     public function replaceTextEntities (array &$tweet) {
228:         if (isset ($tweet['retweeted_status'])) {
229:             $matches = array ();
230:             $name = $tweet['user']['name'];
231:             $retweetedByText = 
232:                 '<div class="retweeted-by-text-container">
233:                     <span class="retweet-icon-small"></span>'.
234:                     CHtml::encode (Yii::t('app', 'Retweeted by')).
235:                     '&nbsp;<a href="https://twitter.com/'.urlencode ($name).'">'.
236:                         $name.
237:                     '</a>'.
238:                 '</div>';
239:             $tweet = $tweet['retweeted_status'];
240:         }
241: 
242: 
243:         if (!isset ($tweet['entities'])) return $tweet['text'];
244:         $text = $tweet['text'];
245:         $entities = $tweet['entities'];
246:         $orderedEntities = array ();
247: 
248:         // collapse entities array so that they can be more easily ordered
249:         foreach ($entities as $type => $entitiesOfType) {
250:             foreach ($entitiesOfType as $entity) {
251:                 $orderedEntities[] = array_merge (array ('type' => $type), $entity);
252:             }
253:         }
254:         // order entities by index into tweet text
255:         usort ($orderedEntities, function ($a, $b) {
256:             return $b['indices'][0] - $a['indices'][0];
257:         });
258: 
259:         // replace entities in reverse order to preserve indices in original tweet text
260:         foreach ($orderedEntities as $entity) {
261:             switch ($entity['type']) {
262:                 case 'hashtags':
263:                     $link = "<a href='https://twitter.com/hashtag/".
264:                         urlencode ($entity['text'])."'?src=hash>
265:                         #".CHtml::encode ($entity['text'])."</a>";
266:                     break;
267:                 case 'symbols':
268:                     $link = "<a href='https://twitter.com/search?1=".
269:                         urlencode ($entity['text'])."'?src=ctag>
270:                         $".CHtml::encode ($entity['text'])."</a>";
271:                     break;
272:                 case 'urls':
273:                     $link = "<a 
274:                         title='".$entity['expanded_url']."'
275:                         href='".$entity['url']."'>
276:                         ".CHtml::encode ($entity['display_url'])."</a>";
277:                     break;
278:                 case 'user_mentions':
279:                     $link = "<a href='https://twitter.com/".urlencode ($entity['screen_name'])."'>
280:                         @".CHtml::encode ($entity['screen_name'])."</a>";
281:                     break;
282:                 default: 
283:                     continue 2;
284:             }
285:             $text = mb_substr ($text, 0, $entity['indices'][0], 'UTF-8').$link.
286:                 mb_substr ($text, $entity['indices'][1] + 1, null, 'UTF-8');
287:         }
288: 
289:         if (isset ($retweetedByText)) $text .= $retweetedByText;
290:         $tweet['text'] = $text;
291:     }
292: 
293:     /**
294:      * @param string $resourceName name of the api-fetched resource (e.g. /statuses/user_timeline)
295:      * @param null|int $value If not null, will be used to update the remaining requests count
296:      */
297:     public function remainingRequests ($resourceName, $value=null) {
298:         $resourceName = preg_replace ('/\.json$/', '', $resourceName);
299:         $rateLimitStatus = $this->getRateLimitStatus ();
300: //       AuxLib::debugLogR ('$resourceName = ');
301: //        AuxLib::debugLogR ($resourceName);
302: //
303: //        AuxLib::debugLogR ($rateLimitStatus);
304:         if (!$rateLimitStatus) {
305:             return false; // no rate limit info available
306:         }
307:         $matches = array ();
308:         preg_match ('/^\/([^\/]+)\//', $resourceName, $matches);
309:         $resourceCategory = $matches[1];
310: //       AuxLib::debugLogR ('$resourceCategory = ');
311: //        AuxLib::debugLogR ($resourceCategory);
312: //       AuxLib::debugLogR ('$resourceName = ');
313: //        AuxLib::debugLogR ($resourceName);
314: 
315:         if (!isset ($rateLimitStatus['resources'][$resourceCategory][$resourceName])) {
316:             return false; // rate limit info not found
317:         }
318:         if ($value !== null) {
319:             $rateLimitStatus['resources'][$resourceCategory][$resourceName][
320:                 'remaining'] = $value;
321:             $this->setRateLimitStatus ($rateLimitStatus);
322:         } 
323: 
324:         $entry = $rateLimitStatus['resources'][$resourceCategory][$resourceName];
325:         $remaining = (int) $entry['remaining'];
326: //       AuxLib::debugLogR ('$remaining = ');
327: //        AuxLib::debugLogR ($remaining);
328: 
329:         return $remaining;
330:     }
331: 
332:     /**
333:      * Update rate limit caches 
334:      */
335:     private function setRateLimitStatus (array $rateLimitStatus) {
336:         $this->_rateLimits = $rateLimitStatus;
337:         Yii::app()->settings->twitterRateLimits = $rateLimitStatus;
338:         Yii::app()->settings->save ();
339:     }
340: 
341:     /**
342:      * Retrieve rate limit status. The status is cached in the admin table and refreshed whenever
343:      * the rate limit window (15 minutes) has passed
344:      */
345:     private $_rateLimits;
346:     private function getRateLimitStatus () {
347:         if (isset ($this->_rateLimits)) return $this->_rateLimits;
348: 
349:         $rateLimitWindow = 60 * 15;
350: 
351:         // first check the cache
352:         $rateLimits = Yii::app()->settings->twitterRateLimits;
353:         if (ctype_digit ($rateLimits)) { // setting is set to window expiration date
354:             if ((int) $rateLimits >= time ()) { // window hasn't expired
355:                 return false;
356:             }
357:         } elseif (is_array ($rateLimits)) { 
358:             // if rate limit field is set but doesn't include needed rate limits, there's no
359:             // way of knowing whether an additional request would surpass the rate limit. 
360:             // Set the rate limit to the window size to ensure that the rate limit gets reset before
361:             // making another api request.
362:             if (!isset (
363:                     $rateLimits['resources']['application']['/application/rate_limit_status'])) {
364: 
365:                 Yii::app()->settings->twitterRateLimits = time () + $rateLimitWindow;
366:                 Yii::app()->settings->save ();
367:                 return false; 
368:             }
369: 
370:             $entry = $rateLimits['resources']['application']['/application/rate_limit_status'];
371:             if ($entry['reset'] > time ()) { // end of window hasn't been reached
372:                 if ((int) $entry['remaining'] < 1) {
373:                     // rate limit on number of requests to retrieve the rate limit has been reached
374:                     return false;
375:                 } else {
376:                     // rate limit info is valid
377:                     //AuxLib::debugLogR ('cache hit');
378:                     return $rateLimits;
379:                 }
380:             }
381: 
382:         } else if ($rateLimits !== null) {
383:             // rate limit was set to an invalid value
384:             Yii::app()->settings->twitterRateLimits = time () + $rateLimitWindow;
385:             Yii::app()->settings->save ();
386:             return false; 
387:         }
388: 
389:         //AuxLib::debugLogR ('cache miss');
390: 
391:         // refresh the rate limit status cache
392:         $credentials = $this->getTwitterCredentials ();
393:         $url = 'https://api.twitter.com/1.1/application/rate_limit_status.json';
394:         $requestMethod = 'GET';
395:         $twitter = new TwitterAPIExchange ($credentials);
396:         $rateLimitStatus = CJSON::decode (
397:             $twitter
398:             ->buildOauth ($url, $requestMethod)
399:             ->performRequest ()); 
400:         if (($statusCode = $twitter->getLastStatusCode ()) != 200) {
401:             $this->throwApiException ($rateLimitStatus, $statusCode);
402:         }
403:         Yii::app()->settings->twitterRateLimits = $rateLimitStatus;
404:         Yii::app()->settings->save ();
405: 
406:         $this->_rateLimits = $rateLimitStatus;
407:         return $rateLimitStatus;
408:     }
409: 
410:     /**
411:      * Handles tweet requests, caching, and pagination. 
412:      *
413:      * Widget pagination is handled by means of the GET parameter maxTweetId; if set, the cache 
414:      * will be scanned for a tweet with the specified id. If that id is found, all tweets in the 
415:      * cache will be returned up to one page past the specified id. If the max id is not in the 
416:      * cache, one attempt will be made to fetch new tweets into the cache.
417:      *
418:      * @param bool $append If true, assuming tweets are cached, new tweets will be fetched 
419:      *  with max id set to the last id of the cached tweets. The results will be appended to 
420:      *  the cache.
421:      * @throws CException if tweets cannot be fetched due to rate limit being met
422:      */
423:     private $_tweets;
424:     public function requestTweets ($append=false) {
425:         $this->getRateLimitStatus ();
426:         $maxId = isset ($_GET['maxTweetId']) ? $_GET['maxTweetId'] : -1;
427: 
428:         if (!isset ($this->_tweets) || $append) {
429:             $username = $this->_username;
430:             $cache = Yii::app()->cache2;
431:             $cacheKey = $this->getCacheKey ();
432:             $pageSize = 5;
433:             $tweets = $cache->get ($cacheKey);
434: 
435:             if ($append && !$tweets) {
436:                 // another page of tweets has been requested but the newer tweets have been 
437:                 // invalidated. To avoid having to determine how many pages down the user is, 
438:                 // we simply refresh the feed.
439:                 $append = false;
440:                 $maxId = -1;
441:             }
442: 
443:             if (!$tweets || $append) { // fetch tweets and add to cache
444:                 $tweetCount = 100;
445:                 $credentials = $this->getTwitterCredentials ();
446:                 $resourceName = '/statuses/user_timeline.json';
447:                 $remainingRequests = $this->remainingRequests ($resourceName);
448: 
449:                 if ($remainingRequests < 1) {
450:                     // rate limit met
451:                     throw new TwitterFeedWidgetException (Yii::t(
452:                         'app', 'Twitter feed could not be retrieved. Please try again later.'));
453:                 }
454: 
455:                 $url = 'https://api.twitter.com/1.1'.$resourceName;;
456:                 $getfield = '?screen_name='.$username.'&count='.$tweetCount;
457:                 if ($append) { 
458:                     $maxId = $tweets[count ($tweets) - 1]['id_str'];
459:                     $getfield .= '&max_id='.$maxId;
460:                 }
461: 
462:                 $requestMethod = 'GET';
463:                 $twitter = new TwitterAPIExchange ($credentials);
464:                 $oldTweets = $tweets;
465: 
466:                 $tweets = CJSON::decode ($twitter->setGetfield ($getfield)
467:                     ->buildOauth ($url, $requestMethod)
468:                     ->performRequest ()); 
469:                 if (($statusCode = $twitter->getLastStatusCode ()) != 200) {
470:                     $this->throwApiException ($tweets, $statusCode);
471:                 }
472:                 $this->remainingRequests ($resourceName, $remainingRequests - 1);
473:                 if ($append) {
474:                     $tweets = array_merge ($oldTweets, $tweets);
475:                 } 
476:                 $cache->set ($cacheKey, $tweets, 60 * 5);
477:                 //AuxLib::debugLogR ('cache miss');
478:             } else {
479:                 //AuxLib::debugLogR ('cache hit');
480:             }
481: 
482:             if ($maxId === -1) { // initial page load, just return the first page
483:                 $this->_tweets = array_slice ($tweets, 0, $pageSize);
484:             } else { // max id specified, return all tweets up one page beyond max id
485:                 $tweetCount = count ($tweets);
486:                 $found = false;
487:                 for ($i = 0; $i < $tweetCount; $i++) { 
488:                     $tweet = $tweets[$i];
489:                     if ($tweet['id_str'] == $maxId) {
490:                         $found = true; 
491:                         break;
492:                     }
493:                 }
494:                 if ($found && $i + $pageSize < $tweetCount) {
495:                     $this->_tweets = array_slice ($tweets, 0, $i + $pageSize + 1);
496:                 } else if (!$append) { // only request more tweets once
497:                     return $this->requestTweets (true);
498:                 } else { // giving up on searching for specified tweet, just display the first page
499:                     $this->_tweets = array_slice ($tweets, 0, $pageSize);
500:                 }
501:             }
502:         }
503:         return $this->_tweets;
504:     }
505: 
506:     private $_tweetDataProvider;
507:     public function getTweetDataProvider () {
508:         if (!isset ($this->_tweetDataProvider)) {
509:             $tweets = $this->requestTweets ();
510:             $this->_tweetDataProvider = new CArrayDataProvider ($tweets, array (
511:                 'pagination' => array (
512:                     'pageSize' => PHP_INT_MAX,
513:                 ),
514:             ));
515:         }
516:         return $this->_tweetDataProvider;
517:     }
518: 
519:     public function getTimeline () {
520:         if (isset ($_GET['twitterFeedAjax'])) {
521:             ob_clean ();
522:             ob_start (); 
523:         }
524:         $dataProvider = $this->getTweetDataProvider ();
525:         if (!$dataProvider) return;
526: 
527:         Yii::app()->controller->widget ('zii.widgets.CListView', array (
528:             'id' => 'twitter-feed',
529:             'ajaxVar' => 'twitterFeedAjax',
530:             'htmlOptions' => array (
531:                 'class' => 'list-view twitter-feed-list-view',
532:             ),
533:             'viewData' => array (
534:                 'twitterFeedWidget' => $this,
535:             ),
536:             'dataProvider' => $dataProvider,
537:             'itemView' => 'application.components.sortableWidget.views._tweet',
538:             'template' => '{items}',
539:         ));
540:         if (isset ($_GET['twitterFeedAjax'])) {
541:             echo '<script>x2.TwitterFeedWidget.lastTweetId = "'.
542:                 $this->getLastTweetId ().'";</script>';
543:             echo ob_get_clean (); 
544:             ob_flush ();
545:             Yii::app()->end ();
546:         }
547:     }
548: 
549:     protected function getJSSortableWidgetParams () {
550:         if (!isset ($this->_JSSortableWidgetParams)) {
551:             if (!$this->hasError ()) {
552:                 $lastTweetId = $this->getLastTweetId ();
553:             } else {
554:                 $lastTweetId = null;
555:             }
556:             $this->_JSSortableWidgetParams = array_merge (parent::getJSSortableWidgetParams (),
557:                 array (
558:                     'lastTweetId' => $lastTweetId,
559:                 )
560:             );
561:         }
562:         return $this->_JSSortableWidgetParams;
563:     }
564: 
565:     /**
566:      * @param array $response decoded Twitter API response
567:      * @param int $code http status code
568:      */
569:     private function throwApiException ($response, $code) {
570:         $error = isset ($response['error']) ? $response['error'] : '';
571:         switch ($code) {
572:             case 404: 
573:                 $message = Yii::t('app', 'Twitter username not found.');
574:                 break;
575:             case 401: 
576:                 $message = Yii::t(
577:                     'app', 'Twitter Integration credentials are missing or incorrect. Please '.
578:                         'contact an administrator.');
579:                 break;
580:             default:
581:                 $message = Yii::t('app', 'Twitter API {code} error{message}', array (
582:                     '{code}' => $code,
583:                     '{message}' => $error ?  ': '.$error : '',
584:                 ));
585:         }
586:         throw new TwitterFeedWidgetException ($message);
587:     }
588: }
589: 
590: class TwitterFeedWidgetException extends CException {
591: }
592: 
593: ?>
594: 
X2CRM Documentation API documentation generated by ApiGen 2.8.0