3
* Xibo - Digital Signage - http://www.xibo.org.uk
4
* Copyright (C) 2006-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
use Xibo\Controller\File;
26
use Xibo\Helper\Theme;
28
class ticker extends Module
33
public function InstallFiles()
36
$media->addModuleFile('modules/preview/vendor/jquery-1.11.1.min.js');
37
$media->addModuleFile('modules/preview/vendor/moment.js');
38
$media->addModuleFile('modules/preview/vendor/jquery.marquee.min.js');
39
$media->addModuleFile('modules/preview/vendor/jquery-cycle-2.1.6.min.js');
40
$media->addModuleFile('modules/preview/xibo-layout-scaler.js');
41
$media->addModuleFile('modules/preview/xibo-text-render.js');
45
* Loads templates for this module
47
public function loadTemplates()
49
// Scan the folder for template files
50
foreach (glob('modules/theme/ticker/*.template.json') as $template) {
51
// Read the contents, json_decode and add to the array
52
$this->module->settings['templates'][] = json_decode(file_get_contents($template), true);
55
Log::debug(count($this->module->settings['templates']));
61
public function AddForm()
63
$response = $this->getState();
66
$this->configureForm('AddMedia');
68
// Augment settings with templates
69
$this->loadTemplates();
71
$formFields = array();
72
$formFields[] = Form::AddCombo(
76
array(array('sourceid' => '1', 'source' => __('Feed')), array('sourceid' => '2', 'source' => __('DataSet'))),
79
__('The source for this Ticker'),
82
$formFields[] = Form::AddText('uri', __('Feed URL'), NULL,
83
__('The Link for the RSS feed'), 'f', '', 'feed-fields');
85
$datasets = $this->getUser()->DataSetList();
86
array_unshift($datasets, array('datasetid' => '0', 'dataset' => 'None'));
87
Theme::Set('dataset_field_list', $datasets);
89
$formFields[] = Form::AddCombo(
96
__('Please select the DataSet to use as a source of data for this ticker.'),
97
'd', 'dataset-fields');
99
$formFields[] = Form::AddNumber('duration', __('Duration'), NULL,
100
__('The duration in seconds this should be displayed'), 'd', 'required');
102
Theme::Set('form_fields', $formFields);
104
// Field dependencies
105
$sourceFieldDepencies_1 = array(
106
'.feed-fields' => array('display' => 'block'),
107
'.dataset-fields' => array('display' => 'none'),
110
$sourceFieldDepencies_2 = array(
111
'.feed-fields' => array('display' => 'none'),
112
'.dataset-fields' => array('display' => 'block'),
115
$response->AddFieldAction('sourceid', 'init', 1, $sourceFieldDepencies_1);
116
$response->AddFieldAction('sourceid', 'change', 1, $sourceFieldDepencies_1);
117
$response->AddFieldAction('sourceid', 'init', 2, $sourceFieldDepencies_2);
118
$response->AddFieldAction('sourceid', 'change', 2, $sourceFieldDepencies_2);
121
$response->html = Theme::RenderReturn('form_render');
122
$this->configureFormButtons($response);
123
$response->dialogTitle = __('Add New Ticker');
129
* Return the Edit Form as HTML
131
public function EditForm()
133
$response = $this->getState();
135
// Edit calls are the same as add calls, except you will to check the user has permissions to do the edit
136
if (!$this->auth->edit)
137
throw new Exception(__('You do not have permission to edit this widget.'));
139
// Configure the form
140
$this->configureForm('EditMedia');
142
// Augment settings with templates
143
$this->loadTemplates();
145
$formFields = array();
147
// What is the source for this ticker?
148
$sourceId = $this->GetOption('sourceId');
149
$dataSetId = $this->GetOption('datasetid');
152
$tabs[] = Form::AddTab('general', __('General'));
153
$tabs[] = Form::AddTab('template', __('Appearance'), array(array('name' => 'enlarge', 'value' => true)));
154
$tabs[] = Form::AddTab('format', __('Format'));
155
$tabs[] = Form::AddTab('advanced', __('Advanced'));
156
Theme::Set('form_tabs', $tabs);
158
$field_name = Form::AddText('name', __('Name'), $this->GetOption('name'),
159
__('An optional name for this media'), 'n');
161
$field_duration = Form::AddNumber('duration', __('Duration'), $this->getDuration(),
162
__('The duration in seconds this item should be displayed'), 'd', 'required', '', ($this->auth->modifyPermissions));
165
$oldDirection = $this->GetOption('direction');
167
if ($oldDirection == 'single')
168
$oldDirection = 'fade';
169
else if ($oldDirection != 'none')
170
$oldDirection = 'marquee' . ucfirst($oldDirection);
172
$fieldFx = Form::AddCombo(
175
$this->GetOption('effect', $oldDirection),
177
array('effectid' => 'none', 'effect' => __('None')),
178
array('effectid' => 'fade', 'effect' => __('Fade')),
179
array('effectid' => 'fadeout', 'effect' => __('Fade Out')),
180
array('effectid' => 'scrollHorz', 'effect' => __('Scroll Horizontal')),
181
array('effectid' => 'scrollVert', 'effect' => __('Scroll Vertical')),
182
array('effectid' => 'flipHorz', 'effect' => __('Flip Horizontal')),
183
array('effectid' => 'flipVert', 'effect' => __('Flip Vertical')),
184
array('effectid' => 'shuffle', 'effect' => __('Shuffle')),
185
array('effectid' => 'tileSlide', 'effect' => __('Tile Slide')),
186
array('effectid' => 'tileBlind', 'effect' => __('Tile Blinds')),
187
array('effectid' => 'marqueeLeft', 'effect' => __('Marquee Left')),
188
array('effectid' => 'marqueeRight', 'effect' => __('Marquee Right')),
189
array('effectid' => 'marqueeUp', 'effect' => __('Marquee Up')),
190
array('effectid' => 'marqueeDown', 'effect' => __('Marquee Down')),
194
__('Please select the effect that will be used to transition between items. If all items should be output, select None. Marquee effects are CPU intensive and may not be suitable for lower power displays.'),
197
$fieldScrollSpeed = Form::AddNumber('speed', __('Speed'), $this->GetOption('speed', $this->GetOption('scrollSpeed')),
198
__('The transition speed of the selected effect in milliseconds (normal = 1000) or the Marquee Speed in a low to high scale (normal = 1).'), 's', NULL, 'effect-controls');
200
$fieldBackgroundColor = Form::AddText('backgroundColor', __('Background Colour'), $this->GetOption('backgroundColor'),
201
__('The selected effect works best with a background colour. Optionally add one here.'), 'c', NULL, 'background-color-group');
203
$field_itemsPerPage = Form::AddNumber('itemsPerPage', __('Items per page'), $this->GetOption('itemsPerPage'),
204
__('When in single mode how many items per page should be shown.'), 'p');
206
$field_updateInterval = Form::AddNumber('updateInterval', __('Update Interval (mins)'), $this->GetOption('updateInterval', 5),
207
__('Please enter the update interval in minutes. This should be kept as high as possible. For example, if the data will only change once per hour this could be set to 60.'),
210
$field_durationIsPerItem = Form::AddCheckbox('durationIsPerItem', __('Duration is per item'),
211
$this->GetOption('durationIsPerItem'), __('The duration specified is per item otherwise it is per feed.'),
214
$field_itemsSideBySide = Form::AddCheckbox('itemsSideBySide', __('Show items side by side?'),
215
$this->GetOption('itemsSideBySide'), __('Should items be shown side by side?'),
219
if ($sourceId == 2) {
221
$formFields['general'][] = $field_name;
222
$formFields['general'][] = $field_duration;
223
$formFields['general'][] = $fieldFx;
224
$formFields['general'][] = $fieldScrollSpeed;
225
$formFields['advanced'][] = $fieldBackgroundColor;
226
$formFields['advanced'][] = $field_durationIsPerItem;
227
$formFields['advanced'][] = $field_updateInterval;
229
// Extra Fields for the DataSet
230
$formFields['general'][] = Form::AddText('ordering', __('Order'), $this->GetOption('ordering'),
231
__('Please enter a SQL clause for how this dataset should be ordered'), 'o');
233
$formFields['general'][] = Form::AddText('filter', __('Filter'), $this->GetOption('filter'),
234
__('Please enter a SQL clause to filter this DataSet.'), 'f');
236
$formFields['advanced'][] = Form::AddNumber('lowerLimit', __('Lower Row Limit'), $this->GetOption('lowerLimit'),
237
__('Please enter the Lower Row Limit for this DataSet (enter 0 for no limit)'), 'l');
239
$formFields['advanced'][] = Form::AddNumber('upperLimit', __('Upper Row Limit'), $this->GetOption('upperLimit'),
240
__('Please enter the Upper Row Limit for this DataSet (enter 0 for no limit)'), 'u');
242
$formFields['format'][] = $field_itemsPerPage;
243
$formFields['format'][] = $field_itemsSideBySide;
245
Theme::Set('columns', \Xibo\Storage\PDOConnect::select(sprintf("SELECT DataSetColumnID, Heading FROM datasetcolumn WHERE DataSetID = %d ", $dataSetId), array()));
247
$formFields['template'][] = Form::AddRaw(Theme::RenderReturn('media_form_ticker_dataset_edit'));
250
// Extra Fields for the Ticker
251
$formFields['general'][] = Form::AddText('uri', __('Feed URL'), urldecode($this->GetOption('uri')),
252
__('The Link for the RSS feed'), 'f');
254
$formFields['general'][] = $field_name;
255
$formFields['general'][] = $field_duration;
256
$formFields['general'][] = $fieldFx;
257
$formFields['format'][] = $fieldScrollSpeed;
259
// Add a field for RTL tickers
260
$formFields['format'][] = Form::AddCombo(
262
__('Text direction'),
263
$this->GetOption('textDirection'),
265
array('textdirectionid' => 'ltr', 'textdirection' => __('Left to Right (LTR)')),
266
array('textdirectionid' => 'rtl', 'textdirection' => __('Right to Left (RTL)'))
270
__('Which direction does the text in the feed use? (left to right or right to left)'),
273
$formFields['advanced'][] = $fieldBackgroundColor;
275
$formFields['format'][] = Form::AddNumber('numItems', __('Number of Items'), $this->GetOption('numItems'),
276
__('The Number of RSS items you want to display'), 'o');
278
$formFields['format'][] = $field_itemsPerPage;
280
$formFields['advanced'][] = Form::AddText('copyright', __('Copyright'), $this->GetOption('copyright'),
281
__('Copyright information to display as the last item in this feed. This can be styled with the #copyright CSS selector.'), 'f');
283
$formFields['advanced'][] = $field_updateInterval;
285
$formFields['format'][] = Form::AddCombo(
287
__('Take items from the '),
288
$this->GetOption('takeItemsFrom'),
290
array('takeitemsfromid' => 'start', 'takeitemsfrom' => __('Start of the Feed')),
291
array('takeitemsfromid' => 'end', 'takeitemsfrom' => __('End of the Feed'))
295
__('Take the items from the beginning or the end of the list'),
298
$formFields['format'][] = $field_durationIsPerItem;
299
$formFields['advanced'][] = $field_itemsSideBySide;
301
$formFields['advanced'][] = Form::AddText('dateFormat', __('Date Format'), $this->GetOption('dateFormat'),
302
__('The format to apply to all dates returned by the ticker. In PHP date format: http://uk3.php.net/manual/en/function.date.php'), 'f');
305
array('Substitute' => 'Name'),
306
array('Substitute' => 'Title'),
307
array('Substitute' => 'Description'),
308
array('Substitute' => 'Date'),
309
array('Substitute' => 'Content'),
310
array('Substitute' => 'Copyright'),
311
array('Substitute' => 'Link'),
312
array('Substitute' => 'PermaLink'),
313
array('Substitute' => 'Tag|Namespace')
315
Theme::Set('substitutions', $subs);
317
$formFieldSubs = Form::AddRaw(Theme::RenderReturn('media_form_ticker_edit'));
319
$formFields['advanced'][] = Form::AddText('allowedAttributes', __('Allowable Attributes'), $this->GetOption('allowedAttributes'),
320
__('A comma separated list of attributes that should not be stripped from the incoming feed.'), '');
322
$formFields['advanced'][] = Form::AddText('stripTags', __('Strip Tags'), $this->GetOption('stripTags'),
323
__('A comma separated list of HTML tags that should be stripped from the feed in addition to the default ones.'), '');
325
$formFields['advanced'][] = Form::AddCheckbox('disableDateSort', __('Disable Date Sort'), $this->GetOption('disableDateSort'),
326
__('Should the date sort applied to the feed be disabled?'), '');
328
// Encode up the template
329
//$formFields['advanced'][] = Form::AddMessage('<pre>' . htmlentities(json_encode(array('id' => 'media-rss-with-title', 'value' => 'Image overlaid with the Title', 'template' => '<div class="image">[Link|image]<div class="cycle-overlay"><p style="font-family: Arial, Verdana, sans-serif; font-size:48px;">[Title]</p></div></div>', 'css' => '.image img { width:100%;}.cycle-overlay {color: white;background: black;opacity: .6;filter: alpha(opacity=60);position: absolute;bottom: 0;width: 100%;padding: 15px;text-align:center;}'))) . '</pre>');
333
$formFields['template'][] = Form::AddMultiText('ta_css', NULL, $this->getRawNode('css', null),
334
__('Optional Style sheet'), 's', 10, NULL, 'template-override-controls');
336
// Get the Text Node out of this
337
$formFields['template'][] = Form::AddMultiText('ta_text', NULL, $this->getRawNode('template', null),
338
__('Enter the template. Please note that the background colour has automatically coloured to your layout background colour.'), 't', 10, NULL, 'template-override-controls');
341
if ($this->GetOption('sourceId') == 1) {
343
// Append the templates to the response
344
$response->extra = $this->module->settings['templates'];
346
$formFields['template'][] = $formFieldSubs;
348
// Add a field for whether to override the template or not.
349
// Default to 1 so that it will work correctly with old items (that didn't have a template selected at all)
350
$formFields['template'][] = Form::AddCheckbox('overrideTemplate', __('Override the template?'), $this->GetOption('overrideTemplate', 1),
351
__('Tick if you would like to override the template.'), 'o');
353
// Template - for standard stuff
354
$formFields['template'][] = Form::AddCombo('templateId', __('Template'), $this->GetOption('templateId', 'title-only'),
355
$this->module->settings['templates'],
358
__('Select the template you would like to apply. This can be overridden using the check box below.'), 't', 'template-selector-control');
360
// Add some field dependencies
361
// When the override template check box is ticked, we want to expose the advanced controls and we want to hide the template selector
362
$response->AddFieldAction('overrideTemplate', 'init', false,
364
'.template-override-controls' => array('display' => 'none'),
365
'.template-selector-control' => array('display' => 'block')
367
$response->AddFieldAction('overrideTemplate', 'change', false,
369
'.template-override-controls' => array('display' => 'none'),
370
'.template-selector-control' => array('display' => 'block')
372
$response->AddFieldAction('overrideTemplate', 'init', true,
374
'.template-override-controls' => array('display' => 'block'),
375
'.template-selector-control' => array('display' => 'none')
377
$response->AddFieldAction('overrideTemplate', 'change', true,
379
'.template-override-controls' => array('display' => 'block'),
380
'.template-selector-control' => array('display' => 'none')
384
Theme::Set('form_fields_general', $formFields['general']);
385
Theme::Set('form_fields_template', array_reverse($formFields['template']));
386
Theme::Set('form_fields_format', $formFields['format']);
387
Theme::Set('form_fields_advanced', $formFields['advanced']);
389
// Generate the Response
390
$response->html = Theme::RenderReturn('form_render');
391
$response->callBack = 'text_callback';
392
$this->configureFormButtons($response);
393
$response->dialogTitle = __('Edit Ticker');
394
$this->response->AddButton(__('Apply'), 'XiboDialogApply("#ModuleForm")');
400
* Add Media to the Database
402
public function AddMedia()
404
$response = $this->getState();
407
$sourceId = \Xibo\Helper\Sanitize::getInt('sourceid');
408
$uri = \Kit::GetParam('uri', _POST, _URI);
409
$dataSetId = \Kit::GetParam('datasetid', _POST, _INT, 0);
410
$duration = \Kit::GetParam('duration', _POST, _INT, 0, false);
412
// Must have a duration
414
trigger_error(__('Please enter a duration'), E_USER_ERROR);
416
if ($sourceId == 1) {
420
if ($uri == "" || $uri == "http://")
421
trigger_error(__('Please enter a Link for this Ticker'), E_USER_ERROR);
423
else if ($sourceId == 2) {
426
// Validate Data Set Selected
428
trigger_error(__('Please select a DataSet'), E_USER_ERROR);
430
// Check we have permission to use this DataSetId
431
if (!$this->getUser()->DataSetAuth($dataSetId))
432
trigger_error(__('You do not have permission to use that dataset'), E_USER_ERROR);
435
// Only supported two source types at the moment
436
trigger_error(__('Unknown Source Type'));
440
$this->setDuration(Kit::GetParam('duration', _POST, _INT, $this->getDuration()));
441
$this->SetOption('xmds', true);
442
$this->SetOption('sourceId', $sourceId);
443
$this->SetOption('uri', $uri);
444
$this->SetOption('datasetid', $dataSetId);
445
$this->SetOption('updateInterval', 120);
446
$this->SetOption('speed', 2);
448
// New tickers have template override set to 0 by add.
449
// the edit form can then default to 1 when the element doesn't exist (for legacy)
450
$this->SetOption('overrideTemplate', 0);
452
$this->setRawNode('template', null);
453
$this->setRawNode('css', null);
459
$response->loadForm = true;
460
$response->loadFormUri = 'index.php?p=module&q=Exec&mod=' . $this->getModuleType() . '&method=EditForm®ionId=' . $this->region->regionId . '&widgetId=' . $this->getWidgetId();
466
* Edit Media in the Database
468
public function EditMedia()
470
$response = $this->getState();
472
if (!$this->auth->edit)
473
throw new Exception(__('You do not have permission to edit this widget.'));
475
$sourceId = $this->GetOption('sourceId', 1);
478
$uri = \Kit::GetParam('uri', _POST, _URI);
479
$name = \Xibo\Helper\Sanitize::getString('name');
480
$text = \Kit::GetParam('ta_text', _POST, _HTMLSTRING);
481
$css = \Kit::GetParam('ta_css', _POST, _HTMLSTRING);
482
$updateInterval = \Kit::GetParam('updateInterval', _POST, _INT, 360);
483
$copyright = \Xibo\Helper\Sanitize::getString('copyright');
484
$numItems = \Xibo\Helper\Sanitize::getString('numItems');
485
$takeItemsFrom = \Xibo\Helper\Sanitize::getString('takeItemsFrom');
486
$durationIsPerItem = \Xibo\Helper\Sanitize::getCheckbox('durationIsPerItem');
487
$itemsSideBySide = \Xibo\Helper\Sanitize::getCheckbox('itemsSideBySide');
489
// DataSet Specific Options
490
$itemsPerPage = \Xibo\Helper\Sanitize::getInt('itemsPerPage');
491
$upperLimit = \Xibo\Helper\Sanitize::getInt('upperLimit');
492
$lowerLimit = \Xibo\Helper\Sanitize::getInt('lowerLimit');
493
$filter = \Kit::GetParam('filter', _POST, _STRINGSPECIAL);
494
$ordering = \Xibo\Helper\Sanitize::getString('ordering');
498
throw new InvalidArgumentException(__('Please enter some text'));
500
if ($sourceId == 1) {
504
if ($uri == "" || $uri == "http://")
505
trigger_error(__('Please enter a Link for this Ticker'), E_USER_ERROR);
507
else if ($sourceId == 2) {
508
// Make sure we havent entered a silly value in the filter
509
if (strstr($filter, 'DESC'))
510
trigger_error(__('Cannot user ordering criteria in the Filter Clause'), E_USER_ERROR);
512
if (!is_numeric($upperLimit) || !is_numeric($lowerLimit))
513
trigger_error(__('Limits must be numbers'), E_USER_ERROR);
515
if ($upperLimit < 0 || $lowerLimit < 0)
516
trigger_error(__('Limits cannot be lower than 0'), E_USER_ERROR);
518
// Check the bounds of the limits
519
if ($upperLimit < $lowerLimit)
520
trigger_error(__('Upper limit must be higher than lower limit'), E_USER_ERROR);
523
if ($numItems != '') {
524
// Make sure we have a number in here
525
if (!is_numeric($numItems))
526
throw new InvalidArgumentException(__('The value in Number of Items must be numeric.'));
529
if ($updateInterval < 0)
530
trigger_error(__('Update Interval must be greater than or equal to 0'), E_USER_ERROR);
533
$this->setDuration(Kit::GetParam('duration', _POST, _INT, $this->getDuration(), false));
534
$this->SetOption('xmds', true);
535
$this->SetOption('name', $name);
536
$this->SetOption('effect', \Kit::GetParam('effect', _POST, _STRING));
537
$this->SetOption('copyright', $copyright);
538
$this->SetOption('speed', \Kit::GetParam('speed', _POST, _INT));
539
$this->SetOption('updateInterval', $updateInterval);
540
$this->SetOption('uri', $uri);
541
$this->SetOption('numItems', $numItems);
542
$this->SetOption('takeItemsFrom', $takeItemsFrom);
543
$this->SetOption('durationIsPerItem', $durationIsPerItem);
544
$this->SetOption('itemsSideBySide', $itemsSideBySide);
545
$this->SetOption('upperLimit', $upperLimit);
546
$this->SetOption('lowerLimit', $lowerLimit);
547
$this->SetOption('filter', $filter);
548
$this->SetOption('ordering', $ordering);
549
$this->SetOption('itemsPerPage', $itemsPerPage);
550
$this->SetOption('dateFormat', \Kit::GetParam('dateFormat', _POST, _STRING));
551
$this->SetOption('allowedAttributes', \Kit::GetParam('allowedAttributes', _POST, _STRING));
552
$this->SetOption('stripTags', \Kit::GetParam('stripTags', _POST, _STRING));
553
$this->SetOption('backgroundColor', \Kit::GetParam('backgroundColor', _POST, _STRING));
554
$this->SetOption('disableDateSort', \Kit::GetParam('disableDateSort', _POST, _CHECKBOX));
555
$this->SetOption('textDirection', \Kit::GetParam('textDirection', _POST, _WORD));
556
$this->SetOption('overrideTemplate', \Kit::GetParam('overrideTemplate', _POST, _CHECKBOX));
557
$this->SetOption('templateId', \Kit::GetParam('templateId', _POST, _WORD));
560
$this->setRawNode('template', $text);
561
$this->setRawNode('css', $css);
567
$response->loadForm = true;
568
$response->loadFormUri = $this->getTimelineLink();
569
$this->response->callBack = 'refreshPreview("' . $this->regionid . '")';
574
public function DeleteMedia()
576
// TODO: Links for datasets
578
//$dataSet = new DataSet($this->db);
579
//$dataSet->UnlinkLayout($this->GetOption('datasetid'), $this->layoutid, $this->regionid, $this->mediaid);
581
parent::DeleteMedia();
584
public function HoverPreview()
586
$name = $this->GetOption('name');
587
$url = urldecode($this->GetOption('uri'));
588
$sourceId = $this->GetOption('sourceId', 1);
590
// Default Hover window contains a thumbnail, media type and duration
591
$output = '<div class="thumbnail"><img alt="' . $this->module->name . ' thumbnail" src="theme/default/img/forms/' . $this->getModuleType() . '.gif"></div>';
592
$output .= '<div class="info">';
594
$output .= ' <li>' . __('Type') . ': ' . $this->module->name . '</li>';
595
$output .= ' <li>' . __('Name') . ': ' . $name . '</li>';
598
$output .= ' <li>' . __('Source') . ': DataSet</li>';
600
$output .= ' <li>' . __('Source') . ': <a href="' . $url . '" target="_blank" title="' . __('Source') . '">' . $url . '</a></li>';
603
$output .= ' <li>' . __('Duration') . ': ' . $this->getDuration() . ' ' . __('seconds') . '</li>';
612
* @param int $displayId
615
public function GetResource($displayId = 0)
617
// Load in the template
618
$template = file_get_contents('modules/preview/HtmlTemplate.html');
620
$isPreview = (\Kit::GetParam('preview', _REQUEST, _WORD, 'false') == 'true');
622
// Replace the View Port Width?
624
$template = str_replace('[[ViewPortWidth]]', $this->region->width, $template);
626
// What is the data source for this ticker?
627
$sourceId = $this->GetOption('sourceId', 1);
629
// Information from the Module
630
$itemsSideBySide = $this->GetOption('itemsSideBySide', 0);
631
$duration = $this->getDuration();
632
$durationIsPerItem = $this->GetOption('durationIsPerItem', 0);
633
$numItems = $this->GetOption('numItems', 0);
634
$takeItemsFrom = $this->GetOption('takeItemsFrom', 'start');
635
$itemsPerPage = $this->GetOption('itemsPerPage', 0);
637
// Get the text out of RAW
638
$text = $this->getRawNode('template', null);
641
$css = $this->getRawNode('css', '');
643
// Handle older layouts that have a direction node but no effect node
644
$oldDirection = $this->GetOption('direction', 'none');
646
if ($oldDirection == 'single')
647
$oldDirection = 'fade';
648
else if ($oldDirection != 'none')
649
$oldDirection = 'marquee' . ucfirst($oldDirection);
651
$effect = $this->GetOption('effect', $oldDirection);
654
'type' => $this->getModuleType(),
656
'duration' => $duration,
657
'durationIsPerItem' => (($durationIsPerItem == 0) ? false : true),
658
'numItems' => $numItems,
659
'takeItemsFrom' => $takeItemsFrom,
660
'itemsPerPage' => $itemsPerPage,
661
'speed' => $this->GetOption('speed'),
662
'originalWidth' => $this->region->width,
663
'originalHeight' => $this->region->height,
664
'previewWidth' => \Kit::GetParam('width', _GET, _DOUBLE, 0),
665
'previewHeight' => \Kit::GetParam('height', _GET, _DOUBLE, 0),
666
'scaleOverride' => \Kit::GetParam('scale_override', _GET, _DOUBLE, 0)
669
// Generate a JSON string of substituted items.
670
if ($sourceId == 2) {
671
$items = $this->GetDataSetItems($displayId, $isPreview, $text);
674
$items = $this->GetRssItems($isPreview, $text);
677
// Return empty string if there are no items to show.
678
if (count($items) == 0)
681
// Work out how many pages we will be showing.
684
if ($numItems > count($items) || $numItems == 0)
685
$pages = count($items);
687
$pages = ($itemsPerPage > 0) ? ceil($pages / $itemsPerPage) : $pages;
688
$totalDuration = ($durationIsPerItem == 0) ? $duration : ($duration * $pages);
690
// Replace and Control Meta options
691
$template = str_replace('<!--[[[CONTROLMETA]]]-->', '<!-- NUMITEMS=' . $pages . ' -->' . PHP_EOL . '<!-- DURATION=' . $totalDuration . ' -->', $template);
693
// Replace the head content
696
if ($itemsSideBySide == 1) {
697
$headContent .= '<style type="text/css">';
698
$headContent .= ' .item, .page { float: left; }';
699
$headContent .= '</style>';
702
if ($this->GetOption('textDirection') == 'rtl') {
703
$headContent .= '<style type="text/css">';
704
$headContent .= ' #content { direction: rtl; }';
705
$headContent .= '</style>';
708
if ($this->GetOption('backgroundColor') != '') {
709
$headContent .= '<style type="text/css">';
710
$headContent .= ' body { background-color: ' . $this->GetOption('backgroundColor') . '; }';
711
$headContent .= '</style>';
714
// Add the CSS if it isn't empty
716
$headContent .= '<style type="text/css">' . $css . '</style>';
719
// Add our fonts.css file
720
$headContent .= '<link href="' . (($isPreview) ? 'modules/preview/' : '') . 'fonts.css" rel="stylesheet" media="screen">';
721
$headContent .= '<style type="text/css">' . file_get_contents(Theme::ItemPath('css/client.css')) . '</style>';
723
// Replace the Head Content with our generated javascript
724
$template = str_replace('<!--[[[HEADCONTENT]]]-->', $headContent, $template);
726
// Add some scripts to the JavaScript Content
727
$javaScriptContent = '<script type="text/javascript" src="' . (($isPreview) ? 'modules/preview/vendor/' : '') . 'jquery-1.11.1.min.js"></script>';
729
// Need the marquee plugin?
730
if (stripos($effect, 'marquee') !== false)
731
$javaScriptContent .= '<script type="text/javascript" src="' . (($isPreview) ? 'modules/preview/vendor/' : '') . 'jquery.marquee.min.js"></script>';
733
// Need the cycle plugin?
734
if ($effect != 'none')
735
$javaScriptContent .= '<script type="text/javascript" src="' . (($isPreview) ? 'modules/preview/vendor/' : '') . 'jquery-cycle-2.1.6.min.js"></script>';
737
$javaScriptContent .= '<script type="text/javascript" src="' . (($isPreview) ? 'modules/preview/' : '') . 'xibo-layout-scaler.js"></script>';
738
$javaScriptContent .= '<script type="text/javascript" src="' . (($isPreview) ? 'modules/preview/' : '') . 'xibo-text-render.js"></script>';
740
$javaScriptContent .= '<script type="text/javascript">';
741
$javaScriptContent .= ' var options = ' . json_encode($options) . ';';
742
$javaScriptContent .= ' var items = ' . json_encode($items) . ';';
743
$javaScriptContent .= ' $(document).ready(function() { ';
744
$javaScriptContent .= ' $("body").xiboLayoutScaler(options); $("#content").xiboTextRender(options, items);';
745
$javaScriptContent .= ' }); ';
746
$javaScriptContent .= '</script>';
748
// Replace the Head Content with our generated javascript
749
$template = str_replace('<!--[[[JAVASCRIPTCONTENT]]]-->', $javaScriptContent, $template);
751
// Replace the Body Content with our generated text
752
$template = str_replace('<!--[[[BODYCONTENT]]]-->', '', $template);
757
private function GetRssItems($isPreview, $text)
759
// Make sure we have the cache location configured
761
File::EnsureLibraryExists();
763
// Make sure we have a $media/$layout object to use
764
$media = new Media();
766
// Parse the text template
768
preg_match_all('/\[.*?\]/', $text, $matches);
770
Log::debug('Loading SimplePie to handle RSS parsing.' . urldecode($this->GetOption('uri')) . '. Will substitute items with ' . $text);
772
// Use SimplePie to get the feed
773
include_once('3rdparty/simplepie/autoloader.php');
775
$feed = new SimplePie();
776
$feed->set_cache_location($file->GetLibraryCacheUri());
777
$feed->set_feed_url(urldecode($this->GetOption('uri')));
778
$feed->force_feed(true);
779
$feed->set_cache_duration(($this->GetOption('updateInterval', 3600) * 60));
780
$feed->handle_content_type();
782
// Get a list of allowed attributes
783
if ($this->GetOption('allowedAttributes') != '') {
784
$attrsStrip = array_diff($feed->strip_attributes, explode(',', $this->GetOption('allowedAttributes')));
785
//Debug::Audit(var_export($attrsStrip, true));
786
$feed->strip_attributes($attrsStrip);
789
// Disable date sorting?
790
if ($this->GetOption('disableDateSort') == 1) {
791
$feed->enable_order_by_date(false);
797
$dateFormat = $this->GetOption('dateFormat');
799
if ($feed->error()) {
800
Log::notice('Feed Error: ' . $feed->error());
804
// Set an expiry time for the media
805
$expires = time() + ($this->GetOption('updateInterval', 3600) * 60);
807
// Store our formatted items
810
foreach ($feed->get_items() as $item) {
811
/* @var SimplePie_Item $item */
813
// Substitute for all matches in the template
817
foreach ($matches[0] as $sub) {
820
// Pick the appropriate column out
821
if (strstr($sub, '|') !== false) {
822
// Use the provided name space to extract a tag
824
if (substr_count($sub, '|') > 1)
825
list($tag, $namespace, $attributes) = explode('|', $sub);
827
list($tag, $namespace) = explode('|', $sub);
829
// What are we looking at
830
Log::debug('Namespace: ' . str_replace(']', '', $namespace) . '. Tag: ' . str_replace('[', '', $tag) . '. ');
832
// Are we an image place holder?
833
if (strstr($namespace, 'image') != false) {
834
// Try to get a link for the image
837
switch (str_replace('[', '', $tag)) {
839
if ($enclosure = $item->get_enclosure()) {
840
// Use the link to get the image
841
$link = $enclosure->get_link();
846
// Default behaviour just tries to get the content from the tag provided (without a name space).
847
$tags = $item->get_item_tags('', str_replace('[', '', $tag));
850
$link = (is_array($tags)) ? $tags[0]['data'] : '';
854
// If we have managed to resolve a link, download it and replace the tag with the downloaded
857
// Grab the profile image
858
$file = $media->addModuleFileFromUrl($link, 'ticker_' . md5($this->GetOption('url') . $link), $expires);
860
// Tag this layout with this file
861
$this->assignMedia($file['mediaId']);
863
$replace = ($isPreview) ? '<img src="index.php?p=index.php?p=content&q=getFile&mediaid=' . $file['mediaId'] . '" ' . $attributes . '/>' : '<img src="' . $file['storedAs'] . '" ' . $attributes . ' />';
867
$tags = $item->get_item_tags(str_replace(']', '', $namespace), str_replace('[', '', $tag));
869
Log::notice('Tags:' . var_export($tags, true));
871
// If we find some tags then do the business with them
873
if ($attributes != NULL)
874
$replace = (is_array($tags)) ? $tags[0]['attribs'][''][str_replace(']', '', $attributes)] : '';
876
$replace = (is_array($tags)) ? $tags[0]['data'] : '';
882
// Use the pool of standard tags
885
$replace = $this->GetOption('name');
889
$replace = $item->get_title();
892
case '[Description]':
893
$replace = $item->get_description();
897
$replace = $item->get_content();
901
$replace = $item->get_copyright();
905
$replace = Date::getLocalDate($item->get_date('U'), $dateFormat);
909
$replace = $item->get_permalink();
913
$replace = $item->get_link();
918
if ($this->GetOption('stripTags') != '') {
919
require_once '3rdparty/htmlpurifier/library/HTMLPurifier.auto.php';
921
$config = HTMLPurifier_Config::createDefault();
922
$config->set('HTML.ForbiddenElements', array_merge($feed->strip_htmltags, explode(',', $this->GetOption('stripTags'))));
923
$purifier = new HTMLPurifier($config);
924
$replace = $purifier->purify($replace);
927
// Substitute the replacement we have found (it might be '')
928
$rowString = str_replace($sub, $replace, $rowString);
931
$items[] = $rowString;
934
// Copyright information?
935
if ($this->GetOption('copyright', '') != '') {
936
$items[] = '<span id="copyright">' . $this->GetOption('copyright') . '</span>';
939
// Return the formatted items
943
private function GetDataSetItems($displayId, $isPreview, $text)
945
// Extra fields for data sets
946
$dataSetId = $this->GetOption('datasetid');
947
$upperLimit = $this->GetOption('upperLimit');
948
$lowerLimit = $this->GetOption('lowerLimit');
949
$filter = $this->GetOption('filter');
950
$ordering = $this->GetOption('ordering');
952
Log::notice('Then template for each row is: ' . $text);
954
// Set an expiry time for the media
955
$media = new Media();
956
$layout = new Layout();
957
$expires = time() + ($this->GetOption('updateInterval', 3600) * 60);
959
// Combine the column id's with the dataset data
961
preg_match_all('/\[(.*?)\]/', $text, $matches);
963
$columnIds = array();
965
foreach ($matches[1] as $match) {
966
// Get the column id's we are interested in
967
Log::notice('Matched column: ' . $match);
969
$col = explode('|', $match);
970
$columnIds[] = $col[1];
973
// Get the dataset results
974
$dataSet = new DataSet();
975
if (!$dataSetResults = $dataSet->DataSetResults($dataSetId, implode(',', $columnIds), $filter, $ordering, $lowerLimit, $upperLimit, $displayId)) {
979
// Create an array of header|datatypeid pairs
980
$columnMap = array();
981
foreach ($dataSetResults['Columns'] as $col) {
982
$columnMap[$col['Text']] = $col;
985
Log::debug(var_export($columnMap, true));
989
foreach ($dataSetResults['Rows'] as $row) {
990
// For each row, substitute into our template
993
foreach ($matches[1] as $sub) {
994
// Pick the appropriate column out
995
$subs = explode('|', $sub);
999
$replace = $row[$header];
1001
// Check in the columns array to see if this is a special one
1002
if ($columnMap[$header]['DataTypeID'] == 4) {
1003
// Download the image, alter the replace to wrap in an image tag
1004
$file = $media->addModuleFileFromUrl(str_replace(' ', '%20', htmlspecialchars_decode($replace)), 'ticker_dataset_' . md5($dataSetId . $columnMap[$header]['DataSetColumnID'] . $replace), $expires);
1006
// Tag this layout with this file
1007
$this->assignMedia($file['mediaId']);
1009
$replace = ($isPreview) ? '<img src="index.php?index.php?p=content&q=getFile&mediaid=' . $file['mediaId'] . '" />' : '<img src="' . $file['storedAs'] . '" />';
1012
$rowString = str_replace('[' . $sub . ']', $replace, $rowString);
1015
$items[] = $rowString;
1021
public function IsValid()
1023
// Can't be sure because the client does the rendering