~xibo-maintainers/xibo/tempel

« back to all changes in this revision

Viewing changes to lib/Widget/TwitterMetro.php

  • Committer: Dan Garner
  • Date: 2016-06-28 15:02:11 UTC
  • mto: This revision was merged to the branch mainline in revision 528.
  • Revision ID: git-v1:51031805c36c1d366fa330b2c2320d1927c57003
Fixes for upgrade steps

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
<?php
2
 
/*
3
 
 * Xibo - Digital Signage - http://www.xibo.org.uk
4
 
 * Copyright (C) 2014-2015 Daniel Garner
5
 
 *
6
 
 * This file is part of Xibo.
7
 
 *
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
11
 
 * any later version. 
12
 
 *
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.
17
 
 *
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/>.
20
 
 *
21
 
 */
22
 
namespace Xibo\Widget;
23
 
 
24
 
use Emojione\Client;
25
 
use Emojione\Ruleset;
26
 
use Respect\Validation\Validator as v;
27
 
use Xibo\Exception\ConfigurationException;
28
 
use Xibo\Factory\ModuleFactory;
29
 
 
30
 
/**
31
 
 * Class TwitterMetro
32
 
 * @package Xibo\Widget
33
 
 */
34
 
class TwitterMetro extends TwitterBase
35
 
{
36
 
    private $parent;
37
 
    public $codeSchemaVersion = 1;
38
 
    private $resourceFolder;
39
 
 
40
 
    /**
41
 
     * TwitterMetro constructor.
42
 
     */
43
 
    public function init()
44
 
    {
45
 
        $this->resourceFolder = PROJECT_ROOT . '/web/modules/twittermetro';
46
 
 
47
 
        // Initialise extra validation rules
48
 
        v::with('Xibo\\Validation\\Rules\\');
49
 
    }
50
 
    
51
 
    /**
52
 
     * Install or Update this module
53
 
     * @param ModuleFactory $moduleFactory
54
 
     */
55
 
    public function installOrUpdate($moduleFactory)
56
 
    {
57
 
        if ($this->module == null) {
58
 
            // Install
59
 
            $module = $moduleFactory->createEmpty();
60
 
            $module->name = 'Twitter Metro';
61
 
            $module->type = 'twittermetro';
62
 
            $module->class = 'Xibo\Widget\TwitterMetro';
63
 
            $module->description = 'Twitter Metro Search Module';
64
 
            $module->imageUri = 'forms/library.gif';
65
 
            $module->enabled = 1;
66
 
            $module->previewEnabled = 1;
67
 
            $module->assignable = 1;
68
 
            $module->regionSpecific = 1;
69
 
            $module->renderAs = 'html';
70
 
            $module->schemaVersion = $this->codeSchemaVersion;
71
 
            $module->defaultDuration = 60;
72
 
            $module->settings = [];
73
 
 
74
 
            $this->setModule($module);
75
 
            $this->installModule();
76
 
        }
77
 
 
78
 
        // Check we are all installed
79
 
        $this->installFiles();
80
 
    }
81
 
 
82
 
    /**
83
 
     * Install Files
84
 
     */
85
 
    public function installFiles()
86
 
    {
87
 
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/web/modules/vendor/jquery-1.11.1.min.js')->save();
88
 
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/web/modules/xibo-metro-render.js')->save();
89
 
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/web/modules/xibo-layout-scaler.js')->save();
90
 
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/web/modules/xibo-image-render.js')->save();
91
 
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/web/modules/emojione/emojione.sprites.svg')->save();
92
 
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/web/modules/vendor/bootstrap.min.css')->save();
93
 
        
94
 
        foreach ($this->mediaFactory->createModuleFileFromFolder($this->resourceFolder) as $media) {
95
 
            /* @var \Xibo\Entity\Media $media */
96
 
            $media->save();
97
 
        }
98
 
    }
99
 
 
100
 
    /**
101
 
     * Override the apikey/secret
102
 
     * @param string $setting
103
 
     * @param null $default
104
 
     * @return mixed|string
105
 
     */
106
 
    public function getSetting($setting, $default = NULL)
107
 
    {
108
 
        if ($setting == 'apiKey' || $setting == 'apiSecret' || $setting == 'cachePeriod') {
109
 
            // Create a Twitter Module as the source of this modules settings
110
 
            // only go to the stock twitter module
111
 
            if ($this->parent === null)
112
 
                $this->parent = $this->moduleFactory->createByClass('Xibo\Widget\Twitter');
113
 
 
114
 
            return $this->parent->getSetting($setting, $default);
115
 
        }
116
 
 
117
 
        return parent::getSetting($setting, $default);
118
 
    }
119
 
    
120
 
    /**
121
 
     * @return string
122
 
     */
123
 
    public function layoutDesignerJavaScript()
124
 
    {
125
 
        // We use the same javascript as the data set view designer
126
 
        return 'twittermetro-form-javascript';
127
 
    }
128
 
    
129
 
    /**
130
 
     * Get the template HTML, CSS, widgetOriginalWidth, widgetOriginalHeight giving its orientation (0:Landscape 1:Portrait)
131
 
     * @return array
132
 
     */
133
 
    public function getTemplateData() {
134
 
        
135
 
        $orientation = ($this->getSanitizer()->getDouble('width', $this->region->width) > $this->getSanitizer()->getDouble('height', $this->region->height)) ? 0 : 1; 
136
 
        
137
 
        $templateArray = array(
138
 
            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>',
139
 
                    '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%; }',
140
 
                    'originalWidth' => '1920',
141
 
                    'originalHeight' => '1080'
142
 
            ),
143
 
            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>',
144
 
                    '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%; }',
145
 
                    'originalWidth' => '1080',
146
 
                    'originalHeight' => '1920'
147
 
            )
148
 
        );
149
 
        
150
 
        return $templateArray[$orientation];
151
 
    }
152
 
    
153
 
    /**
154
 
    * Loads color templates for this module
155
 
    */
156
 
    private function loadColorTemplates()
157
 
    {
158
 
        $this->module->settings['colortemplates'] = [];
159
 
 
160
 
        // Scan the folder for template files
161
 
        foreach (glob(PROJECT_ROOT . '/modules/twittermetro/*.colortemplate.json') as $template) {
162
 
            // Read the contents, json_decode and add to the array
163
 
            $this->module->settings['colortemplates'][] = json_decode(file_get_contents($template), true);
164
 
        }
165
 
    }
166
 
 
167
 
    /**
168
 
    * Color templates available
169
 
    * @return array
170
 
    */
171
 
    public function colorTemplatesAvailable()
172
 
    {
173
 
        if (!isset($this->module->settings['colortemplates']))
174
 
            $this->loadColorTemplates();
175
 
 
176
 
        return $this->module->settings['colortemplates'];
177
 
    }
178
 
 
179
 
    /**
180
 
     * Form for updating the module settings
181
 
     */
182
 
    public function settingsForm()
183
 
    {
184
 
        return 'twittermetro-form-settings';
185
 
    }
186
 
 
187
 
    public function validate()
188
 
    {
189
 
        // If overrideColorTemplate is false we have to define a template Id 
190
 
        if($this->getOption('overrideColorTemplate') == 0 && ( $this->getOption('colorTemplateId') == '' || $this->getOption('colorTemplateId') == null) )
191
 
            throw new \InvalidArgumentException(__('Please choose a template'));
192
 
            
193
 
        if ($this->getUseDuration() == 1 && $this->getDuration() == 0)
194
 
            throw new \InvalidArgumentException(__('Please enter a duration'));
195
 
 
196
 
        if (!v::string()->notEmpty()->validate($this->getOption('searchTerm')))
197
 
            throw new \InvalidArgumentException(__('Please enter a search term'));
198
 
    }
199
 
 
200
 
    /**
201
 
     * Add Media
202
 
     */
203
 
    public function add()
204
 
    {
205
 
        $this->setCommonOptions();
206
 
 
207
 
        // Save the widget
208
 
        $this->validate();
209
 
        $this->saveWidget();
210
 
    }
211
 
 
212
 
    /**
213
 
     * Edit Media
214
 
     */
215
 
    public function edit()
216
 
    {
217
 
        $this->setCommonOptions();
218
 
 
219
 
        // Save the widget
220
 
        $this->validate();
221
 
        $this->saveWidget();
222
 
    }
223
 
 
224
 
    /**
225
 
     * Set common options from Request Params
226
 
     */
227
 
    private function setCommonOptions()
228
 
    {
229
 
        $this->setDuration($this->getSanitizer()->getInt('duration', $this->getDuration()));
230
 
        $this->setUseDuration($this->getSanitizer()->getCheckbox('useDuration'));
231
 
        $this->setOption('name', $this->getSanitizer()->getString('name'));
232
 
        $this->setOption('searchTerm', $this->getSanitizer()->getString('searchTerm'));
233
 
        $this->setOption('effect', $this->getSanitizer()->getString('effect'));
234
 
        $this->setOption('speed', $this->getSanitizer()->getInt('speed'));
235
 
        $this->setOption('backgroundColor', $this->getSanitizer()->getString('backgroundColor'));
236
 
        $this->setOption('noTweetsMessage', $this->getSanitizer()->getString('noTweetsMessage'));
237
 
        $this->setOption('dateFormat', $this->getSanitizer()->getString('dateFormat'));
238
 
        $this->setOption('resultType', $this->getSanitizer()->getString('resultType'));
239
 
        $this->setOption('tweetDistance', $this->getSanitizer()->getInt('tweetDistance'));
240
 
        $this->setOption('tweetCount', $this->getSanitizer()->getInt('tweetCount', 60));
241
 
        $this->setOption('removeUrls', $this->getSanitizer()->getCheckbox('removeUrls'));
242
 
        $this->setOption('removeMentions', $this->getSanitizer()->getCheckbox('removeMentions'));
243
 
        $this->setOption('removeHashtags', $this->getSanitizer()->getCheckbox('removeHashtags'));
244
 
        $this->setOption('overrideColorTemplate', $this->getSanitizer()->getCheckbox('overrideColorTemplate'));
245
 
        $this->setOption('updateInterval', $this->getSanitizer()->getInt('updateInterval', 60));
246
 
        $this->setOption('colorTemplateId', $this->getSanitizer()->getString('colorTemplateId'));
247
 
        $this->setOption('resultContent', $this->getSanitizer()->getString('resultContent'));
248
 
        $this->setOption('removeRetweets', $this->getSanitizer()->getCheckbox('removeRetweets'));
249
 
        
250
 
        if( $this->getOption('overrideColorTemplate') == 1 ){
251
 
            
252
 
            // Convert the colors array to string to be able to save it
253
 
            $stringColor = $this->getSanitizer()->getStringArray('color')[0];
254
 
            for ($i=1; $i < count($this->getSanitizer()->getStringArray('color')); $i++) {
255
 
                if(!empty($this->getSanitizer()->getStringArray('color')[$i]))
256
 
                    $stringColor .= "|" . $this->getSanitizer()->getStringArray('color')[$i];
257
 
            }
258
 
            $this->setOption('templateColours', $stringColor);
259
 
        }
260
 
    }
261
 
 
262
 
    /**
263
 
     * @param int $displayId
264
 
     * @param bool $isPreview
265
 
     * @return array
266
 
     * @throws ConfigurationException
267
 
     */
268
 
    protected function getTwitterFeed($displayId = 0, $isPreview = true)
269
 
    {
270
 
        if (!extension_loaded('curl'))
271
 
            throw new ConfigurationException(__('cURL extension is required for Twitter'));
272
 
 
273
 
        // Do we need to add a geoCode?
274
 
        $geoCode = '';
275
 
        $distance = $this->getOption('tweetDistance');
276
 
        if ($distance != 0) {
277
 
            // Use the display ID or the default.
278
 
            if ($displayId != 0) {
279
 
                // Look up the lat/long
280
 
                $display = $this->displayFactory->getById($displayId);
281
 
                $defaultLat = $display->latitude;
282
 
                $defaultLong = $display->longitude;
283
 
            } else {
284
 
                $defaultLat = $this->getConfig()->GetSetting('DEFAULT_LAT');
285
 
                $defaultLong = $this->getConfig()->GetSetting('DEFAULT_LONG');
286
 
            }
287
 
 
288
 
            // Built the geoCode string.
289
 
            $geoCode = implode(',', array($defaultLat, $defaultLong, $distance)) . 'mi';
290
 
        }
291
 
        
292
 
        // Search content filtered by type of tweets  
293
 
        $searchTerm = $this->getOption('searchTerm');
294
 
        $resultContent = $this->getOption('resultContent');
295
 
        
296
 
        switch ($resultContent) {
297
 
          case 0:
298
 
            //Default
299
 
            $searchTerm .= '';
300
 
            break;
301
 
            
302
 
          case 1:
303
 
            // Remove media
304
 
            $searchTerm .= ' -filter:media';
305
 
            break;
306
 
            
307
 
          case 2:
308
 
            // Only tweets with native images
309
 
            $searchTerm .= ' filter:twimg';
310
 
            break; 
311
 
               
312
 
          default:
313
 
            $searchTerm .= '';
314
 
            break;
315
 
        }
316
 
        
317
 
        // Search term retweets filter
318
 
        $searchTerm .= ($this->getOption('removeRetweets')) ? ' -filter:retweets' : '';
319
 
        
320
 
        // Connect to twitter and get the twitter feed.
321
 
        $cache = $this->getPool()->getItem(md5($searchTerm . $this->getOption('resultType') . $this->getOption('tweetCount', 60) . $geoCode));
322
 
 
323
 
        $data = $cache->get();
324
 
 
325
 
        if ($cache->isMiss()) {
326
 
 
327
 
            $this->getLog()->debug('Querying API for ' . $searchTerm);
328
 
 
329
 
            // We need to search for it
330
 
            if (!$token = $this->getToken())
331
 
                return false;
332
 
 
333
 
            // We have the token, make a tweet
334
 
            if (!$data = $this->searchApi($token, $searchTerm, $this->getOption('resultType'), $geoCode, $this->getOption('tweetCount', 60)))
335
 
                return false;
336
 
 
337
 
            // Cache it
338
 
            $cache->set($data);
339
 
            $cache->expiresAfter($this->getSetting('cachePeriod', 3600));
340
 
            $this->getPool()->saveDeferred($cache);
341
 
        }
342
 
 
343
 
        // Get the template data
344
 
        $templateData = $this->getTemplateData();
345
 
        $template = $this->parseLibraryReferences($isPreview, $templateData['template']);
346
 
 
347
 
        // Parse the text template
348
 
        $matches = '';
349
 
        preg_match_all('/\[.*?\]/', $template, $matches);
350
 
 
351
 
        // Build an array to return
352
 
        $return = array();
353
 
 
354
 
        // Expiry time for any media that is downloaded
355
 
        $expires = $this->getDate()->parse()->addHours($this->getSetting('cachePeriodImages', 24))->format('U');
356
 
 
357
 
        // Remove URL setting
358
 
        $removeUrls = $this->getOption('removeUrls', 1)  == 1;
359
 
        $removeMentions = $this->getOption('removeMentions', 1)  == 1;
360
 
        $removeHashTags = $this->getOption('removeHashTags', 1)  == 1;
361
 
 
362
 
        // If we have nothing to show, display a no tweets message.
363
 
        if (count($data->statuses) <= 0) {
364
 
            // Create ourselves an empty tweet so that the rest of the code can continue as normal
365
 
            $user = new \stdClass();
366
 
            $user->name = '';
367
 
            $user->screen_name = '';
368
 
            $user->profile_image_url = '';
369
 
            $user->location = '';
370
 
 
371
 
            $tweet = new \stdClass();
372
 
            $tweet->full_text = $this->getOption('noTweetsMessage', __('There are no tweets to display'));
373
 
            $tweet->created_at = '';
374
 
            $tweet->user = $user;
375
 
 
376
 
            // Append to our statuses
377
 
            $data->statuses[] = $tweet;
378
 
        }
379
 
 
380
 
        // Make an emojione client
381
 
        $emoji = new Client(new Ruleset());
382
 
        $emoji->imageType = 'svg';
383
 
        $emoji->sprites = true;
384
 
        $emoji->imagePathSVGSprites = $this->getResourceUrl('emojione/emojione.sprites.svg');
385
 
 
386
 
        // Get the date format to apply
387
 
        $dateFormat = $this->getOption('dateFormat', $this->getConfig()->GetSetting('DATE_FORMAT'));
388
 
 
389
 
        // This should return the formatted items.
390
 
        foreach ($data->statuses as $tweet) {
391
 
            // Substitute for all matches in the template
392
 
            $rowString = $template;
393
 
 
394
 
            foreach ($matches[0] as $sub) {
395
 
                // Always clear the stored template replacement
396
 
                $replace = '';
397
 
                $tagOptions = array();
398
 
                
399
 
                // Get the options from the tag and create an array
400
 
                $subClean = str_replace('[', '', str_replace(']', '', $sub));
401
 
                if (stripos($subClean, '|') > -1) {
402
 
                    $tagOptions = explode('|', $subClean);
403
 
                    
404
 
                    // Save the main tag 
405
 
                    $subClean = $tagOptions[0];
406
 
                    
407
 
                    // Remove the tag from the first position
408
 
                    array_shift($tagOptions);
409
 
                }
410
 
                
411
 
                // Maybe make this more generic?
412
 
                switch ($subClean) {
413
 
                    case 'Tweet':
414
 
                        // Get the tweet text to operate on
415
 
                        $tweetText = $tweet->full_text;
416
 
 
417
 
                        // Replace URLs with their display_url before removal
418
 
                        if (isset($tweet->entities->urls)) {
419
 
                            foreach ($tweet->entities->urls as $url) {
420
 
                                $tweetText = str_replace($url->url, $url->display_url, $tweetText);
421
 
                            }
422
 
                        }
423
 
 
424
 
                        // Clean up the tweet text
425
 
                        // thanks to https://github.com/solarbug (https://github.com/xibosignage/xibo/issues/703)
426
 
                        // Remove Mentions
427
 
                        if ($removeMentions)
428
 
                            $tweetText = preg_replace('/(\s+|^)@\S+/', '', $tweetText);
429
 
 
430
 
                        // Remove HashTags
431
 
                        if ($removeHashTags)
432
 
                            $tweetText = preg_replace('/(\s+|^)#\S+/', '', $tweetText);
433
 
 
434
 
                        if ($removeUrls)
435
 
                            // Regex taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
436
 
                            $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
437
 
 
438
 
                        $replace = $emoji->toImage($tweetText);
439
 
                        break;
440
 
 
441
 
                    case 'User':
442
 
                        $replace = $tweet->user->name;
443
 
                        break;
444
 
 
445
 
                    case 'ScreenName':
446
 
                        $replace = ($tweet->user->screen_name != '') ? ('@' . $tweet->user->screen_name) : '';
447
 
                        break;
448
 
 
449
 
                    case 'Date':
450
 
                        if($tweet->created_at != '')
451
 
                            $replace = $this->getDate()->getLocalDate(strtotime($tweet->created_at), $dateFormat);
452
 
                        break;
453
 
  
454
 
                    case 'Location':
455
 
                        $replace = $tweet->user->location;
456
 
                        break;
457
 
 
458
 
                    case 'ProfileImage':
459
 
                        // Grab the profile image
460
 
                        if ($tweet->user->profile_image_url != '') {
461
 
                            
462
 
                            // Original Default Image
463
 
                            $imageSizeType = "";
464
 
                            if( count($tagOptions) > 0 ) {
465
 
                              // Image options ( normal, bigger, mini )
466
 
                              $imageSizeType = '_' . $tagOptions[0];
467
 
                            }
468
 
                            
469
 
                            // Twitter image size
470
 
                            $tweet->user->profile_image_url = str_replace('_normal', $imageSizeType, $tweet->user->profile_image_url);
471
 
                            
472
 
                            // Grab the profile image
473
 
                            $file = $this->mediaFactory->queueDownload('twitter_' . $tweet->user->id, $tweet->user->profile_image_url, $expires);
474
 
 
475
 
                            // Tag this layout with this file
476
 
                            $this->assignMedia($file->mediaId);
477
 
 
478
 
                            $replace = ($isPreview)
479
 
                                ? '<img src="' . $this->getApp()->urlFor('library.download', ['id' => $file->mediaId, 'type' => 'image']) . '?preview=1" />'
480
 
                                : '<img src="' . $file->storedAs . '"  />';
481
 
                        }
482
 
                        break;
483
 
                        
484
 
                    case 'Color':
485
 
                        // See if there is a profile image
486
 
                        if (!$this->tweetHasPhoto($tweet)) {
487
 
                        
488
 
                            // Get the colors array
489
 
                            if( $this->getOption('overrideColorTemplate') == 0 ) {
490
 
                                
491
 
                                $templates = $this->colorTemplatesAvailable();
492
 
                                
493
 
                                foreach ($templates as $tmplt) {
494
 
                                    if( $tmplt['id'] == $this->getOption('colorTemplateId') ){
495
 
                                        $colorArray = $tmplt['colors'];
496
 
                                    }
497
 
                                }
498
 
                                
499
 
                            } else {
500
 
                                $colorArray = explode("|", $this->getOption('templateColours'));
501
 
                            }
502
 
                            
503
 
                            // Find a random color
504
 
                            $randomNum = rand(0,count($colorArray)-1);
505
 
                            $randomColor = $colorArray[$randomNum];
506
 
                            
507
 
                            $replace = 'background-color:' . $randomColor;
508
 
                        }
509
 
                        break;
510
 
                        
511
 
                    case 'ShadowType':
512
 
                        // See if there is a profile image
513
 
                        $replace = ($this->tweetHasPhoto($tweet)) ? 'shadow darken-container' : '';
514
 
                        break;
515
 
 
516
 
                    case 'Photo':
517
 
                        // See if there are any photos associated with this tweet.
518
 
                        if ($this->tweetHasPhoto($tweet)) {
519
 
                            
520
 
                            // See if it's an image from a tweet or RT, and only take the first one
521
 
                            $mediaObject = (isset($tweet->entities->media))
522
 
                                ? $tweet->entities->media[0]
523
 
                                : $tweet->retweeted_status->entities->media[0];
524
 
                            
525
 
                            $photoUrl = $mediaObject->media_url;
526
 
                            
527
 
                            if ($photoUrl != '') {
528
 
                                $file = $this->mediaFactory->queueDownload('twitter_photo_' . $tweet->user->id . '_' . $mediaObject->id_str, $photoUrl, $expires);
529
 
 
530
 
                                // Tag this layout with this file
531
 
                                $this->assignMedia($file->mediaId);
532
 
 
533
 
                                $replace = "background-image: url(" 
534
 
                                    . (($isPreview) ? $this->getApp()->urlFor('library.download', ['id' => $file->mediaId, 'type' => 'image']) : $file->storedAs)
535
 
                                    . ")";
536
 
                            }
537
 
                        }
538
 
                        break;
539
 
                        
540
 
                    case 'TwitterLogoWhite':
541
 
                        //Get the Twitter logo image file path
542
 
                        $replace = $this->getResourceUrl('twitter/twitter_white.png');
543
 
                        break;
544
 
                        
545
 
                    case 'TwitterLogoBlue':
546
 
                        //Get the Twitter logo image file path
547
 
                        $replace = $this->getResourceUrl('twitter/twitter_blue.png');
548
 
                        break;
549
 
 
550
 
                    default:
551
 
                        $replace = '[' . $subClean . ']';
552
 
                }
553
 
 
554
 
                $rowString = str_replace($sub, $replace, $rowString);
555
 
            }
556
 
 
557
 
            // Substitute the replacement we have found (it might be '')
558
 
            $return[] = $rowString;
559
 
        }
560
 
 
561
 
        // Process the download queue
562
 
        $this->mediaFactory->processDownloads();
563
 
 
564
 
        // Return the data array
565
 
        return $return;
566
 
    }
567
 
 
568
 
    /**
569
 
     * Get Resource
570
 
     * @param int $displayId
571
 
     * @return mixed
572
 
     */
573
 
    public function getResource($displayId = 0)
574
 
    {
575
 
        // Make sure we are set up correctly
576
 
        if ($this->getSetting('apiKey') == '' || $this->getSetting('apiSecret') == '') {
577
 
            $this->getLog()->error('Twitter Module not configured. Missing API Keys');
578
 
            return '';
579
 
        }
580
 
 
581
 
        $data = [];
582
 
        $isPreview = ($this->getSanitizer()->getCheckbox('preview') == 1);
583
 
        
584
 
        // Replace the View Port Width?
585
 
        $data['viewPortWidth'] = ($isPreview) ? $this->region->width : '[[ViewPortWidth]]';
586
 
 
587
 
        // Information from the Module
588
 
        $duration = $this->getCalculatedDurationForGetResource();
589
 
 
590
 
        // Generate a JSON string of substituted items.
591
 
        $items = $this->getTwitterFeed($displayId, $isPreview);
592
 
        
593
 
        // Get the template data
594
 
        $templateData = $this->getTemplateData();
595
 
        
596
 
        // Return empty string if there are no items to show.
597
 
        if (count($items) == 0)
598
 
            return '';
599
 
 
600
 
        $options = array(
601
 
            'type' => $this->getModuleType(),
602
 
            'fx' => $this->getOption('effect', 'noAnim'),
603
 
            'speed' => $this->getOption('speed', 500),
604
 
            'duration' => $duration,
605
 
            'numItems' => count($items),
606
 
            'originalWidth' => $this->region->width,
607
 
            'originalHeight' => $this->region->height,
608
 
            'previewWidth' => $this->getSanitizer()->getDouble('width', 0),
609
 
            'previewHeight' => $this->getSanitizer()->getDouble('height', 0),
610
 
            'scaleOverride' => $this->getSanitizer()->getDouble('scale_override', 0),
611
 
            'widgetDesignWidth' => $templateData['originalWidth'],
612
 
            'widgetDesignHeight'=> $templateData['originalHeight'],
613
 
            'resultContent'=> $this->getSanitizer()->string($this->getOption('resultContent'))            
614
 
        );
615
 
 
616
 
        // Replace the control meta with our data from twitter
617
 
        $data['controlMeta'] = '<!-- NUMITEMS=' . count($items) . ' -->' . PHP_EOL . '<!-- DURATION=' . $duration . ' -->';
618
 
 
619
 
        // Replace the head content
620
 
        $headContent = '';
621
 
 
622
 
        $backgroundColor = $this->getOption('backgroundColor');
623
 
        if ($backgroundColor != '') {
624
 
            $headContent .= '<style type="text/css">body, .page, .item { background-color: ' . $backgroundColor . ' }</style>';
625
 
        }
626
 
 
627
 
        // Add our fonts.css file
628
 
        $headContent .= '<link href="' . (($isPreview) ? $this->getApp()->urlFor('library.font.css') : 'fonts.css') . '" rel="stylesheet" media="screen">
629
 
        <link href="' . $this->getResourceUrl('vendor/bootstrap.min.css')  . '" rel="stylesheet" media="screen">';
630
 
        
631
 
        // Add the CSS if it isn't empty
632
 
        $css = $templateData['styleSheet'];
633
 
        
634
 
        if ($css != '') {
635
 
            $headContent .= '<style type="text/css">' . $this->parseLibraryReferences($isPreview, $css) . '</style>';
636
 
        }
637
 
        $headContent .= '<style type="text/css">' . file_get_contents($this->getConfig()->uri('css/client.css', true)) . '</style>';
638
 
 
639
 
        // Replace the Head Content with our generated javascript
640
 
        $data['head'] = $headContent;
641
 
 
642
 
        // Add some scripts to the JavaScript Content
643
 
        $javaScriptContent = '<script type="text/javascript" src="' . $this->getResourceUrl('vendor/jquery-1.11.1.min.js') . '"></script>';
644
 
 
645
 
        // Get the colors array
646
 
        if( $this->getOption('overrideColorTemplate') == 0 ) {
647
 
            
648
 
            $templates = $this->colorTemplatesAvailable();
649
 
            
650
 
            foreach ($templates as $tmplt) {
651
 
                if( $tmplt['id'] == $this->getOption('colorTemplateId') ){
652
 
                    $colorArray = $tmplt['colors'];
653
 
                }
654
 
            }
655
 
            
656
 
        } else {
657
 
            $colorArray = explode("|", $this->getOption('templateColours'));
658
 
        }
659
 
        
660
 
        // Need the cycle plugin?
661
 
        if ($this->getOption('effect') != 'none')
662
 
            $javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('vendor/jquery-cycle-2.1.6.min.js') . '"></script>';
663
 
 
664
 
        $javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('xibo-layout-scaler.js') . '"></script>';
665
 
        $javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('xibo-metro-render.js') . '"></script>';
666
 
        $javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('xibo-image-render.js') . '"></script>';
667
 
 
668
 
        $javaScriptContent .= '<script type="text/javascript">';
669
 
        $javaScriptContent .= '   var options = ' . json_encode($options) . ';';
670
 
        $javaScriptContent .= '   var items = ' . json_encode($items) . ';';
671
 
        $javaScriptContent .= '   var colors = ' . json_encode($colorArray) . ';';
672
 
        $javaScriptContent .= '   $(document).ready(function() { ';
673
 
        $javaScriptContent .= '       $("body").xiboLayoutScaler(options); $("#content").xiboMetroRender(options, items, colors); $("#content").find("img").xiboImageRender(options); $("#content").find(".cell").xiboImageRender(options);';
674
 
        $javaScriptContent .= '   }); ';
675
 
        $javaScriptContent .= '</script>';
676
 
 
677
 
        // Replace the Head Content with our generated javascript
678
 
        $data['javaScript'] = $javaScriptContent;
679
 
 
680
 
        // Update and save widget if we've changed our assignments.
681
 
        if ($this->hasMediaChanged())
682
 
            $this->widget->save(['saveWidgetOptions' => false, 'notifyDisplays' => true]);
683
 
 
684
 
        return $this->renderTemplate($data);
685
 
    }
686
 
 
687
 
    /** @inheritdoc */
688
 
    public function isValid()
689
 
    {
690
 
        // Using the information you have in your module calculate whether it is valid or not.
691
 
        // 0 = Invalid
692
 
        // 1 = Valid
693
 
        // 2 = Unknown
694
 
        return 1;
695
 
    }
696
 
 
697
 
    /**
698
 
     * @param $tweet
699
 
     * @return bool
700
 
     */
701
 
    private function tweetHasPhoto($tweet)
702
 
    {
703
 
        return ((isset($tweet->entities->media)
704
 
                && count($tweet->entities->media) > 0)
705
 
            || (isset($tweet->retweeted_status->entities->media)
706
 
                && count($tweet->retweeted_status->entities->media) > 0));
707
 
    }
708
 
}