345 lines
12 KiB
PHP
345 lines
12 KiB
PHP
<?php
|
|
/*
|
|
* Copyright (C) 2024 Xibo Signage Ltd
|
|
*
|
|
* Xibo - Digital Signage - https://xibosignage.com
|
|
*
|
|
* 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\XTR;
|
|
|
|
use Carbon\Carbon;
|
|
use GuzzleHttp\Client;
|
|
use GuzzleHttp\Exception\GuzzleException;
|
|
use Xibo\Controller\Module;
|
|
use Xibo\Event\MaintenanceDailyEvent;
|
|
use Xibo\Factory\DataSetFactory;
|
|
use Xibo\Factory\FontFactory;
|
|
use Xibo\Factory\LayoutFactory;
|
|
use Xibo\Factory\ModuleFactory;
|
|
use Xibo\Factory\ModuleTemplateFactory;
|
|
use Xibo\Factory\UserFactory;
|
|
use Xibo\Helper\DatabaseLogHandler;
|
|
use Xibo\Helper\DateFormatHelper;
|
|
use Xibo\Helper\Random;
|
|
use Xibo\Service\MediaService;
|
|
use Xibo\Service\MediaServiceInterface;
|
|
use Xibo\Support\Exception\GeneralException;
|
|
use Xibo\Support\Exception\NotFoundException;
|
|
|
|
/**
|
|
* Class MaintenanceDailyTask
|
|
* @package Xibo\XTR
|
|
*/
|
|
class MaintenanceDailyTask implements TaskInterface
|
|
{
|
|
use TaskTrait;
|
|
|
|
/** @var LayoutFactory */
|
|
private $layoutFactory;
|
|
|
|
/** @var UserFactory */
|
|
private $userFactory;
|
|
|
|
/** @var Module */
|
|
private $moduleController;
|
|
|
|
/** @var MediaServiceInterface */
|
|
private $mediaService;
|
|
|
|
/** @var DataSetFactory */
|
|
private $dataSetFactory;
|
|
|
|
/** @var FontFactory */
|
|
private $fontFactory;
|
|
|
|
/** @var ModuleFactory */
|
|
private $moduleFactory;
|
|
|
|
/** @var ModuleTemplateFactory */
|
|
private $moduleTemplateFactory;
|
|
|
|
/** @var string */
|
|
private $libraryLocation;
|
|
|
|
/** @inheritdoc */
|
|
public function setFactories($container)
|
|
{
|
|
$this->moduleController = $container->get('\Xibo\Controller\Module');
|
|
$this->layoutFactory = $container->get('layoutFactory');
|
|
$this->userFactory = $container->get('userFactory');
|
|
$this->dataSetFactory = $container->get('dataSetFactory');
|
|
$this->mediaService = $container->get('mediaService');
|
|
$this->fontFactory = $container->get('fontFactory');
|
|
$this->moduleFactory = $container->get('moduleFactory');
|
|
$this->moduleTemplateFactory = $container->get('moduleTemplateFactory');
|
|
return $this;
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function run()
|
|
{
|
|
$this->runMessage = '# ' . __('Daily Maintenance') . PHP_EOL . PHP_EOL;
|
|
|
|
// Long-running task
|
|
set_time_limit(0);
|
|
|
|
// Make sure our library structure is as it should be
|
|
try {
|
|
$this->libraryLocation = $this->getConfig()->getSetting('LIBRARY_LOCATION');
|
|
MediaService::ensureLibraryExists($this->libraryLocation);
|
|
} catch (\Exception $exception) {
|
|
$this->getLogger()->error('Library structure invalid, e = ' . $exception->getMessage());
|
|
$this->appendRunMessage(__('Library structure invalid'));
|
|
}
|
|
|
|
// Import layouts
|
|
$this->importLayouts();
|
|
|
|
// Cycle the XMR Key
|
|
$this->cycleXmrKey();
|
|
|
|
try {
|
|
$this->appendRunMessage(__('## Build caches'));
|
|
|
|
// TODO: should we remove all bundle/asset cache before we start?
|
|
// Player bundle
|
|
$this->cachePlayerBundle();
|
|
|
|
// Cache Assets
|
|
$this->cacheAssets();
|
|
|
|
// Fonts
|
|
$this->mediaService->setUser($this->userFactory->getSystemUser())->updateFontsCss();
|
|
} catch (\Exception $exception) {
|
|
$this->getLogger()->error('Failure to build caches, e = ' . $exception->getMessage());
|
|
$this->appendRunMessage(__('Failure to build caches'));
|
|
}
|
|
|
|
// Tidy logs
|
|
$this->tidyLogs();
|
|
|
|
// Tidy Cache
|
|
$this->tidyCache();
|
|
|
|
// Dispatch an event so that consumers can hook into daily maintenance.
|
|
$event = new MaintenanceDailyEvent();
|
|
$this->getDispatcher()->dispatch($event, MaintenanceDailyEvent::$NAME);
|
|
foreach ($event->getMessages() as $message) {
|
|
$this->appendRunMessage($message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tidy the DB logs
|
|
*/
|
|
private function tidyLogs()
|
|
{
|
|
$this->runMessage .= '## ' . __('Tidy Logs') . PHP_EOL;
|
|
|
|
$maxage = $this->config->getSetting('MAINTENANCE_LOG_MAXAGE');
|
|
if ($maxage != 0) {
|
|
// Run this in the log handler so that we share the same connection and don't deadlock.
|
|
DatabaseLogHandler::tidyLogs(
|
|
Carbon::now()
|
|
->subDays(intval($maxage))
|
|
->format(DateFormatHelper::getSystemFormat())
|
|
);
|
|
|
|
$this->runMessage .= ' - ' . __('Done') . PHP_EOL . PHP_EOL;
|
|
} else {
|
|
$this->runMessage .= ' - ' . __('Disabled') . PHP_EOL . PHP_EOL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tidy Cache
|
|
*/
|
|
private function tidyCache()
|
|
{
|
|
$this->runMessage .= '## ' . __('Tidy Cache') . PHP_EOL;
|
|
$this->pool->purge();
|
|
$this->runMessage .= ' - ' . __('Done.') . PHP_EOL . PHP_EOL;
|
|
}
|
|
|
|
/**
|
|
* Import Layouts
|
|
* @throws GeneralException|\FontLib\Exception\FontNotFoundException
|
|
*/
|
|
private function importLayouts()
|
|
{
|
|
$this->runMessage .= '## ' . __('Import Layouts and Fonts') . PHP_EOL;
|
|
|
|
if ($this->config->getSetting('DEFAULTS_IMPORTED') == 0) {
|
|
// Make sure the library exists
|
|
$this->mediaService->initLibrary();
|
|
|
|
// Import any layouts
|
|
$folder = $this->config->uri('layouts', true);
|
|
|
|
foreach (array_diff(scandir($folder), array('..', '.')) as $file) {
|
|
if (stripos($file, '.zip')) {
|
|
try {
|
|
$layout = $this->layoutFactory->createFromZip(
|
|
$folder . '/' . $file,
|
|
null,
|
|
$this->userFactory->getSystemUser()->getId(),
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
$this->dataSetFactory,
|
|
null,
|
|
$this->mediaService,
|
|
1
|
|
);
|
|
|
|
$layout->save([
|
|
'audit' => false,
|
|
'import' => true
|
|
]);
|
|
|
|
if (!empty($layout->getUnmatchedProperty('thumbnail'))) {
|
|
rename($layout->getUnmatchedProperty('thumbnail'), $layout->getThumbnailUri());
|
|
}
|
|
|
|
try {
|
|
$this->layoutFactory->getById($this->config->getSetting('DEFAULT_LAYOUT'));
|
|
} catch (NotFoundException $exception) {
|
|
$this->config->changeSetting('DEFAULT_LAYOUT', $layout->layoutId);
|
|
}
|
|
} catch (\Exception $exception) {
|
|
$this->log->error('Unable to import layout: ' . $file . '. E = ' . $exception->getMessage());
|
|
$this->log->debug($exception->getTraceAsString());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fonts
|
|
// -----
|
|
// install fonts from the theme folder
|
|
$libraryLocation = $this->config->getSetting('LIBRARY_LOCATION');
|
|
$fontFolder = $this->config->uri('fonts', true);
|
|
foreach (array_diff(scandir($fontFolder), array('..', '.')) as $file) {
|
|
// check if we already have this font file
|
|
if (count($this->fontFactory->getByFileName($file)) <= 0) {
|
|
// if we don't add it
|
|
$filePath = $fontFolder . DIRECTORY_SEPARATOR . $file;
|
|
$fontLib = \FontLib\Font::load($filePath);
|
|
|
|
// check embed flag, just in case
|
|
$embed = intval($fontLib->getData('OS/2', 'fsType'));
|
|
// if it's not embeddable, log error and skip it
|
|
if ($embed != 0 && $embed != 8) {
|
|
$this->log->error('Unable to install default Font: ' . $file
|
|
. ' . Font file is not embeddable due to its permissions');
|
|
continue;
|
|
}
|
|
|
|
$font = $this->fontFactory->createEmpty();
|
|
$font->modifiedBy = $this->userFactory->getSystemUser()->userName;
|
|
$font->name = $fontLib->getFontName() . ' ' . $fontLib->getFontSubfamily();
|
|
$font->familyName = strtolower(preg_replace('/\s+/', ' ', preg_replace('/\d+/u', '', $font->name)));
|
|
$font->fileName = $file;
|
|
$font->size = filesize($filePath);
|
|
$font->md5 = md5_file($filePath);
|
|
$font->save();
|
|
|
|
$copied = copy($filePath, $libraryLocation . 'fonts/' . $file);
|
|
if (!$copied) {
|
|
$this->getLogger()->error('importLayouts: Unable to copy fonts to ' . $libraryLocation);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->config->changeSetting('DEFAULTS_IMPORTED', 1);
|
|
|
|
$this->runMessage .= ' - ' . __('Done.') . PHP_EOL . PHP_EOL;
|
|
} else {
|
|
$this->runMessage .= ' - ' . __('Not Required.') . PHP_EOL . PHP_EOL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh the cache of assets
|
|
* @return void
|
|
* @throws GeneralException
|
|
*/
|
|
private function cacheAssets(): void
|
|
{
|
|
// Assets
|
|
$failedCount = 0;
|
|
$assets = array_merge($this->moduleFactory->getAllAssets(), $this->moduleTemplateFactory->getAllAssets());
|
|
foreach ($assets as $asset) {
|
|
try {
|
|
$asset->updateAssetCache($this->libraryLocation, true);
|
|
} catch (GeneralException $exception) {
|
|
$failedCount++;
|
|
$this->log->error('Unable to copy asset: ' . $asset->id . ', e: ' . $exception->getMessage());
|
|
}
|
|
}
|
|
|
|
$this->appendRunMessage(sprintf(__('Assets cached, %d failed.'), $failedCount));
|
|
}
|
|
|
|
/**
|
|
* Cache the player bundle.
|
|
* @return void
|
|
*/
|
|
private function cachePlayerBundle(): void
|
|
{
|
|
// Output the player bundle
|
|
$bundlePath = $this->getConfig()->getSetting('LIBRARY_LOCATION') . 'assets/bundle.min.js';
|
|
$bundleMd5CachePath = $bundlePath . '.md5';
|
|
|
|
copy(PROJECT_ROOT . '/modules/bundle.min.js', $bundlePath);
|
|
file_put_contents($bundleMd5CachePath, md5_file($bundlePath));
|
|
|
|
$this->appendRunMessage(__('Player bundle cached'));
|
|
}
|
|
|
|
/**
|
|
* Once per day we cycle the XMR CMS key
|
|
* the old key should remain valid in XMR for up to 1 hour further to allow for cross over
|
|
* @return void
|
|
*/
|
|
private function cycleXmrKey(): void
|
|
{
|
|
$this->log->debug('cycleXmrKey: adding new key');
|
|
try {
|
|
$key = Random::generateString(20, 'xmr_');
|
|
|
|
$this->getConfig()->changeSetting('XMR_CMS_KEY', $key);
|
|
$client = new Client($this->config->getGuzzleProxy([
|
|
'base_uri' => $this->getConfig()->getSetting('XMR_ADDRESS'),
|
|
]));
|
|
|
|
$client->post('/', [
|
|
'json' => [
|
|
'id' => constant('SECRET_KEY'),
|
|
'type' => 'keys',
|
|
'key' => $key,
|
|
],
|
|
]);
|
|
$this->log->debug('cycleXmrKey: added new key');
|
|
} catch (GuzzleException | \Exception $e) {
|
|
$this->log->error('cycleXmrKey: failed. E = ' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|