3
* Xibo - Digital Signage - http://www.xibo.org.uk
4
* Copyright (C) 2014-2015 Daniel Garner
6
* This file is part of Xibo.
8
* Xibo is free software: you can redistribute it and/or modify
9
* it under the terms of the GNU Affero General Public License as published by
10
* the Free Software Foundation, either version 3 of the License, or
13
* Xibo is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
* GNU Affero General Public License for more details.
18
* You should have received a copy of the GNU Affero General Public License
19
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
22
namespace Xibo\Widget;
26
use Respect\Validation\Validator as v;
27
use Stash\Invalidation;
28
use Xibo\Exception\ConfigurationException;
29
use Xibo\Exception\XiboException;
30
use Xibo\Factory\ModuleFactory;
34
* @package Xibo\Widget
36
class TwitterMetro extends TwitterBase
39
public $codeSchemaVersion = 1;
40
private $resourceFolder;
43
* TwitterMetro constructor.
45
public function init()
47
$this->resourceFolder = PROJECT_ROOT . '/modules/twittermetro';
49
// Initialise extra validation rules
50
v::with('Xibo\\Validation\\Rules\\');
54
* Install or Update this module
55
* @param ModuleFactory $moduleFactory
57
public function installOrUpdate($moduleFactory)
59
if ($this->module == null) {
61
$module = $moduleFactory->createEmpty();
62
$module->name = 'Twitter Metro';
63
$module->type = 'twittermetro';
64
$module->class = 'Xibo\Widget\TwitterMetro';
65
$module->description = 'Twitter Metro Search Module';
66
$module->imageUri = 'forms/library.gif';
68
$module->previewEnabled = 1;
69
$module->assignable = 1;
70
$module->regionSpecific = 1;
71
$module->renderAs = 'html';
72
$module->schemaVersion = $this->codeSchemaVersion;
73
$module->defaultDuration = 60;
74
$module->settings = [];
76
$this->setModule($module);
77
$this->installModule();
80
// Check we are all installed
81
$this->installFiles();
87
public function installFiles()
89
$this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/vendor/jquery-1.11.1.min.js')->save();
90
$this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/xibo-metro-render.js')->save();
91
$this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/xibo-layout-scaler.js')->save();
92
$this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/xibo-image-render.js')->save();
93
$this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/emojione/emojione.sprites.svg')->save();
94
$this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/vendor/bootstrap.min.css')->save();
96
foreach ($this->mediaFactory->createModuleFileFromFolder($this->resourceFolder) as $media) {
97
/* @var \Xibo\Entity\Media $media */
103
* Override the apikey/secret
104
* @param string $setting
105
* @param null $default
106
* @return mixed|string
108
public function getSetting($setting, $default = NULL)
110
if ($setting == 'apiKey' || $setting == 'apiSecret' || $setting == 'cachePeriod') {
111
// Create a Twitter Module as the source of this modules settings
112
// only go to the stock twitter module
113
if ($this->parent === null)
114
$this->parent = $this->moduleFactory->createByClass('Xibo\Widget\Twitter');
116
return $this->parent->getSetting($setting, $default);
119
return parent::getSetting($setting, $default);
125
public function layoutDesignerJavaScript()
127
// We use the same javascript as the data set view designer
128
return 'twittermetro-form-javascript';
132
* Get the template HTML, CSS, widgetOriginalWidth, widgetOriginalHeight giving its orientation (0:Landscape 1:Portrait)
135
public function getTemplateData() {
137
$orientation = ($this->getSanitizer()->getDouble('width', $this->region->width) > $this->getSanitizer()->getDouble('height', $this->region->height)) ? 0 : 1;
139
$templateArray = array(
140
array( 'template' => '<div class="cell-[itemType] [ShadowType] cell" id="item-[itemId]" style="[Photo]"> <div class="item-container [ShadowType]" style="[Color]"> <div class="item-text">[Tweet]</div> <div class="userData"> <div class="tweet-profilePic">[ProfileImage|normal]</div> <div class="tweet-userData"> <div class="user">[User]</div> <small>[Date]</small></div> </div> </div> </div>',
141
'styleSheet' => 'body { font-family: "Helvetica", "Arial", sans-serif; line-height: 1; margin: 0; } #content { width: 1920px !important; height: 1080px !important; background: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 1); } .row-1 { height: 360px; } .page { float: left; margin: 0; padding: 0; } .cell-1 { width: 310px; } .cell-2 { width: 630px; } .cell-3 { width: 950px; } .cell-1, .cell-2, .cell-3 { float: left; height: inherit; margin: 5px; background-repeat: no-repeat; background-size: cover; background-position-x: 50%; background-position-y: 50%; } .item-container { padding: 10px; color: #fff; height: 350px; } .userData { height: 50px; } .darken-container { background-color: rgba(0, 0, 0, 0.4); } .tweet-profilePic { width: 20%; float: left; } .tweet-profilePic img { width: 48px; } .tweet-userData { width: 80%; float: left; text-align: right; } .item-text { padding: 10px; color: #fff; } .emojione { width: 26px; height: 26px; } .cell-1 .item-text { line-height: 30px; font-size: 25px; height: 280px; } .cell-2 .item-text { line-height: 40px; font-size: 40px; height: 280px; } .cell-3 .item-text { line-height: 53px; font-size: 50px; height: 280px; } .user { font-size: 14px; font-weight: bold; padding-top: 20px; } .shadow { text-shadow: 1px 1px 2px rgba(0, 0, 3, 1); } .no-shadow { text-shadow: none !important; } small { font-size: 70%; }',
142
'originalWidth' => '1920',
143
'originalHeight' => '1080'
145
array( 'template' => '<div class="cell-[itemType] [ShadowType] cell" id="item-[itemId]" style="[Photo]"> <div class="item-container [ShadowType]" style="[Color]"> <div class="item-text">[Tweet]</div> <div class="userData"> <div class="tweet-profilePic">[ProfileImage|normal]</div> <div class="tweet-userData"> <div class="user">[User]</div> <small>[Date]</small></div> </div> </div> </div>',
146
'styleSheet' => 'body { font-family: "Helvetica", "Arial", sans-serif; line-height: 1; margin: 0; } #content { width: 1080px !important; height: 1920px !important; background: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 1); } .row-1 { height: 320px; } .page { float: left; margin: 0; padding: 0; } .cell-1 { width: 350px; } .cell-2 { width: 710px; } .cell-3 { width: 1070px; } .cell-1, .cell-2, .cell-3 { float: left; height: inherit; margin: 5px; background-repeat: no-repeat; background-size: cover; background-position-x: 50%; background-position-y: 50%; } .item-container { padding: 10px; color: #fff; height: 310px; } .userData { height: 50px; } .darken-container { background-color: rgba(0, 0, 0, 0.4); } .tweet-profilePic { width: 20%; float: left; } .tweet-profilePic img { width: 48px; } .tweet-userData { width: 80%; float: left; text-align: right; } .item-text { padding: 10px; color: #fff; } .emojione { width: 26px; height: 26px; } .cell-1 .item-text { line-height: 30px; font-size: 25px; height: 240px; } .cell-2 .item-text { line-height: 40px; font-size: 40px; height: 240px; } .cell-3 .item-text { line-height: 53px; font-size: 50px; height: 240px; } .user { font-size: 14px; font-weight: bold; padding-top: 20px; } .shadow { text-shadow: 1px 1px 2px rgba(0, 0, 3, 1); } .no-shadow { text-shadow: none !important; } small { font-size: 70%; }',
147
'originalWidth' => '1080',
148
'originalHeight' => '1920'
152
return $templateArray[$orientation];
156
* Form for updating the module settings
158
public function settingsForm()
160
return 'twittermetro-form-settings';
163
public function validate()
165
// If overrideColorTemplate is false we have to define a template Id
166
if($this->getOption('overrideColorTemplate') == 0 && ( $this->getOption('colorTemplateId') == '' || $this->getOption('colorTemplateId') == null) )
167
throw new \InvalidArgumentException(__('Please choose a template'));
169
if ($this->getUseDuration() == 1 && $this->getDuration() == 0)
170
throw new \InvalidArgumentException(__('Please enter a duration'));
172
if (!v::stringType()->notEmpty()->validate($this->getOption('searchTerm')))
173
throw new \InvalidArgumentException(__('Please enter a search term'));
179
public function add()
181
$this->setCommonOptions();
191
public function edit()
193
$this->setCommonOptions();
201
* Set common options from Request Params
203
private function setCommonOptions()
205
$this->setDuration($this->getSanitizer()->getInt('duration', $this->getDuration()));
206
$this->setUseDuration($this->getSanitizer()->getCheckbox('useDuration'));
207
$this->setOption('name', $this->getSanitizer()->getString('name'));
208
$this->setOption('searchTerm', $this->getSanitizer()->getString('searchTerm'));
209
$this->setOption('effect', $this->getSanitizer()->getString('effect'));
210
$this->setOption('speed', $this->getSanitizer()->getInt('speed'));
211
$this->setOption('backgroundColor', $this->getSanitizer()->getString('backgroundColor'));
212
$this->setOption('noTweetsMessage', $this->getSanitizer()->getString('noTweetsMessage'));
213
$this->setOption('dateFormat', $this->getSanitizer()->getString('dateFormat'));
214
$this->setOption('resultType', $this->getSanitizer()->getString('resultType'));
215
$this->setOption('tweetDistance', $this->getSanitizer()->getInt('tweetDistance'));
216
$this->setOption('tweetCount', $this->getSanitizer()->getInt('tweetCount', 60));
217
$this->setOption('removeUrls', $this->getSanitizer()->getCheckbox('removeUrls'));
218
$this->setOption('removeMentions', $this->getSanitizer()->getCheckbox('removeMentions'));
219
$this->setOption('removeHashtags', $this->getSanitizer()->getCheckbox('removeHashtags'));
220
$this->setOption('overrideColorTemplate', $this->getSanitizer()->getCheckbox('overrideColorTemplate'));
221
$this->setOption('updateInterval', $this->getSanitizer()->getInt('updateInterval', 60));
222
$this->setOption('colorTemplateId', $this->getSanitizer()->getString('colorTemplateId'));
223
$this->setOption('resultContent', $this->getSanitizer()->getString('resultContent'));
224
$this->setOption('removeRetweets', $this->getSanitizer()->getCheckbox('removeRetweets'));
226
if( $this->getOption('overrideColorTemplate') == 1 ){
228
// Convert the colors array to string to be able to save it
229
$stringColor = $this->getSanitizer()->getStringArray('color')[0];
230
for ($i=1; $i < count($this->getSanitizer()->getStringArray('color')); $i++) {
231
if(!empty($this->getSanitizer()->getStringArray('color')[$i]))
232
$stringColor .= "|" . $this->getSanitizer()->getStringArray('color')[$i];
234
$this->setOption('templateColours', $stringColor);
239
* @param int $displayId
240
* @param bool $isPreview
241
* @return array|false
242
* @throws XiboException
244
protected function getTwitterFeed($displayId = 0, $isPreview = true)
246
if (!extension_loaded('curl'))
247
throw new ConfigurationException(__('cURL extension is required for Twitter'));
249
// Do we need to add a geoCode?
251
$distance = $this->getOption('tweetDistance');
252
if ($distance != 0) {
253
// Use the display ID or the default.
254
if ($displayId != 0) {
255
// Look up the lat/long
256
$display = $this->displayFactory->getById($displayId);
257
$defaultLat = $display->latitude;
258
$defaultLong = $display->longitude;
260
$defaultLat = $this->getConfig()->GetSetting('DEFAULT_LAT');
261
$defaultLong = $this->getConfig()->GetSetting('DEFAULT_LONG');
264
// Built the geoCode string.
265
$geoCode = implode(',', array($defaultLat, $defaultLong, $distance)) . 'mi';
268
// Search content filtered by type of tweets
269
$searchTerm = $this->getOption('searchTerm');
270
$resultContent = $this->getOption('resultContent');
272
switch ($resultContent) {
280
$searchTerm .= ' -filter:media';
284
// Only tweets with native images
285
$searchTerm .= ' filter:twimg';
293
// Search term retweets filter
294
$searchTerm .= ($this->getOption('removeRetweets')) ? ' -filter:retweets' : '';
296
// Connect to twitter and get the twitter feed.
297
/** @var \Stash\Item $cache */
298
$cache = $this->getPool()->getItem($this->makeCacheKey(md5($searchTerm . $this->getOption('resultType') . $this->getOption('tweetCount', 60) . $geoCode)));
299
$cache->setInvalidationMethod(Invalidation::SLEEP, 5000, 15);
301
$data = $cache->get();
303
if ($cache->isMiss()) {
305
$this->getLog()->debug('Querying API for ' . $searchTerm);
307
// Lock this cache item (for 30 seconds)
310
// We need to search for it
311
if (!$token = $this->getToken())
314
// We have the token, make a tweet
315
if (!$data = $this->searchApi($token, $searchTerm, $this->getOption('resultType'), $geoCode, $this->getOption('tweetCount', 60)))
320
$cache->expiresAfter($this->getSetting('cachePeriod', 3600));
321
$this->getPool()->saveDeferred($cache);
324
// Get the template data
325
$templateData = $this->getTemplateData();
326
$template = $this->parseLibraryReferences($isPreview, $templateData['template']);
328
// Parse the text template
330
preg_match_all('/\[.*?\]/', $template, $matches);
332
// Build an array to return
335
// Expiry time for any media that is downloaded
336
$expires = $this->getDate()->parse()->addHours($this->getSetting('cachePeriodImages', 24))->format('U');
338
// Remove URL setting
339
$removeUrls = $this->getOption('removeUrls', 1) == 1;
340
$removeMentions = $this->getOption('removeMentions', 1) == 1;
341
$removeHashTags = $this->getOption('removeHashTags', 1) == 1;
343
// If we have nothing to show, display a no tweets message.
344
if (count($data->statuses) <= 0) {
345
// Create ourselves an empty tweet so that the rest of the code can continue as normal
346
$user = new \stdClass();
348
$user->screen_name = '';
349
$user->profile_image_url = '';
350
$user->location = '';
352
$tweet = new \stdClass();
353
$tweet->full_text = $this->getOption('noTweetsMessage', __('There are no tweets to display'));
354
$tweet->created_at = '';
355
$tweet->user = $user;
357
// Append to our statuses
358
$data->statuses[] = $tweet;
361
// Make an emojione client
362
$emoji = new Client(new Ruleset());
363
$emoji->imageType = 'svg';
364
$emoji->sprites = true;
365
$emoji->imagePathSVGSprites = $this->getResourceUrl('emojione/emojione.sprites.svg');
367
// Get the date format to apply
368
$dateFormat = $this->getOption('dateFormat', $this->getConfig()->GetSetting('DATE_FORMAT'));
370
// This should return the formatted items.
371
foreach ($data->statuses as $tweet) {
372
// Substitute for all matches in the template
373
$rowString = $template;
375
foreach ($matches[0] as $sub) {
376
// Always clear the stored template replacement
378
$tagOptions = array();
380
// Get the options from the tag and create an array
381
$subClean = str_replace('[', '', str_replace(']', '', $sub));
382
if (stripos($subClean, '|') > -1) {
383
$tagOptions = explode('|', $subClean);
386
$subClean = $tagOptions[0];
388
// Remove the tag from the first position
389
array_shift($tagOptions);
392
// Maybe make this more generic?
395
// Get the tweet text to operate on
396
$tweetText = $tweet->full_text;
398
// Replace URLs with their display_url before removal
399
if (isset($tweet->entities->urls)) {
400
foreach ($tweet->entities->urls as $url) {
401
$tweetText = str_replace($url->url, $url->display_url, $tweetText);
405
// Clean up the tweet text
406
// thanks to https://github.com/solarbug (https://github.com/xibosignage/xibo/issues/703)
409
$tweetText = preg_replace('/(\s+|^)@\S+/', '', $tweetText);
413
$tweetText = preg_replace('/(\s+|^)#\S+/', '', $tweetText);
416
// Regex taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
417
$tweetText = preg_replace('~(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))~', '', $tweetText); // remove urls
419
$replace = $emoji->toImage($tweetText);
423
$replace = $emoji->toImage($tweet->user->name);
427
$replace = ($tweet->user->screen_name != '') ? ('@' . $tweet->user->screen_name) : '';
431
if($tweet->created_at != '')
432
$replace = $this->getDate()->getLocalDate(strtotime($tweet->created_at), $dateFormat);
436
$replace = $tweet->user->location;
440
// Grab the profile image
441
if ($tweet->user->profile_image_url != '') {
443
// Original Default Image
445
if( count($tagOptions) > 0 ) {
446
// Image options ( normal, bigger, mini )
447
$imageSizeType = '_' . $tagOptions[0];
450
// Twitter image size
451
$tweet->user->profile_image_url = str_replace('_normal', $imageSizeType, $tweet->user->profile_image_url);
453
// Grab the profile image
454
$file = $this->mediaFactory->queueDownload('twitter_' . $tweet->user->id, $tweet->user->profile_image_url, $expires);
456
$replace = ($isPreview)
457
? '<img src="' . $this->getApp()->urlFor('library.download', ['id' => $file->mediaId, 'type' => 'image']) . '?preview=1" />'
458
: '<img src="' . $file->storedAs . '" />';
463
// See if there is a profile image
464
if (!$this->tweetHasPhoto($tweet)) {
466
// Get the colors array
467
if ($this->getOption('overrideColorTemplate') == 0) {
468
$colorTemplate = $this->getTemplateById($this->getOption('colorTemplateId'));
470
if ($colorTemplate === null)
471
$colorTemplate = $this->getTemplateById('default');
473
$colorArray = $colorTemplate['colors'];
475
$colorArray = explode("|", $this->getOption('templateColours'));
478
// Find a random color
479
$randomNum = rand(0,count($colorArray)-1);
480
$randomColor = $colorArray[$randomNum];
482
$replace = 'background-color:' . $randomColor;
487
// See if there is a profile image
488
$replace = ($this->tweetHasPhoto($tweet)) ? 'shadow darken-container' : '';
492
// See if there are any photos associated with this tweet.
493
if ($this->tweetHasPhoto($tweet)) {
495
// See if it's an image from a tweet or RT, and only take the first one
496
$mediaObject = (isset($tweet->entities->media))
497
? $tweet->entities->media[0]
498
: $tweet->retweeted_status->entities->media[0];
500
$photoUrl = $mediaObject->media_url;
502
if ($photoUrl != '') {
503
$file = $this->mediaFactory->queueDownload('twitter_photo_' . $tweet->user->id . '_' . $mediaObject->id_str, $photoUrl, $expires);
505
$replace = "background-image: url("
506
. (($isPreview) ? $this->getApp()->urlFor('library.download', ['id' => $file->mediaId, 'type' => 'image']) : $file->storedAs)
512
case 'TwitterLogoWhite':
513
//Get the Twitter logo image file path
514
$replace = $this->getResourceUrl('twitter/twitter_white.png');
517
case 'TwitterLogoBlue':
518
//Get the Twitter logo image file path
519
$replace = $this->getResourceUrl('twitter/twitter_blue.png');
523
$replace = '[' . $subClean . ']';
526
$rowString = str_replace($sub, $replace, $rowString);
529
// Substitute the replacement we have found (it might be '')
530
$return[] = $rowString;
533
// Process queued downloads
534
$this->mediaFactory->processDownloads(function($media) {
536
$this->getLog()->debug('Successfully downloaded ' . $media->mediaId);
538
// Tag this layout with this file
539
$this->assignMedia($media->mediaId);
542
// Return the data array
548
* @param int $displayId
550
* @throws XiboException
552
public function getResource($displayId = 0)
554
// Make sure we are set up correctly
555
if ($this->getSetting('apiKey') == '' || $this->getSetting('apiSecret') == '') {
556
$this->getLog()->error('Twitter Module not configured. Missing API Keys');
561
$this->concurrentRequestLock();
564
$isPreview = ($this->getSanitizer()->getCheckbox('preview') == 1);
566
// Replace the View Port Width?
567
$data['viewPortWidth'] = ($isPreview) ? $this->region->width : '[[ViewPortWidth]]';
569
// Information from the Module
570
$duration = $this->getCalculatedDurationForGetResource();
572
// Generate a JSON string of substituted items.
573
$items = $this->getTwitterFeed($displayId, $isPreview);
575
// Get the template data
576
$templateData = $this->getTemplateData();
578
// Return empty string if there are no items to show.
579
if (count($items) == 0)
583
'type' => $this->getModuleType(),
584
'fx' => $this->getOption('effect', 'noAnim'),
585
'speed' => $this->getOption('speed', 500),
586
'duration' => $duration,
587
'numItems' => count($items),
588
'originalWidth' => $this->region->width,
589
'originalHeight' => $this->region->height,
590
'previewWidth' => $this->getSanitizer()->getDouble('width', 0),
591
'previewHeight' => $this->getSanitizer()->getDouble('height', 0),
592
'scaleOverride' => $this->getSanitizer()->getDouble('scale_override', 0),
593
'widgetDesignWidth' => $templateData['originalWidth'],
594
'widgetDesignHeight'=> $templateData['originalHeight'],
595
'resultContent'=> $this->getSanitizer()->string($this->getOption('resultContent'))
598
// Replace the control meta with our data from twitter
599
$data['controlMeta'] = '<!-- NUMITEMS=' . count($items) . ' -->' . PHP_EOL . '<!-- DURATION=' . $duration . ' -->';
601
// Replace the head content
604
// Add our fonts.css file
605
$headContent .= '<link href="' . (($isPreview) ? $this->getApp()->urlFor('library.font.css') : 'fonts.css') . '" rel="stylesheet" media="screen">
606
<link href="' . $this->getResourceUrl('vendor/bootstrap.min.css') . '" rel="stylesheet" media="screen">';
608
$backgroundColor = $this->getOption('backgroundColor');
609
if ($backgroundColor != '') {
610
$headContent .= '<style type="text/css">body { background-color: ' . $backgroundColor . ' }</style>';
612
$headContent .= '<style type="text/css"> body { background-color: transparent }</style>';
615
// Add the CSS if it isn't empty
616
$css = $templateData['styleSheet'];
619
$headContent .= '<style type="text/css">' . $this->parseLibraryReferences($isPreview, $css) . '</style>';
621
$headContent .= '<style type="text/css">' . file_get_contents($this->getConfig()->uri('css/client.css', true)) . '</style>';
623
// Replace the Head Content with our generated javascript
624
$data['head'] = $headContent;
626
// Add some scripts to the JavaScript Content
627
$javaScriptContent = '<script type="text/javascript" src="' . $this->getResourceUrl('vendor/jquery-1.11.1.min.js') . '"></script>';
629
// Get the colors array
630
if ($this->getOption('overrideColorTemplate') == 0) {
631
$template = $this->getTemplateById($this->getOption('colorTemplateId'));
633
if ($template === null)
634
$template = $this->getTemplateById('default');
636
$colorArray = $template['colors'];
638
$colorArray = explode("|", $this->getOption('templateColours'));
641
// Need the cycle plugin?
642
if ($this->getOption('effect') != 'none')
643
$javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('vendor/jquery-cycle-2.1.6.min.js') . '"></script>';
645
$javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('xibo-layout-scaler.js') . '"></script>';
646
$javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('xibo-metro-render.js') . '"></script>';
647
$javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('xibo-image-render.js') . '"></script>';
649
$javaScriptContent .= '<script type="text/javascript">';
650
$javaScriptContent .= ' var options = ' . json_encode($options) . ';';
651
$javaScriptContent .= ' var items = ' . json_encode($items) . ';';
652
$javaScriptContent .= ' var colors = ' . json_encode($colorArray) . ';';
653
$javaScriptContent .= ' $(document).ready(function() { ';
654
$javaScriptContent .= ' $("body").xiboLayoutScaler(options); $("#content").xiboMetroRender(options, items, colors); $("#content").find("img").xiboImageRender(options); $("#content").find(".cell").xiboImageRender(options);';
655
$javaScriptContent .= ' }); ';
656
$javaScriptContent .= '</script>';
658
// Replace the Head Content with our generated javascript
659
$data['javaScript'] = $javaScriptContent;
661
// Update and save widget if we've changed our assignments.
662
if ($this->hasMediaChanged())
663
$this->widget->save(['saveWidgetOptions' => false, 'notify' => false, 'notifyDisplays' => true, 'audit' => false]);
665
$this->concurrentRequestRelease();
667
return $this->renderTemplate($data);
671
public function isValid()
673
// Using the information you have in your module calculate whether it is valid or not.
684
private function tweetHasPhoto($tweet)
686
return ((isset($tweet->entities->media)
687
&& count($tweet->entities->media) > 0)
688
|| (isset($tweet->retweeted_status->entities->media)
689
&& count($tweet->retweeted_status->entities->media) > 0));