~xibo-maintainers/xibo/tempel

« back to all changes in this revision

Viewing changes to lib/Widget/Currencies.php

  • Committer: Dan Garner
  • Date: 2015-08-11 09:29:02 UTC
  • mto: This revision was merged to the branch mainline in revision 453.
  • Revision ID: git-v1:a86fb4369b7395c13367577d23b14c0ab4528c1a
Transitions fixes.

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 GuzzleHttp\Exception\RequestException;
25
 
use Stash\Invalidation;
26
 
use Xibo\Exception\InvalidArgumentException;
27
 
use Xibo\Exception\NotFoundException;
28
 
use Xibo\Factory\ModuleFactory;
29
 
 
30
 
/**
31
 
 * Class Currencies
32
 
 * @package Xibo\Widget
33
 
 */
34
 
class Currencies extends AlphaVantageBase
35
 
{
36
 
    public $codeSchemaVersion = 1;
37
 
 
38
 
    /**
39
 
     * Install or Update this module
40
 
     * @param ModuleFactory $moduleFactory
41
 
     */
42
 
    public function installOrUpdate($moduleFactory)
43
 
    {
44
 
        if ($this->module == null) {
45
 
            // Install
46
 
            $module = $moduleFactory->createEmpty();
47
 
            $module->name = 'Currencies';
48
 
            $module->type = 'currencies';
49
 
            $module->class = 'Xibo\Widget\Currencies';
50
 
            $module->description = 'A module for showing Currency pairs and exchange rates';
51
 
            $module->imageUri = 'forms/library.gif';
52
 
            $module->enabled = 1;
53
 
            $module->previewEnabled = 1;
54
 
            $module->assignable = 1;
55
 
            $module->regionSpecific = 1;
56
 
            $module->renderAs = 'html';
57
 
            $module->schemaVersion = $this->codeSchemaVersion;
58
 
            $module->defaultDuration = 30;
59
 
            $module->settings = [];
60
 
 
61
 
            $this->setModule($module);
62
 
            $this->installModule();
63
 
        }
64
 
 
65
 
        // Check we are all installed
66
 
        $this->installFiles();
67
 
    }
68
 
 
69
 
    /**
70
 
     * Install Files
71
 
     */
72
 
    public function installFiles()
73
 
    {
74
 
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/vendor/jquery-1.11.1.min.js')->save();
75
 
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/xibo-finance-render.js')->save();
76
 
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/xibo-layout-scaler.js')->save();
77
 
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/xibo-image-render.js')->save();
78
 
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/modules/vendor/bootstrap.min.css')->save();
79
 
    }
80
 
 
81
 
    /**
82
 
     * Form for updating the module settings
83
 
     */
84
 
    public function settingsForm()
85
 
    {
86
 
        return 'currencies-form-settings';
87
 
    }
88
 
 
89
 
    /**
90
 
     * Process any module settings
91
 
     */
92
 
    public function settings()
93
 
    {
94
 
        $this->module->settings['apiKey'] = $this->getSanitizer()->getString('apiKey');
95
 
        $this->module->settings['cachePeriod'] = $this->getSanitizer()->getInt('cachePeriod', 300);
96
 
 
97
 
        // Return an array of the processed settings.
98
 
        return $this->module->settings;
99
 
    }
100
 
 
101
 
    /**
102
 
     * Validate
103
 
     * @throws InvalidArgumentException
104
 
     */
105
 
    public function validate()
106
 
    {
107
 
        if($this->getOption('overrideTemplate') == 0 && ( $this->getOption('templateId') == '' || $this->getOption('templateId') == null) )
108
 
            throw new InvalidArgumentException(__('Please choose a template'), 'templateId');
109
 
            
110
 
        if ($this->getUseDuration() == 1 && $this->getDuration() == 0)
111
 
            throw new InvalidArgumentException(__('Please enter a duration'), 'duration');
112
 
 
113
 
        // Validate for the items field
114
 
        if ($this->getOption('items') == '')
115
 
            throw new InvalidArgumentException(__('Please provide a comma separated list of symbols in the items field.'), 'items');
116
 
 
117
 
        if ($this->getOption('base') == '')
118
 
            throw new InvalidArgumentException(__('Please provide a symbols in the base field.'), 'base');
119
 
    }
120
 
 
121
 
    /**
122
 
     * Adds a Currencies Widget
123
 
     * @SWG\Post(
124
 
     *  path="/playlist/widget/currencies/{playlistId}",
125
 
     *  operationId="WidgetCurrenciesAdd",
126
 
     *  tags={"widget"},
127
 
     *  summary="Add a Currencies Widget",
128
 
     *  description="Add a new Currencies Widget to the specified playlist",
129
 
     *  @SWG\Parameter(
130
 
     *      name="playlistId",
131
 
     *      in="path",
132
 
     *      description="The playlist ID to add a Currencies widget",
133
 
     *      type="integer",
134
 
     *      required=true
135
 
     *   ),
136
 
     *  @SWG\Parameter(
137
 
     *      name="name",
138
 
     *      in="formData",
139
 
     *      description="Optional Widget Name",
140
 
     *      type="string",
141
 
     *      required=false
142
 
     *  ),
143
 
     *  @SWG\Parameter(
144
 
     *      name="duration",
145
 
     *      in="formData",
146
 
     *      description="Widget Duration",
147
 
     *      type="integer",
148
 
     *      required=false
149
 
     *  ),
150
 
     *  @SWG\Parameter(
151
 
     *      name="useDuration",
152
 
     *      in="formData",
153
 
     *      description="(0, 1) Select 1 only if you will provide duration parameter as well",
154
 
     *      type="integer",
155
 
     *      required=false
156
 
     *  ),
157
 
     *  @SWG\Parameter(
158
 
     *      name="base",
159
 
     *      in="formData",
160
 
     *      description="The base currency",
161
 
     *      type="string",
162
 
     *      required=true
163
 
     *   ),
164
 
     *  @SWG\Parameter(
165
 
     *      name="items",
166
 
     *      in="formData",
167
 
     *      description="Items wanted",
168
 
     *      type="string",
169
 
     *      required=true
170
 
     *   ),
171
 
     *  @SWG\Parameter(
172
 
     *      name="reverseConversion",
173
 
     *      in="formData",
174
 
     *      description="(0, 1) Select 1 if you'd like your base currency to be used as the comparison currency you've entered",
175
 
     *      type="integer",
176
 
     *      required=false
177
 
     *  ),
178
 
     *  @SWG\Parameter(
179
 
     *      name="effect",
180
 
     *      in="formData",
181
 
     *      description="Effect that will be used to transitions between items, available options: fade, fadeout, scrollVert, scollHorz, flipVert, flipHorz, shuffle, tileSlide, tileBlind ",
182
 
     *      type="string",
183
 
     *      required=false
184
 
     *   ),
185
 
     *  @SWG\Parameter(
186
 
     *      name="speed",
187
 
     *      in="formData",
188
 
     *      description="The transition speed of the selected effect in milliseconds (1000 = normal)",
189
 
     *      type="integer",
190
 
     *      required=false
191
 
     *   ),
192
 
     *  @SWG\Parameter(
193
 
     *      name="backgroundColor",
194
 
     *      in="formData",
195
 
     *      description="A HEX color to use as the background color of this widget",
196
 
     *      type="string",
197
 
     *      required=false
198
 
     *   ),
199
 
     *  @SWG\Parameter(
200
 
     *      name="noRecordsMessage",
201
 
     *      in="formData",
202
 
     *      description="A message to display when there are no records returned by the search query",
203
 
     *      type="string",
204
 
     *      required=false
205
 
     *   ),
206
 
     *  @SWG\Parameter(
207
 
     *      name="dateFormat",
208
 
     *      in="formData",
209
 
     *      description="The format to apply to all dates returned by he widget",
210
 
     *      type="string",
211
 
     *      required=false
212
 
     *   ),
213
 
     *  @SWG\Parameter(
214
 
     *      name="updateInterval",
215
 
     *      in="formData",
216
 
     *      description="Update interval in minutes, should be kept as high as possible, if data change once per hour, this should be set to 60",
217
 
     *      type="integer",
218
 
     *      required=false
219
 
     *   ),
220
 
     *  @SWG\Parameter(
221
 
     *      name="durationIsPerPage",
222
 
     *      in="formData",
223
 
     *      description="A flag (0, 1), The duration specified is per page/item, otherwise the widget duration is divided between the number of pages/items",
224
 
     *      type="integer",
225
 
     *      required=false
226
 
     *   ),
227
 
     *  @SWG\Parameter(
228
 
     *      name="templateId",
229
 
     *      in="formData",
230
 
     *      description="Use pre-configured templates, available options: currencies1, currencies2",
231
 
     *      type="string",
232
 
     *      required=false
233
 
     *   ),
234
 
     *  @SWG\Parameter(
235
 
     *      name="overrideTemplate",
236
 
     *      in="formData",
237
 
     *      description="flag (0, 1) set to 0 and use templateId or set to 1 and provide whole template in the next parameters",
238
 
     *      type="integer",
239
 
     *      required=false
240
 
     *   ),
241
 
     *  @SWG\Parameter(
242
 
     *      name="widgetOriginalWidth",
243
 
     *      in="formData",
244
 
     *      description="This is the intended Width of the template and is used to scale the Widget within it's region when the template is applied, Pass only with overrideTemplate set to 1",
245
 
     *      type="integer",
246
 
     *      required=false
247
 
     *   ),
248
 
     *  @SWG\Parameter(
249
 
     *      name="widgetOriginalHeight",
250
 
     *      in="formData",
251
 
     *      description="This is the intended Height of the template and is used to scale the Widget within it's region when the template is applied, Pass only with overrideTemplate set to 1",
252
 
     *      type="integer",
253
 
     *      required=false
254
 
     *   ),
255
 
     *  @SWG\Parameter(
256
 
     *      name="maxItemsPerPage",
257
 
     *      in="formData",
258
 
     *      description="This is the intended number of items on each page",
259
 
     *      type="integer",
260
 
     *      required=false
261
 
     *   ),
262
 
     *  @SWG\Parameter(
263
 
     *      name="mainTemplate",
264
 
     *      in="formData",
265
 
     *      description="Main template, Pass only with overrideTemplate set to 1 ",
266
 
     *      type="string",
267
 
     *      required=false
268
 
     *   ),
269
 
     *  @SWG\Parameter(
270
 
     *      name="itemtemplate",
271
 
     *      in="formData",
272
 
     *      description="Template for each item, replaces [itemsTemplate] in main template, Pass only with overrideTemplate set to 1 ",
273
 
     *      type="string",
274
 
     *      required=false
275
 
     *   ),
276
 
     *  @SWG\Parameter(
277
 
     *      name="styleSheet",
278
 
     *      in="formData",
279
 
     *      description="Optional StyleSheet Pass only with overrideTemplate set to 1 ",
280
 
     *      type="string",
281
 
     *      required=false
282
 
     *   ),
283
 
     *  @SWG\Parameter(
284
 
     *      name="javaScript",
285
 
     *      in="formData",
286
 
     *      description="Optional JavaScript, Pass only with overrideTemplate set to 1 ",
287
 
     *      type="string",
288
 
     *      required=false
289
 
     *   ),
290
 
     *  @SWG\Response(
291
 
     *      response=201,
292
 
     *      description="successful operation",
293
 
     *      @SWG\Schema(ref="#/definitions/Widget"),
294
 
     *      @SWG\Header(
295
 
     *          header="Location",
296
 
     *          description="Location of the new widget",
297
 
     *          type="string"
298
 
     *      )
299
 
     *  )
300
 
     * )
301
 
     */
302
 
    public function add()
303
 
    {
304
 
        $this->setCommonOptions();
305
 
 
306
 
        // Save the widget
307
 
        $this->validate();
308
 
        $this->saveWidget();
309
 
    }
310
 
 
311
 
    /**
312
 
     * Edit Media
313
 
     */
314
 
    public function edit()
315
 
    {
316
 
        $this->setCommonOptions();
317
 
 
318
 
        // Save the widget
319
 
        $this->validate();
320
 
        $this->saveWidget();
321
 
    }
322
 
 
323
 
    public function setCommonOptions()
324
 
    {
325
 
        $this->setDuration($this->getSanitizer()->getInt('duration', $this->getDuration()));
326
 
        $this->setUseDuration($this->getSanitizer()->getCheckbox('useDuration'));
327
 
        $this->setOption('name', $this->getSanitizer()->getString('name'));
328
 
        $this->setOption('base', $this->getSanitizer()->getString('base', ''));
329
 
        $this->setOption('items', $this->getSanitizer()->getString('items'));
330
 
        $this->setOption('effect', $this->getSanitizer()->getString('effect'));
331
 
        $this->setOption('speed', $this->getSanitizer()->getInt('speed'));
332
 
        $this->setOption('backgroundColor', $this->getSanitizer()->getString('backgroundColor'));
333
 
        $this->setOption('noRecordsMessage', $this->getSanitizer()->getString('noRecordsMessage'));
334
 
        $this->setOption('dateFormat', $this->getSanitizer()->getString('dateFormat'));
335
 
        $this->setOption('reverseConversion', $this->getSanitizer()->getCheckbox('reverseConversion'));
336
 
        $this->setOption('updateInterval', $this->getSanitizer()->getInt('updateInterval', 60));
337
 
        $this->setOption('templateId', $this->getSanitizer()->getString('templateId'));
338
 
        $this->setOption('durationIsPerPage', $this->getSanitizer()->getCheckbox('durationIsPerPage'));
339
 
        $this->setRawNode('javaScript', $this->getSanitizer()->getParam('javaScript', ''));
340
 
        $this->setOption('overrideTemplate', $this->getSanitizer()->getCheckbox('overrideTemplate'));
341
 
        
342
 
        if( $this->getOption('overrideTemplate') == 1 ){
343
 
            $this->setRawNode('mainTemplate', $this->getSanitizer()->getParam('mainTemplate', $this->getSanitizer()->getParam('mainTemplate', null)));
344
 
            $this->setRawNode('itemTemplate', $this->getSanitizer()->getParam('itemTemplate', $this->getSanitizer()->getParam('itemTemplate', null)));
345
 
            $this->setRawNode('styleSheet', $this->getSanitizer()->getParam('styleSheet', $this->getSanitizer()->getParam('styleSheet', null)));
346
 
            $this->setOption('widgetOriginalWidth', $this->getSanitizer()->getInt('widgetOriginalWidth'));
347
 
            $this->setOption('widgetOriginalHeight', $this->getSanitizer()->getInt('widgetOriginalHeight'));
348
 
            $this->setOption('maxItemsPerPage', $this->getSanitizer()->getInt('maxItemsPerPage', 4));
349
 
        }
350
 
    }
351
 
 
352
 
    /**
353
 
     * Get Results
354
 
     * @return array|bool an array of results. false if an invalid value is returned.
355
 
     */
356
 
    protected function getResults()
357
 
    {
358
 
        // Does this require a reversed conversion?
359
 
        $reverseConversion = ($this->getOption('reverseConversion', 0) == 1);
360
 
 
361
 
        // What items/base currencies are we interested in?
362
 
        $items = $this->getOption('items');
363
 
        $base = $this->getOption('base');
364
 
 
365
 
        if ($items == '' || $base == '' ) {
366
 
            $this->getLog()->error('Missing Items for Currencies Module with WidgetId ' . $this->getWidgetId());
367
 
            return false;
368
 
        }
369
 
 
370
 
        // Parse items out into an array
371
 
        $items = explode(',', $items);
372
 
        
373
 
        // Get current item template
374
 
        $itemTemplate = null;
375
 
 
376
 
        if ($this->getOption('overrideTemplate') == 0) {
377
 
            $template = $this->getTemplateById($this->getOption('templateId'));
378
 
            
379
 
            if (isset($template)) {
380
 
                $itemTemplate = $template['item'];
381
 
            }    
382
 
        } else {
383
 
            $itemTemplate = $this->getRawNode('itemTemplate');
384
 
        }
385
 
        
386
 
        // Does the template require a percentage change calculation.
387
 
        $percentageChangeRequested = stripos($itemTemplate, '[ChangePercentage]') > -1;
388
 
 
389
 
        // Our cache key is based on the base/items/changepercentage
390
 
        /** @var \Stash\Item $cache */
391
 
        $cache = $this->getPool()->getItem($this->makeCacheKey(md5($base . implode(',', $items) . $percentageChangeRequested . $reverseConversion)));
392
 
        $cache->setInvalidationMethod(Invalidation::SLEEP, 5000, 15);
393
 
 
394
 
        $data = $cache->get();
395
 
 
396
 
        if ($cache->isMiss()) {
397
 
            // Lock this cache record
398
 
            $cache->lock();
399
 
 
400
 
            // Start fresh
401
 
            $data = [];
402
 
            $priorDay = [];
403
 
 
404
 
            // Do we need to get the data for percentage change?
405
 
            if ($percentageChangeRequested && !$reverseConversion) {
406
 
                try {
407
 
                    // Get the prior day
408
 
                    $priorDay = $this->getPriorDay($base, $items);
409
 
 
410
 
                    $this->getLog()->debug('Percentage change requested, prior day is ' . var_export($priorDay, true));
411
 
 
412
 
                } catch (RequestException $requestException) {
413
 
                    $this->getLog()->error('Problem getting percentage change currency information. E = ' . $requestException->getMessage());
414
 
                    $this->getLog()->debug($requestException->getTraceAsString());
415
 
                }
416
 
            }
417
 
 
418
 
            // Each item we want is a call to the results API
419
 
            try {
420
 
                foreach ($items as $currency) {
421
 
                    // Remove the multiplier if there's one (this is handled when we substitute the results into the template)
422
 
                    $currency = explode('|', $currency)[0];
423
 
 
424
 
                    // Do we need to reverse the from/to currency for this comparison?
425
 
                    if ($reverseConversion) {
426
 
                        $result = $this->getCurrencyExchangeRate($currency, $base);
427
 
 
428
 
                        // We need to get the proir day for this pair only (reversed)
429
 
                        $priorDay = $this->getPriorDay($currency, $base);
430
 
 
431
 
                        $this->getLog()->debug('Percentage change requested, prior day is ' . var_export($priorDay, true));
432
 
 
433
 
                    } else {
434
 
                        $result = $this->getCurrencyExchangeRate($base, $currency);
435
 
                    }
436
 
 
437
 
                    $this->getLog()->debug('Results are: ' . var_export($result, true));
438
 
 
439
 
                    $parsedResult = [
440
 
                        'time' => $result['Realtime Currency Exchange Rate']['6. Last Refreshed'],
441
 
                        'ToName' => $result['Realtime Currency Exchange Rate']['3. To_Currency Code'],
442
 
                        'ToCurrency' => $result['Realtime Currency Exchange Rate']['4. To_Currency Name'],
443
 
                        'FromName' => $result['Realtime Currency Exchange Rate']['1. From_Currency Code'],
444
 
                        'FromCurrency' => $result['Realtime Currency Exchange Rate']['2. From_Currency Name'],
445
 
                        'Bid' => round($result['Realtime Currency Exchange Rate']['5. Exchange Rate'], 4),
446
 
                        'Ask' => round($result['Realtime Currency Exchange Rate']['5. Exchange Rate'], 4),
447
 
                        'LastTradePriceOnly' => round($result['Realtime Currency Exchange Rate']['5. Exchange Rate'], 4),
448
 
                        'RawLastTradePriceOnly' => $result['Realtime Currency Exchange Rate']['5. Exchange Rate'],
449
 
                        'TimeZone' => $result['Realtime Currency Exchange Rate']['7. Time Zone'],
450
 
                    ];
451
 
 
452
 
                    // Set the name/currency to be the full name including the base currency
453
 
                    $parsedResult['Name'] = $parsedResult['FromName'] . '/' . $parsedResult['ToName'];
454
 
                    $parsedResult['Currency'] = $parsedResult['FromCurrency'] . '/' . $parsedResult['ToCurrency'];
455
 
 
456
 
                    // work out the change when compared to the previous day
457
 
                    if ($percentageChangeRequested && isset($priorDay[$parsedResult['ToName']]) && is_numeric($priorDay[$parsedResult['ToName']])) {
458
 
                        $parsedResult['YesterdayTradePriceOnly'] = $priorDay[$parsedResult['ToName']];
459
 
                        $parsedResult['Change'] = $parsedResult['RawLastTradePriceOnly'] - $parsedResult['YesterdayTradePriceOnly'];
460
 
                    } else {
461
 
                        $parsedResult['YesterdayTradePriceOnly'] = 0;
462
 
                        $parsedResult['Change'] = 0;
463
 
                    }
464
 
 
465
 
                    // Parse the result and add it to our data array
466
 
                    $data[] = $parsedResult;
467
 
                }
468
 
            } catch (RequestException $requestException) {
469
 
                $this->getLog()->error('Problem getting currency information. E = ' . $requestException->getMessage());
470
 
                $this->getLog()->debug($requestException->getTraceAsString());
471
 
 
472
 
                return false;
473
 
            }
474
 
 
475
 
            $this->getLog()->debug('Parsed Results are: ' . var_export($data, true));
476
 
 
477
 
            // Cache it
478
 
            $cache->set($data);
479
 
            $cache->expiresAfter($this->getSetting('cachePeriod', 3600));
480
 
            $this->getPool()->saveDeferred($cache);
481
 
        }
482
 
 
483
 
        return $data;
484
 
    }
485
 
 
486
 
    /**
487
 
     * Run through the data and substitute into the template
488
 
     * @param $data
489
 
     * @param $source
490
 
     * @param $baseCurrency
491
 
     * @return mixed
492
 
     */
493
 
    private function makeSubstitutions($data, $source, $baseCurrency)
494
 
    {
495
 
        // Replace all matches.
496
 
        $matches = '';
497
 
        preg_match_all('/\[.*?\]/', $source, $matches);
498
 
        
499
 
        // Get the currencies' items
500
 
        $items = $this->getOption('items');
501
 
        
502
 
        if (strstr($items, ','))
503
 
            $items = explode(',', $items);
504
 
        else
505
 
            $items = [$items];
506
 
        
507
 
        $reverseConversion = ($this->getOption('reverseConversion', 0) == 1);
508
 
 
509
 
        // Substitute
510
 
        foreach ($matches[0] as $sub) {
511
 
            $replace = str_replace('[', '', str_replace(']', '', $sub));
512
 
            $replacement = 'NULL';
513
 
            
514
 
            $isPreview = ($this->getSanitizer()->getCheckbox('preview') == 1);
515
 
            
516
 
            // Match that in the array
517
 
            if (isset($data[$replace])) {
518
 
                // If the tag exists on the data variables use that var
519
 
                $replacement = $data[$replace];
520
 
            } else {
521
 
                // Custom tags
522
 
                
523
 
                // Replace the time tag
524
 
                if (stripos($replace, 'time|') > -1) {
525
 
                    $timeSplit = explode('|', $replace);
526
 
 
527
 
                    $time = $this->getDate()->parse($data['time']. 'Y-m-d H:i:s')->format($timeSplit[1]);
528
 
 
529
 
                    $replacement = $time;
530
 
 
531
 
                } else if (stripos($replace, 'NameTrimmed|') > -1) {
532
 
                    $nameSplit = explode('|', $replace);
533
 
                    $name = $data['Name'];
534
 
                    
535
 
                    // Remove the last word until the string is inside the pretended Serializable
536
 
                    while (strlen($name) > $nameSplit[1]) {
537
 
                        $name = substr($name, 0, strrpos($name, " "));
538
 
                    }
539
 
 
540
 
                    $replacement = strtoupper($name);
541
 
 
542
 
                } else {
543
 
                    
544
 
                    // Replace the other tags
545
 
                    switch ($replace) {
546
 
                        case 'NameShort':
547
 
 
548
 
                            $replacement = ($reverseConversion) ? $data['FromName'] : $data['ToName'];
549
 
 
550
 
                            break;
551
 
                            
552
 
                        case 'Multiplier':
553
 
                            
554
 
                            // Initialize replacement with empty string 
555
 
                            $replacement = '';
556
 
                            
557
 
                            // Get the current currency name/code
558
 
                            $currencyName = ($reverseConversion) ? $data['FromName'] : $data['ToName'];
559
 
                            
560
 
                            // Search for the item that relates to the actual currency
561
 
                            foreach ($items as $item) {
562
 
                                
563
 
                                // Get the item name
564
 
                                $itemName = trim(explode('|', $item)[0]);
565
 
                                
566
 
                                // Compare the item name with the actual currency and test if the inputed value has a multiplier flag
567
 
                                if( sizeof(explode('|', $item)) > 1 && strcmp($itemName, $currencyName) == 0 ){
568
 
                                    
569
 
                                    // Get the multiplier
570
 
                                    $replacement = explode('|', $item)[1];
571
 
                                }
572
 
                            }
573
 
                            
574
 
                            break;
575
 
                            
576
 
                        case 'CurrencyFlag':
577
 
 
578
 
                            $currencyCode = ($reverseConversion) ? $data['FromName'] : $data['ToName'];
579
 
                            
580
 
                            if (!file_exists(PROJECT_ROOT . '/modules/currencies/currency-flags/' . $currencyCode . '.svg'))
581
 
                                $currencyCode = 'default';
582
 
                            
583
 
                            $file = $this->mediaFactory->createModuleFile('currency_' . $currencyCode, PROJECT_ROOT . '/modules/currencies/currency-flags/' . $currencyCode . '.svg');
584
 
                            $file->alwaysCopy = true;
585
 
                            $file->storedAs = 'currency_' . $currencyCode . '.svg';
586
 
                            $file->save();
587
 
                            
588
 
                            // Tag this layout with this file
589
 
                            $this->assignMedia($file->mediaId);
590
 
                            
591
 
                            $replacement = $this->getFileUrl($file);
592
 
                            
593
 
                            break;
594
 
                            
595
 
                        case 'LastTradePriceOnlyValue':
596
 
                        case 'BidValue':
597
 
                        case 'AskValue':
598
 
                            
599
 
                            // Get the converted currency name
600
 
                            $currencyName = ($reverseConversion) ? $data['FromName'] : $data['ToName'];
601
 
                            
602
 
                            // Get the field's name and set the replacement as the default value from the API
603
 
                            $fieldName = str_replace('Value', '', $replace);
604
 
                            $replacement = $data[$fieldName];
605
 
                                
606
 
                            // Search for the item that relates to the actual currency
607
 
                            foreach ($items as $item) {
608
 
                                
609
 
                                // Get the item name
610
 
                                $itemName = trim(explode('|', $item)[0]);
611
 
                                
612
 
                                // Compare the item name with the actual currency and test if the inputed value has a multiplier flag
613
 
                                if( sizeof(explode('|', $item)) > 1 && strcmp($itemName, $currencyName) == 0 ){
614
 
                                    // Get the multiplier
615
 
                                    $multiplier = explode('|', $item)[1];
616
 
                                    
617
 
                                    // Set the replacement to be the API value times the multiplier
618
 
                                    $replacement = $data[$fieldName] * (float)$multiplier;
619
 
                                }
620
 
                            }        
621
 
                            
622
 
                            break;
623
 
 
624
 
                        case 'ChangePercentage':
625
 
                            // Protect against null values
626
 
                            if(($data['Change'] == null || $data['LastTradePriceOnly'] == null)){
627
 
                                $replacement = "NULL";
628
 
                            } else {
629
 
                                // Calculate the percentage dividing the change by the ( previous value minus the change )
630
 
                                $percentage = $data['Change'] / ( $data['LastTradePriceOnly'] - $data['Change'] );
631
 
 
632
 
                                // Convert the value to percentage and round it
633
 
                                $replacement = round($percentage*100, 2);
634
 
                            }
635
 
 
636
 
                            break;
637
 
                            
638
 
                        case 'ChangeStyle':
639
 
                            // Default value as no change
640
 
                            $replacement = 'value-equal';
641
 
                            
642
 
                            // Protect against null values
643
 
                            if (($data['Change'] != null && $data['LastTradePriceOnly'] != null)) {
644
 
                    
645
 
                                if ($data['Change'] > 0) {
646
 
                                    $replacement = 'value-up';
647
 
                                } else if ( $data['Change'] < 0 ){
648
 
                                    $replacement = 'value-down';
649
 
                                }
650
 
                            }
651
 
                            
652
 
                            break;
653
 
                            
654
 
                        case 'ChangeIcon':
655
 
                        
656
 
                            // Default value as no change
657
 
                            $replacement = 'right-arrow';
658
 
                            
659
 
                            // Protect against null values
660
 
                            if (($data['Change'] != null && $data['LastTradePriceOnly'] != null)) {
661
 
                    
662
 
                                if ( $data['Change'] > 0 ) {
663
 
                                    $replacement = 'up-arrow';
664
 
                                } else if ( $data['Change'] < 0 ){
665
 
                                    $replacement = 'down-arrow';
666
 
                                }
667
 
                            }
668
 
                            
669
 
                            break;
670
 
                            
671
 
                        case 'CurrencyUpper':
672
 
                            // Currency in uppercase
673
 
                            $replacement = strtoupper($data['Currency']);
674
 
                            
675
 
                            break;
676
 
                                
677
 
                        default:
678
 
                            $replacement = 'NULL';
679
 
                            
680
 
                            break;
681
 
                    }    
682
 
                }
683
 
            }
684
 
            
685
 
            // Replace the variable on the source string
686
 
            $source = str_replace($sub, $replacement, $source);
687
 
        }
688
 
 
689
 
        return $source;
690
 
    }
691
 
 
692
 
    /**
693
 
     * Get Tab
694
 
     */
695
 
    public function getTab($tab)
696
 
    {
697
 
        if (!$data = $this->getResults())
698
 
            throw new NotFoundException(__('No data returned, please check error log.'));
699
 
 
700
 
        return ['results' => $data[0]];
701
 
    }
702
 
 
703
 
    /**
704
 
     * Get Resource
705
 
     * @param int $displayId
706
 
     * @return mixed
707
 
     */
708
 
    public function getResource($displayId = 0)
709
 
    {        
710
 
        $data = [];
711
 
        $isPreview = ($this->getSanitizer()->getCheckbox('preview') == 1);
712
 
 
713
 
        // Replace the View Port Width?
714
 
        $data['viewPortWidth'] = ($isPreview) ? $this->region->width : '[[ViewPortWidth]]';
715
 
 
716
 
        // Information from the Module        
717
 
        $duration = $this->getCalculatedDurationForGetResource();
718
 
        $durationIsPerItem = $this->getOption('durationIsPerItem', 1);
719
 
 
720
 
        // Generate a JSON string of items.
721
 
        if (!$items = $this->getResults()) {
722
 
            return '';
723
 
        }
724
 
 
725
 
        if( $this->getOption('overrideTemplate') == 0 ) {
726
 
            
727
 
            $template = $this->getTemplateById($this->getOption('templateId'));
728
 
            
729
 
            if (isset($template)) {
730
 
                $mainTemplate = $template['main'];
731
 
                $itemTemplate = $template['item'];
732
 
                $styleSheet = $template['css'];
733
 
                $widgetOriginalWidth = $template['widgetOriginalWidth'];
734
 
                $widgetOriginalHeight = $template['widgetOriginalHeight'];
735
 
                $maxItemsPerPage = $template['maxItemsPerPage'];
736
 
            }
737
 
            
738
 
        } else {
739
 
            
740
 
            $mainTemplate = $this->getRawNode('mainTemplate');
741
 
            $itemTemplate = $this->getRawNode('itemTemplate');
742
 
            $styleSheet = $this->getRawNode('styleSheet', '');
743
 
            $widgetOriginalWidth = $this->getSanitizer()->int($this->getOption('widgetOriginalWidth'));
744
 
            $widgetOriginalHeight = $this->getSanitizer()->int($this->getOption('widgetOriginalHeight'));
745
 
            $maxItemsPerPage = $this->getSanitizer()->int($this->getOption('maxItemsPerPage'));
746
 
        }
747
 
        
748
 
        // Run through each item and substitute with the template
749
 
        $mainTemplate = $this->parseLibraryReferences($isPreview, $mainTemplate);
750
 
        $itemTemplate = $this->parseLibraryReferences($isPreview, $itemTemplate);
751
 
        
752
 
        $renderedItems = [];
753
 
        
754
 
        $base = $this->getOption('base');
755
 
 
756
 
        foreach ($items as $item) {
757
 
            $renderedItems[] = $this->makeSubstitutions($item, $itemTemplate, $base);
758
 
        }        
759
 
 
760
 
        $options = array(
761
 
            'type' => $this->getModuleType(),
762
 
            'fx' => $this->getOption('effect', 'none'),
763
 
            'speed' => $this->getOption('speed', 500),
764
 
            'duration' => $duration,
765
 
            'durationIsPerPage' => ($this->getOption('durationIsPerPage', 0) == 1),
766
 
            'numItems' => count($renderedItems),
767
 
            'originalWidth' => $this->region->width,
768
 
            'originalHeight' => $this->region->height,
769
 
            'previewWidth' => $this->getSanitizer()->getDouble('width', 0),
770
 
            'previewHeight' => $this->getSanitizer()->getDouble('height', 0),
771
 
            'widgetDesignWidth' => $widgetOriginalWidth,
772
 
            'widgetDesignHeight'=> $widgetOriginalHeight,
773
 
            'scaleOverride' => $this->getSanitizer()->getDouble('scale_override', 0), 
774
 
            'maxItemsPerPage' => $maxItemsPerPage
775
 
        );
776
 
 
777
 
        $itemsPerPage = $options['maxItemsPerPage'];
778
 
        $pages = count($renderedItems);
779
 
        $pages = ($itemsPerPage > 0) ? ceil($pages / $itemsPerPage) : $pages;
780
 
        $totalDuration = ($durationIsPerItem == 0) ? $duration : ($duration * $pages);
781
 
        
782
 
        // Replace and Control Meta options
783
 
        $data['controlMeta'] = '<!-- NUMITEMS=' . $pages . ' -->' . PHP_EOL . '<!-- DURATION=' . $totalDuration . ' -->';
784
 
 
785
 
        // Get the JavaScript node
786
 
        $javaScript = $this->parseLibraryReferences($isPreview, $this->getRawNode('javaScript', ''));
787
 
 
788
 
        // Replace the head content
789
 
        $headContent = '';
790
 
 
791
 
        // Add our fonts.css file
792
 
        $headContent .= '<link href="' . (($isPreview) ? $this->getApp()->urlFor('library.font.css') : 'fonts.css') . '" rel="stylesheet" media="screen">
793
 
        <link href="' . $this->getResourceUrl('vendor/bootstrap.min.css')  . '" rel="stylesheet" media="screen">';
794
 
        
795
 
        $backgroundColor = $this->getOption('backgroundColor');
796
 
        if ($backgroundColor != '') {
797
 
            $headContent .= '<style type="text/css"> body { background-color: ' . $backgroundColor . ' }</style>';
798
 
        } else {
799
 
          $headContent .= '<style type="text/css"> body { background-color: transparent }</style>';
800
 
        }
801
 
        
802
 
        // Add the CSS if it isn't empty, and replace the wallpaper
803
 
        $css = $this->makeSubstitutions([], $styleSheet, '');
804
 
 
805
 
        if ($css != '') {
806
 
            $headContent .= '<style type="text/css">' . $this->parseLibraryReferences($isPreview, $css) . '</style>';
807
 
        }
808
 
        $headContent .= '<style type="text/css">' . file_get_contents($this->getConfig()->uri('css/client.css', true)) . '</style>';
809
 
 
810
 
        // Replace the Head Content with our generated javascript
811
 
        $data['head'] = $headContent;
812
 
 
813
 
        // Add some scripts to the JavaScript Content
814
 
        $javaScriptContent = '<script type="text/javascript" src="' . $this->getResourceUrl('vendor/jquery-1.11.1.min.js') . '"></script>';
815
 
 
816
 
        $javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('vendor/jquery-cycle-2.1.6.min.js') . '"></script>';
817
 
 
818
 
        $javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('xibo-layout-scaler.js') . '"></script>';
819
 
        $javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('xibo-finance-render.js') . '"></script>';
820
 
        $javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('xibo-image-render.js') . '"></script>';
821
 
 
822
 
        $javaScriptContent .= '<script type="text/javascript">';
823
 
        $javaScriptContent .= '   var options = ' . json_encode($options) . ';';
824
 
        $javaScriptContent .= '   var items = ' . json_encode($renderedItems) . ';';
825
 
        $javaScriptContent .= '   var body = ' . json_encode($mainTemplate) . ';';
826
 
        $javaScriptContent .= '   $(document).ready(function() { ';
827
 
        $javaScriptContent .= '       $("body").xiboLayoutScaler(options); $("#content").xiboFinanceRender(options, items, body); $("#content").find("img").xiboImageRender(options); ';
828
 
        $javaScriptContent .= '   }); ';
829
 
        $javaScriptContent .= $javaScript;
830
 
        $javaScriptContent .= '</script>';
831
 
 
832
 
        // Replace the Head Content with our generated javascript
833
 
        $data['javaScript'] = $javaScriptContent;
834
 
 
835
 
        // Update and save widget if we've changed our assignments.
836
 
        if ($this->hasMediaChanged())
837
 
            $this->widget->save(['saveWidgetOptions' => false, 'notify' => false, 'notifyDisplays' => true, 'audit' => false]);
838
 
 
839
 
        return $this->renderTemplate($data);
840
 
    }
841
 
 
842
 
    public function isValid()
843
 
    {
844
 
        // Using the information you have in your module calculate whether it is valid or not.
845
 
        // 0 = Invalid
846
 
        // 1 = Valid
847
 
        // 2 = Unknown
848
 
        return 1;
849
 
    }
850
 
    
851
 
}