HEX
Server: Apache
System: Linux server2.voipitup.com.au 4.18.0-553.104.1.lve.el8.x86_64 #1 SMP Tue Feb 10 20:07:30 UTC 2026 x86_64
User: posscale (1027)
PHP: 8.2.29
Disabled: exec,passthru,shell_exec,system
Upload Files
File: /home/posscale/subdomains/xibo/lib/Widget/Clock.php
<?php
/*
 * Xibo - Digital Signage - http://www.xibo.org.uk
 * Copyright (C) 2014-15 Daniel Garner
 *
 * This file is part of Xibo.
 *
 * Xibo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version. 
 *
 * Xibo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Xibo.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
namespace Xibo\Widget;

use Respect\Validation\Validator as v;
use Xibo\Helper\Translate;

class Clock extends ModuleWidget
{
    public $codeSchemaVersion = 1;

    public function installFiles()
    {
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/web/modules/vendor/jquery-1.11.1.min.js')->save();
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/web/modules/vendor/jquery-cycle-2.1.6.min.js')->save();
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/web/modules/vendor/moment.js')->save();
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/web/modules/vendor/flipclock.min.js')->save();
        $this->mediaFactory->createModuleSystemFile(PROJECT_ROOT . '/web/modules/xibo-layout-scaler.js')->save();
    }

    /**
     * Validate
     */
    public function validate()
    {
        // Validate
        if ($this->getUseDuration() == 1 && !v::int()->min(1)->validate($this->getDuration()))
            throw new \InvalidArgumentException(__('Please enter a duration.'));
    }

    /**
     * Adds a Clock Widget
     * @SWG\Post(
     *  path="/playlist/widget/clock/{playlistId}",
     *  operationId="WidgetClockAdd",
     *  tags={"widget"},
     *  summary="Add a Clock Widget",
     *  description="Add a new Clock Widget to the specified playlist",
     *  @SWG\Parameter(
     *      name="playlistId",
     *      in="path",
     *      description="The playlist ID to add a Clock widget to",
     *      type="integer",
     *      required=true
     *   ),
     *  @SWG\Parameter(
     *      name="name",
     *      in="formData",
     *      description="Optional Widget Name",
     *      type="string",
     *      required=false
     *  ),
     *  @SWG\Parameter(
     *      name="duration",
     *      in="formData",
     *      description="The Widget Duration",
     *      type="integer",
     *      required=false
     *  ),
     *  @SWG\Parameter(
     *      name="useDuration",
     *      in="formData",
     *      description="(0, 1) Select 1 only if you will provide duration parameter as well",
     *      type="integer",
     *      required=false
     *  ),
     *  @SWG\Parameter(
     *      name="themeId",
     *      in="formData",
     *      description="Flag (0 , 1) for Analogue clock the light and dark theme",
     *      type="integer",
     *      required=false
     *   ),
     *  @SWG\Parameter(
     *      name="clockTypeId",
     *      in="formData",
     *      description="Type of a clock widget 1-Analogue, 2-Digital, 3-Flip clock",
     *      type="integer",
     *      required=false
     *   ),
     *  @SWG\Parameter(
     *      name="offset",
     *      in="formData",
     *      description="The offset in minutes that should be applied to the current time, if a counter is selected then date/time to run from in the format Y-m-d H:i:s",
     *      type="string",
     *      required=false
     *   ),
     *  @SWG\Parameter(
     *      name="format",
     *      in="formData",
     *      description="For digital clock, format in which the time should be displayed example [HH:mm]",
     *      type="string",
     *      required=false
     *   ),
     *  @SWG\Parameter(
     *      name="showSeconds",
     *      in="formData",
     *      description="For Flip Clock, should the clock show seconds or not",
     *      type="integer",
     *      required=false
     *   ),
     *  @SWG\Parameter(
     *      name="ClockFace",
     *      in="formData",
     *      description="For Flip Clock, supported options: TwelveHourClock TwentyFourHourClock HourlyCounter MinuteCounter DailyCounter",
     *      type="string",
     *      required=false
     *   ),
     *  @SWG\Response(
     *      response=201,
     *      description="successful operation",
     *      @SWG\Schema(ref="#/definitions/Widget"),
     *      @SWG\Header(
     *          header="Location",
     *          description="Location of the new widget",
     *          type="string"
     *      )
     *  )
     * )
     */
    public function add()
    {
        // You must also provide a duration (all media items must provide this field)
        $this->setOption('name', $this->getSanitizer()->getString('name'));
        $this->setUseDuration($this->getSanitizer()->getCheckbox('useDuration'));
        $this->setDuration($this->getSanitizer()->getInt('duration', $this->getDuration()));
        $this->setOption('theme', $this->getSanitizer()->getInt('themeId', 0));
        $this->setOption('clockTypeId', $this->getSanitizer()->getInt('clockTypeId', 1));
        $this->setOption('offset', $this->getSanitizer()->getString('offset', 0));
        $this->setRawNode('format', $this->getSanitizer()->getParam('ta_text', $this->getSanitizer()->getParam('format', '')));
        $this->setOption('showSeconds', $this->getSanitizer()->getCheckbox('showSeconds', 1));
        $this->setOption('clockFace', $this->getSanitizer()->getString('clockFace', 'TwentyFourHourClock'));

        $this->validate();

        // Save the widget
        $this->saveWidget();
    }

     /**
     * Edit Clock
     */
    public function edit()
    {
        // You must also provide a duration (all media items must provide this field)
        $this->setOption('name', $this->getSanitizer()->getString('name'));
        $this->setUseDuration($this->getSanitizer()->getCheckbox('useDuration'));
        $this->setDuration($this->getSanitizer()->getInt('duration', $this->getDuration()));
        $this->setOption('theme', $this->getSanitizer()->getInt('themeId', 0));
        $this->setOption('clockTypeId', $this->getSanitizer()->getInt('clockTypeId', 1));
        $this->setOption('offset', $this->getSanitizer()->getString('offset', 0));
        $this->setRawNode('format', $this->getSanitizer()->getParam('ta_text', $this->getSanitizer()->getParam('format', '')));
        $this->setOption('showSeconds', $this->getSanitizer()->getCheckbox('showSeconds'));
        $this->setOption('clockFace', $this->getSanitizer()->getString('clockFace'));

        $this->validate();

        // Save the widget
        $this->saveWidget();
    }

    /**
     * Supported Clock Faces
     * @return array
     */
    public function clockFaces()
    {
        return [
            ['id' => 'TwelveHourClock', 'value' => __('12h Clock')],
            ['id' => 'TwentyFourHourClock', 'value' => __('24h Clock')],
            ['id' => 'HourlyCounter', 'value' => __('Hourly Counter')],
            ['id' => 'MinuteCounter', 'value' => __('Minute Counter')],
            ['id' => 'DailyCounter', 'value' => __('Daily Counter')]
        ];
    }

    /**
     * GetResource
     * Return the rendered resource to be used by the client (or a preview) for displaying this content.
     * @param integer $displayId If this comes from a real client, this will be the display id.
     * @return mixed
     */
    public function getResource($displayId = 0)
    {
        $template = null;
        $data = [];
        $isPreview = ($this->getSanitizer()->getCheckbox('preview') == 1);

        // Clock Type
        switch ($this->getOption('clockTypeId', 1)) {

            case 1:
                // Analogue
                $template = 'clock-get-resource-analog';

                // Render our clock face
                $theme = ($this->getOption('theme') == 1 ? 'light' : 'dark');
                $theme_face = ($this->getOption('theme') == 1 ? 'clock_bg_modern_light.png' : 'clock_bg_modern_dark.png');

                $data['clockFace'] = base64_encode(file_get_contents('modules/clock/' . $theme_face));

                // Light or dark?
                $data['clockTheme'] = $theme;
                $data['offset'] = $this->getOption('offset', 0);

                // After body content
                $javaScriptContent = '<script type="text/javascript" src="' . $this->getResourceUrl('vendor/jquery-1.11.1.min.js') . '"></script>';
                $javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('vendor/moment.js') . '"></script>';

                // Replace the After body Content
                $data['javaScript'] = $javaScriptContent;
                break;

            case 2:
                // Digital
                // Digital clock is essentially a cut down text module which always fits to the region
                $template = 'get-resource';

                // Extract the format from the raw node in the XLF
                $format = $this->getRawNode('format', null);

                // Strip out the bit between the [] brackets and use that as the format mask for moment.
                $matches = '';
                preg_match_all('/\[.*?\]/', $format, $matches);

                foreach ($matches[0] as $subs) {
                    $format = str_replace($subs, '<span class="clock" format="' . str_replace('[', '', str_replace(']', '', $subs)) . '"></span>', $format);
                }

                // Replace all the subs
                $data['body'] = $format;

                // After body content
                $options = array(
                    'previewWidth' => $this->getSanitizer()->getDouble('width', 0),
                    'previewHeight' => $this->getSanitizer()->getDouble('height', 0),
                    'originalWidth' => $this->region->width,
                    'originalHeight' => $this->region->height,
                    'scaleOverride' => $this->getSanitizer()->getDouble('scale_override', 0)
                );

                $javaScriptContent = '<script type="text/javascript" src="' . $this->getResourceUrl('vendor/jquery-1.11.1.min.js') . '"></script>';
                $javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('vendor/moment.js') . '"></script>';
                $javaScriptContent .= '<script type="text/javascript" src="' . $this->getResourceUrl('xibo-layout-scaler.js') . '"></script>';
                $javaScriptContent .= '<script type="text/javascript">
                    var locale = "' . Translate::GetJsLocale() . '";
                    var options = ' . json_encode($options) . ';

                    function updateClock() {
                        $(".clock").each(function() {
                            $(this).html(moment().add(' . $this->getOption('offset', 0) . ', "m").format($(this).attr("format")));
                        });
                    }

                    $(document).ready(function() {
                                        moment.locale(locale);
                        updateClock();
                        setInterval(updateClock, 1000);
                        $("body").xiboLayoutScaler(options);
                    });
                </script>';

                // Replace the After body Content
                $data['javaScript'] = $javaScriptContent;

                // Add our fonts.css file
                $headContent  = '<link href = "' . (($isPreview) ? $this->getApp()->urlFor('library.font.css') : 'fonts.css') . '" rel="stylesheet" media="screen">';
                $headContent .= '<style type = "text/css" > ' . file_get_contents($this->getConfig()->uri('css/client.css', true)) . '</style>';

                $data['head'] = $headContent;

                break;

            case 3:
                // Flip Clock
                $template = 'clock-get-resource-flip';

                // Head Content (CSS for flip clock)
                $data['head'] = '<style type="text/css">' . file_get_contents('modules/vendor/flipclock.css') . '</style>';
                $data['offset'] = $this->getOption('offset', '0');
                $data['duration'] = $this->getDuration();
                $data['clockFace'] = $this->getOption('clockFace', 'TwentyFourHourClock');
                $data['showSeconds'] = $this->getOption('showSeconds', 1);

                // After body content
                $javaScriptContent  = '<script type = "text/javascript" src = "' . $this->getResourceUrl('vendor/jquery-1.11.1.min.js') . '" ></script > ';
                $javaScriptContent .= '<script type = "text/javascript" src = "' . $this->getResourceUrl('vendor/flipclock.min.js') . '" ></script > ';

                // Replace the After body Content
                $data['javaScript'] = $javaScriptContent;

                break;
        }

        // If we are a preview, then pass in the width and height
        $data['previewWidth'] = $this->getSanitizer()->getDouble('width', 0);
        $data['previewHeight'] = $this->getSanitizer()->getDouble('height', 0);

        // Replace the View Port Width?
        $data['viewPortWidth'] = ($isPreview) ? $this->region->width : '[[ViewPortWidth]]';

        // Return that content.
        return $this->renderTemplate($data, $template);
    }

    /**
     * Is Valid
     * @return int
     */
    public function isValid()
    {
        // Using the information you have in your module calculate whether it is valid or not.
        // 0 = Invalid
        // 1 = Valid
        // 2 = Unknown
        return 1;
    }
}