芝麻web文件管理V1.00
编辑当前文件:/home/mgatv524/public_html/avenida/views/XTR.zip
PK 7qYn'ٚ ClearCachedMediaDataTask.phpnu [ . */ namespace Xibo\XTR; use Xibo\Factory\MediaFactory; use Xibo\Service\DateServiceInterface; /** * Class ClearCachedMediaDataTask * @package Xibo\XTR */ class ClearCachedMediaDataTask implements TaskInterface { use TaskTrait; /** @var DateServiceInterface */ private $date; /** @var MediaFactory */ private $mediaFactory; /** @inheritdoc */ public function setFactories($container) { $this->date = $container->get('dateService'); $this->mediaFactory = $container->get('mediaFactory'); return $this; } /** @inheritdoc */ public function run() { $this->runMessage = '# ' . __('Clear Cached Media Data') . PHP_EOL . PHP_EOL; // Long running task set_time_limit(0); $this->runClearCache(); } /** * Updates all md5/filesizes to empty for any image/module file created since 2.2.0 release date */ private function runClearCache() { $cutOffDate = $this->date->parse('2019-11-26', 'Y-m-d')->startOfDay()->format('Y-m-d H:i:s'); // Update the MD5 and fileSize to null $this->store->update('UPDATE `media` SET md5 = :md5, fileSize = :fileSize, modifiedDt = :modifiedDt WHERE (`media`.type = \'image\' OR (`media`.type = \'module\' AND `media`.moduleSystemFile = 0)) AND createdDt >= :createdDt ', [ 'fileSize' => null, 'md5' => null, 'createdDt' => $cutOffDate, 'modifiedDt' => date('Y-m-d H:i:s') ]); // Disable the task $this->appendRunMessage('# Disabling task.'); $this->getTask()->isActive = 0; $this->getTask()->save(); $this->appendRunMessage(__('Done.'. PHP_EOL)); } }PK 7qYW? ? MaintenanceRegularTask.phpnu [ displayController = $container->get('\Xibo\Controller\Display'); $this->libraryController = $container->get('\Xibo\Controller\Library'); $this->displayFactory = $container->get('displayFactory'); $this->notificationFactory = $container->get('notificationFactory'); $this->userGroupFactory = $container->get('userGroupFactory'); $this->layoutFactory = $container->get('layoutFactory'); $this->playlistFactory = $container->get('playlistFactory'); $this->moduleFactory = $container->get('moduleFactory'); return $this; } /** @inheritdoc */ public function run() { $this->runMessage = '# ' . __('Regular Maintenance') . PHP_EOL . PHP_EOL; $this->displayDownEmailAlerts(); $this->licenceSlotValidation(); $this->wakeOnLan(); $this->updatePlaylistDurations(); $this->buildLayouts(); $this->tidyLibrary(); $this->checkLibraryUsage(); $this->checkOverRequestedFiles(); $this->publishLayouts(); } /** * Display Down email alerts * - just runs validate displays */ private function displayDownEmailAlerts() { $this->runMessage .= '## ' . __('Email Alerts') . PHP_EOL; $this->displayController->validateDisplays($this->displayFactory->query()); $this->appendRunMessage(__('Done')); } /** * Licence Slot Validation */ private function licenceSlotValidation() { $maxDisplays = $this->config->getSetting('MAX_LICENSED_DISPLAYS'); if ($maxDisplays > 0) { $this->runMessage .= '## ' . __('Licence Slot Validation') . PHP_EOL; // Get a list of all displays try { $dbh = $this->store->getConnection(); $sth = $dbh->prepare('SELECT displayId, display FROM `display` WHERE licensed = 1 ORDER BY lastAccessed'); $sth->execute(); $displays = $sth->fetchAll(\PDO::FETCH_ASSOC); if (count($displays) > $maxDisplays) { // :( // We need to un-licence some displays $difference = count($displays) - $maxDisplays; $this->log->alert('Max %d authorised displays exceeded, we need to un-authorise %d of %d displays', $maxDisplays, $difference, count($displays)); $update = $dbh->prepare('UPDATE `display` SET licensed = 0 WHERE displayId = :displayId'); foreach ($displays as $display) { // If we are down to 0 difference, then stop if ($difference == 0) break; $this->appendRunMessage(sprintf(__('Disabling %s'), $this->sanitizer->string($display['display']))); $update->execute(['displayId' => $display['displayId']]); $this->log->audit('Display', $display['displayId'], 'Regular Maintenance unauthorised display due to max number of slots exceeded.', ['display' => $display['display']]); $difference--; } } $this->runMessage .= ' - Done' . PHP_EOL . PHP_EOL; } catch (\Exception $e) { $this->log->error($e); } } } /** * Wake on LAN */ private function wakeOnLan() { $this->runMessage = '# ' . __('Wake On LAN') . PHP_EOL; try { // Get a list of all displays which have WOL enabled foreach($this->displayFactory->query(null, ['wakeOnLan' => 1]) as $display) { /** @var \Xibo\Entity\Display $display */ // Time to WOL (with respect to today) $timeToWake = strtotime(date('Y-m-d') . ' ' . $display->wakeOnLanTime); $timeNow = time(); // Should the display be awake? if ($timeNow >= $timeToWake) { // Client should be awake, so has this displays WOL time been passed if ($display->lastWakeOnLanCommandSent < $timeToWake) { // Call the Wake On Lan method of the display object if ($display->macAddress == '' || $display->broadCastAddress == '') { $this->log->error('This display has no mac address recorded against it yet. Make sure the display is running.'); $this->runMessage .= ' - ' . $display->display . ' Did not send MAC address yet' . PHP_EOL; continue; } $this->log->notice('About to send WOL packet to ' . $display->broadCastAddress . ' with Mac Address ' . $display->macAddress); try { WakeOnLan::TransmitWakeOnLan($display->macAddress, $display->secureOn, $display->broadCastAddress, $display->cidr, '9', $this->log); $this->runMessage .= ' - ' . $display->display . ' Sent WOL Message. Previous WOL send time: ' . $this->date->getLocalDate($display->lastWakeOnLanCommandSent) . PHP_EOL; $display->lastWakeOnLanCommandSent = time(); $display->save(['validate' => false, 'audit' => true]); } catch (\Exception $e) { $this->runMessage .= ' - ' . $display->display . ' Error=' . $e->getMessage() . PHP_EOL; } } else { $this->runMessage .= ' - ' . $display->display . ' Display already awake. Previous WOL send time: ' . $this->date->getLocalDate($display->lastWakeOnLanCommandSent) . PHP_EOL; } } else { $this->runMessage .= ' - ' . $display->display . ' Sleeping' . PHP_EOL; } } $this->runMessage .= ' - Done' . PHP_EOL . PHP_EOL; } catch (\PDOException $e) { $this->log->error($e->getMessage()); $this->runMessage .= ' - Error' . PHP_EOL . PHP_EOL; } } /** * Build layouts */ private function buildLayouts() { $this->runMessage .= '## ' . __('Build Layouts') . PHP_EOL; // Build Layouts // We do not want to build any draft Layouts - they are built in the Layout Designer or on Publish foreach ($this->layoutFactory->query(null, ['status' => 3, 'showDrafts' => 0]) as $layout) { /* @var \Xibo\Entity\Layout $layout */ try { $layout->xlfToDisk(['notify' => true]); // Commit after each build // https://github.com/xibosignage/xibo/issues/1593 $this->store->commitIfNecessary(); } catch (\Exception $e) { $this->log->error('Maintenance cannot build Layout %d, %s.', $layout->layoutId, $e->getMessage()); } } $this->runMessage .= ' - Done' . PHP_EOL . PHP_EOL; } /** * Tidy library */ private function tidyLibrary() { $this->runMessage .= '## ' . __('Tidy Library') . PHP_EOL; // Keep tidy $this->libraryController->removeExpiredFiles(); $this->libraryController->removeTempFiles(); $this->runMessage .= ' - Done' . PHP_EOL . PHP_EOL; } /** * Check library usage */ private function checkLibraryUsage() { $libraryLimit = $this->config->getSetting('LIBRARY_SIZE_LIMIT_KB') * 1024; if ($libraryLimit <= 0) return; $results = $this->store->select('SELECT IFNULL(SUM(FileSize), 0) AS SumSize FROM media', []); $size = $this->sanitizer->int($results[0]['SumSize']); if ($size >= $libraryLimit) { // Create a notification if we don't already have one today for this display. $subject = __('Library allowance exceeded'); $date = $this->date->parse(); if (count($this->notificationFactory->getBySubjectAndDate($subject, $this->date->getLocalDate($date->startOfDay(), 'U'), $this->date->getLocalDate($date->addDay(1)->startOfDay(), 'U'))) <= 0) { $body = __(sprintf('Library allowance of %s exceeded. Used %s', ByteFormatter::format($libraryLimit), ByteFormatter::format($size))); $notification = $this->notificationFactory->createSystemNotification( $subject, $body, $this->date->parse() ); $notification->save(); $this->log->critical($subject); } } } /** * Checks to see if there are any overrequested files. */ private function checkOverRequestedFiles() { $items = $this->store->select(' SELECT display.displayId, display.display, COUNT(*) AS countFiles FROM `requiredfile` INNER JOIN `display` ON display.displayId = requiredfile.displayId WHERE `bytesRequested` > 0 AND `requiredfile`.bytesRequested >= `requiredfile`.`size` * :factor AND `requiredfile`.type <> :excludedType AND display.lastAccessed > :lastAccessed AND `requiredfile`.complete = 0 GROUP BY display.displayId, display.display ', [ 'factor' => 3, 'excludedType' => 'W', 'lastAccessed' => $this->date->parse()->subDay()->format('U') ]); foreach ($items as $item) { // Create a notification if we don't already have one today for this display. $subject = sprintf(__('%s is downloading %d files too many times'), $this->sanitizer->string($item['display']), $this->sanitizer->int($item['countFiles'])); $date = $this->date->parse(); if (count($this->notificationFactory->getBySubjectAndDate($subject, $this->date->getLocalDate($date->startOfDay(), 'U'), $this->date->getLocalDate($date->addDay(1)->startOfDay(), 'U'))) <= 0) { $body = sprintf(__('Please check the bandwidth graphs and display status for %s to investigate the issue.'), $this->sanitizer->string($item['display'])); $notification = $this->notificationFactory->createSystemNotification( $subject, $body, $this->date->parse() ); $display = $this->displayFactory->getById($item['displayId']); // Add in any displayNotificationGroups, with permissions foreach ($this->userGroupFactory->getDisplayNotificationGroups($display->displayGroupId) as $group) { $notification->assignUserGroup($group); } $notification->save(); $this->log->critical($subject); } } } /** * Update Playlist Durations */ private function updatePlaylistDurations() { $this->runMessage .= '## ' . __('Playlist Duration Updates') . PHP_EOL; // Build Layouts foreach ($this->playlistFactory->query(null, ['requiresDurationUpdate' => 1]) as $playlist) { try { $playlist->setModuleFactory($this->moduleFactory); $playlist->updateDuration(); } catch (XiboException $xiboException) { $this->log->error('Maintenance cannot update Playlist ' . $playlist->playlistId . ', ' . $xiboException->getMessage()); } } $this->runMessage .= ' - Done' . PHP_EOL . PHP_EOL; } /** * Publish layouts with set publishedDate * @throws \Xibo\Exception\NotFoundException * @throws XiboException */ private function publishLayouts() { $this->runMessage .= '## ' . __('Publishing layouts with set publish dates') . PHP_EOL; $layouts = $this->layoutFactory->query(null, ['havePublishDate' => 1]); // check if we have any layouts with set publish date if (count($layouts) > 0) { foreach ($layouts as $layout) { // check if the layout should be published now according to the date if ($this->date->parse($layout->publishedDate)->format('U') < $this->date->getLocalDate(null, 'U')) { try { // check if draft is valid if ($layout->status === ModuleWidget::$STATUS_INVALID && isset($layout->statusMessage)) { throw new XiboException(__($layout->statusMessage)); } else { // publish the layout $draft = $this->layoutFactory->getByParentId($layout->layoutId); $draft->publishDraft(); $draft->load(); $draft->xlfToDisk(['notify' => true, 'exceptionOnError' => true, 'exceptionOnEmptyRegion' => false]); $this->log->info('Published layout ID ' . $layout->layoutId . ' new layout id is ' . $draft->layoutId); } } catch (XiboException $e) { $this->log->error('Error publishing layout ID ' . $layout->layoutId . ' Failed with message: ' . $e->getMessage()); // create a notification $subject = __(sprintf('Error publishing layout ID %d', $layout->layoutId)); $date = $this->date->parse(); if (count($this->notificationFactory->getBySubjectAndDate($subject, $this->date->getLocalDate($date->startOfDay(), 'U'), $this->date->getLocalDate($date->addDay(1)->startOfDay(), 'U'))) <= 0) { $body = __(sprintf('Publishing layout ID %d failed. With message %s', $layout->layoutId, $e->getMessage())); $notification = $this->notificationFactory->createSystemNotification( $subject, $body, $this->date->parse() ); $notification->save(); $this->log->critical($subject); } } } else { $this->log->debug('Layouts with published date were found, they are set to publish later than current time'); } } } else { $this->log->debug('No layouts to publish.'); } $this->runMessage .= ' - Done' . PHP_EOL . PHP_EOL; } }PK 7qYC C MaintenanceDailyTask.phpnu [ libraryController = $container->get('\Xibo\Controller\Library'); $this->layoutFactory = $container->get('layoutFactory'); $this->userFactory = $container->get('userFactory'); return $this; } /** @inheritdoc */ public function run() { $this->runMessage = '# ' . __('Daily Maintenance') . PHP_EOL . PHP_EOL; // Long running task set_time_limit(0); // Import layouts $this->importLayouts(); // Install module files $this->installModuleFiles(); // Tidy logs $this->tidyLogs(); // Tidy Cache $this->tidyCache(); // API tokens $this->purgeExpiredApiTokens(); } /** * Tidy the DB logs */ private function tidyLogs() { $this->runMessage .= '## ' . __('Tidy Logs') . PHP_EOL; if ($this->config->getSetting('MAINTENANCE_LOG_MAXAGE') != 0) { $maxage = date('Y-m-d H:i:s', time() - (86400 * intval($this->config->getSetting('MAINTENANCE_LOG_MAXAGE')))); try { $this->store->update('DELETE FROM `log` WHERE logdate < :maxage', ['maxage' => $maxage]); $this->runMessage .= ' - ' . __('Done.') . PHP_EOL . PHP_EOL; } catch (\PDOException $e) { $this->runMessage .= ' - ' . __('Error.') . PHP_EOL . PHP_EOL; $this->log->error($e->getMessage()); } } 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 XiboException */ private function importLayouts() { $this->runMessage .= '## ' . __('Import Layouts') . PHP_EOL; if ($this->config->getSetting('DEFAULTS_IMPORTED') == 0) { $folder = $this->config->uri('layouts', true); foreach (array_diff(scandir($folder), array('..', '.')) as $file) { if (stripos($file, '.zip')) { $layout = $this->layoutFactory->createFromZip( $folder . '/' . $file, null, $this->userFactory->getSystemUser()->getId(), false, false, true, false, true, $this->libraryController ); $layout->save([ 'audit' => false ]); } } $this->config->changeSetting('DEFAULTS_IMPORTED', 1); $this->runMessage .= ' - ' . __('Done.') . PHP_EOL . PHP_EOL; } else { $this->runMessage .= ' - ' . __('Not Required.') . PHP_EOL . PHP_EOL; } } /** * Install Module Files */ private function installModuleFiles() { $this->libraryController->installAllModuleFiles(); } /** * Purge expired API tokens */ private function purgeExpiredApiTokens() { $this->runMessage .= '## ' . __('Purge Expired API Tokens') . PHP_EOL; $params = ['now' => time()]; try { // Run delete SQL for all token and session tables. $this->store->update('DELETE FROM `oauth_refresh_tokens` WHERE expire_time < :now', $params); $this->store->update(' DELETE FROM `oauth_sessions` WHERE id IN ( SELECT session_id FROM oauth_access_tokens WHERE expire_time < :now AND access_token NOT IN (SELECT access_token FROM oauth_refresh_tokens) ) ', $params); // Delete access_tokens without a refresh token $this->store->update(' DELETE FROM `oauth_access_tokens` WHERE expire_time < :now AND access_token NOT IN ( SELECT access_token FROM oauth_refresh_tokens ) ', $params); $this->store->update(' DELETE FROM `oauth_access_token_scopes` WHERE access_token NOT IN ( SELECT access_token FROM oauth_access_tokens ) ', []); // Auth codes $this->store->update(' DELETE FROM `oauth_sessions` WHERE id IN ( SELECT session_id FROM oauth_auth_codes WHERE expire_time < :now ) ', $params); $this->store->update(' DELETE FROM `oauth_auth_codes` WHERE expire_time < :now', $params); $this->store->update(' DELETE FROM `oauth_auth_code_scopes` WHERE auth_code NOT IN ( SELECT auth_code FROM oauth_auth_codes ) ', []); // Delete session scopes $this->store->update(' DELETE FROM `oauth_session_scopes` WHERE session_id NOT IN ( SELECT id FROM oauth_sessions ) ', []); $this->runMessage .= ' - ' . __('Done.') . PHP_EOL . PHP_EOL; } catch (\PDOException $PDOException) { $this->log->debug($PDOException->getTraceAsString()); $this->log->error($PDOException->getMessage()); $this->runMessage .= ' - ' . __('Error.') . PHP_EOL . PHP_EOL; } } }PK 7qYc|SI I UpdateEmptyVideoDurations.phpnu [ mediaFactory = $container->get('mediaFactory'); $this->moduleFactory = $container->get('moduleFactory'); return $this; } /** @inheritdoc */ public function run() { $libraryLocation = $this->config->getSetting('LIBRARY_LOCATION'); $videos = $this->mediaFactory->getByMediaType('video'); foreach ($videos as $video) { /* @var \Xibo\Entity\Media $video */ if ($video->duration == 0) { // Update $module = $this->moduleFactory->createWithMedia($video); $video->duration = $module->determineDuration($libraryLocation . $video->storedAs); $video->save(['validate' => false]); } } } }PK 7qYy ! ! StatsArchiveTask.phpnu [ userFactory = $container->get('userFactory'); $this->mediaFactory = $container->get('mediaFactory'); return $this; } /** @inheritdoc */ public function run() { $this->archiveStats(); $this->tidyStats(); } public function archiveStats() { $this->runMessage = '# ' . __('Stats Archive') . PHP_EOL . PHP_EOL; if ($this->getOption('archiveStats', "Off") == "On") { // Archive tasks by week. $periodSizeInDays = $this->getOption('periodSizeInDays', 7); $maxPeriods = $this->getOption('maxPeriods', 4); $periodsToKeep = $this->getOption('periodsToKeep', 1); $this->setArchiveOwner(); // Get the earliest $earliestDate = $this->timeSeriesStore->getEarliestDate(); if (count($earliestDate) <= 0) { $this->runMessage = __('Nothing to archive'); return; } /** @var Date $earliestDate */ $earliestDate = $this->date->parse($earliestDate['minDate'], 'U')->setTime(0, 0, 0); // Take the earliest date and roll forward until the current time /** @var Date $now */ $now = $this->date->parse()->subDay($periodSizeInDays * $periodsToKeep)->setTime(0, 0, 0); $i = 0; while ($earliestDate < $now && $i < $maxPeriods) { $i++; $this->log->debug('Running archive number ' . $i); // Push forward $fromDt = $earliestDate->copy(); $earliestDate->addDays($periodSizeInDays); $this->exportStatsToLibrary($fromDt, $earliestDate); $this->store->commitIfNecessary(); } $this->runMessage .= ' - ' . __('Done') . PHP_EOL . PHP_EOL; } else { $this->runMessage .= ' - ' . __('Disabled') . PHP_EOL . PHP_EOL; } } /** * Export stats to the library * @param Date $fromDt * @param Date $toDt */ private function exportStatsToLibrary($fromDt, $toDt) { $this->runMessage .= ' - ' . $this->date->getLocalDate($fromDt) . ' / ' . $this->date->getLocalDate($toDt) . PHP_EOL; $resultSet = $this->timeSeriesStore->getStats([ 'fromDt'=> $fromDt, 'toDt'=> $toDt, ]); // Create a temporary file for this $fileName = tempnam(sys_get_temp_dir(), 'stats'); $out = fopen($fileName, 'w'); fputcsv($out, ['Stat Date', 'Type', 'FromDT', 'ToDT', 'Layout', 'Display', 'Media', 'Tag', 'Duration', 'Count', 'DisplayId', 'LayoutId', 'WidgetId', 'MediaId']); while ($row = $resultSet->getNextRow() ) { if ($this->timeSeriesStore->getEngine() == 'mongodb') { $statDate = isset($row['statDate']) ? $this->date->parse($row['statDate']->toDateTime()->format('U'), 'U')->format('Y-m-d H:i:s') : null; $start = $this->date->parse($row['start']->toDateTime()->format('U'), 'U')->format('Y-m-d H:i:s'); $end = $this->date->parse($row['end']->toDateTime()->format('U'), 'U')->format('Y-m-d H:i:s'); } else { $statDate = isset($row['statDate']) ?$this->date->parse($row['statDate'], 'U')->format('Y-m-d H:i:s') : null; $start = $this->date->parse($row['start'], 'U')->format('Y-m-d H:i:s'); $end = $this->date->parse($row['end'], 'U')->format('Y-m-d H:i:s'); } // Read the columns fputcsv($out, [ $statDate, $this->sanitizer->string($row['type']), $start, $end, isset($row['layout']) ? $this->sanitizer->string($row['layout']) :'', isset($row['display']) ? $this->sanitizer->string($row['display']) :'', isset($row['media']) ? $this->sanitizer->string($row['media']) :'', isset($row['tag']) ? $this->sanitizer->string($row['tag']) :'', $this->sanitizer->string($row['duration']), $this->sanitizer->string($row['count']), $this->sanitizer->int($row['displayId']), isset($row['layoutId']) ? $this->sanitizer->int($row['layoutId']) :'', isset($row['widgetId']) ? $this->sanitizer->int($row['widgetId']) :'', isset($row['mediaId']) ? $this->sanitizer->int($row['mediaId']) :'', ]); } fclose($out); // Create a ZIP file and add our temporary file $zipName = $this->config->getSetting('LIBRARY_LOCATION') . 'temp/stats.csv.zip'; $zip = new \ZipArchive(); $result = $zip->open($zipName, \ZipArchive::CREATE | \ZipArchive::OVERWRITE); if ($result !== true) throw new \InvalidArgumentException(__('Can\'t create ZIP. Error Code: %s', $result)); $zip->addFile($fileName, 'stats.csv'); $zip->close(); // Remove the CSV file unlink($fileName); // Upload to the library $media = $this->mediaFactory->create( __('Stats Export %s to %s - %s', $fromDt->format('Y-m-d'), $toDt->format('Y-m-d'), Random::generateString(5)), 'stats.csv.zip', 'genericfile', $this->archiveOwner->getId() ); $media->save(); // Set max attempts to -1 so that we continue deleting until we've removed all of the stats that we've exported $options = [ 'maxAttempts' => -1, 'statsDeleteSleep' => 1, 'limit' => 1000 ]; // Delete the stats, incrementally $this->timeSeriesStore->deleteStats($toDt, $fromDt, $options); } /** * Set the archive owner * @throws TaskRunException */ private function setArchiveOwner() { $archiveOwner = $this->getOption('archiveOwner', null); if ($archiveOwner == null) { $admins = $this->userFactory->getSuperAdmins(); if (count($admins) <= 0) throw new TaskRunException(__('No super admins to use as the archive owner, please set one in the configuration.')); $this->archiveOwner = $admins[0]; } else { try { $this->archiveOwner = $this->userFactory->getByName($archiveOwner); } catch (NotFoundException $e) { throw new TaskRunException(__('Archive Owner not found')); } } } /** * Tidy Stats */ private function tidyStats() { $this->runMessage .= '## ' . __('Tidy Stats') . PHP_EOL; if ($this->config->getSetting('MAINTENANCE_STAT_MAXAGE') != 0) { $maxage = Date::now()->subDays(intval($this->config->getSetting('MAINTENANCE_STAT_MAXAGE'))); $maxAttempts = $this->getOption('statsDeleteMaxAttempts', 10); $statsDeleteSleep = $this->getOption('statsDeleteSleep', 3); $options = [ 'maxAttempts' => $maxAttempts, 'statsDeleteSleep' => $statsDeleteSleep, 'limit' => 10000 // Note: for mongo we dont use $options['limit'] anymore ]; try { $result = $this->timeSeriesStore->deleteStats($maxage, null, $options); if ($result > 0) { $this->runMessage .= ' - ' . __('Done.') . PHP_EOL . PHP_EOL; } } catch (\Exception $exception) { $this->runMessage .= ' - ' . __('Error.') . PHP_EOL . PHP_EOL; } } else { $this->runMessage .= ' - ' . __('Disabled') . PHP_EOL . PHP_EOL; } } }PK 7qYI?! ?! RemoteDataSetFetchTask.phpnu [ . */ namespace Xibo\XTR; use Xibo\Entity\DataSet; use Xibo\Exception\XiboException; use Xibo\Factory\DataSetFactory; use Xibo\Factory\NotificationFactory; use Xibo\Factory\UserFactory; use Xibo\Factory\UserGroupFactory; /** * Class RemoteDataSetFetchTask * @package Xibo\XTR */ class RemoteDataSetFetchTask implements TaskInterface { use TaskTrait; /** @var DataSetFactory */ private $dataSetFactory; /** @var NotificationFactory */ private $notificationFactory; /** @var UserFactory */ private $userFactory; /** @var UserGroupFactory */ private $userGroupFactory; /** @inheritdoc */ public function setFactories($container) { $this->dataSetFactory = $container->get('dataSetFactory'); $this->notificationFactory = $container->get('notificationFactory'); $this->userFactory = $container->get('userFactory'); $this->userGroupFactory = $container->get('userGroupFactory'); return $this; } /** * @inheritdoc */ public function run() { $this->runMessage = '# ' . __('Fetching Remote-DataSets') . PHP_EOL . PHP_EOL; $runTime = $this->date->getLocalDate(null, 'U'); /** @var DataSet $dataSet */ $dataSet = null; // Process all Remote DataSets (and their dependants) $dataSets = $this->orderDataSetsByDependency($this->dataSetFactory->query(null, ['isRemote' => 1])); // Log the order. $this->log->debug('Order of processing: ' . json_encode(array_map(function($element) { return $element->dataSetId . ' - ' . $element->runsAfter; }, $dataSets)) ); // Reorder this list according to which order we want to run in foreach ($dataSets as $dataSet) { $this->log->debug('Processing ' . $dataSet->dataSet . '. ID:' . $dataSet->dataSetId); try { // Has this dataSet been accessed recently? if (!$dataSet->isActive()) { // Skipping dataSet due to it not being accessed recently $this->log->info('Skipping dataSet ' . $dataSet->dataSetId . ' due to it not being accessed recently'); continue; } $this->log->debug('Comparing run time ' . $runTime . ' to next sync time ' . $dataSet->getNextSyncTime()); if ($runTime >= $dataSet->getNextSyncTime()) { // Getting the dependant DataSet to process the current DataSet on $dependant = null; if ($dataSet->runsAfter != null && $dataSet->runsAfter != $dataSet->dataSetId) { $dependant = $this->dataSetFactory->getById($dataSet->runsAfter); } $this->log->debug('Fetch and process ' . $dataSet->dataSet); $results = $this->dataSetFactory->callRemoteService($dataSet, $dependant); if ($results->number > 0) { // Truncate only if we also fetch new Data if ($dataSet->isTruncateEnabled() && $runTime >= $dataSet->getNextClearTime()) { $this->log->debug('Truncate ' . $dataSet->dataSet); $dataSet->deleteData(); // Update the last clear time. $dataSet->saveLastClear($runTime); } if ($dataSet->sourceId === 1) { $this->dataSetFactory->processResults($dataSet, $results); } else { $this->dataSetFactory->processCsvEntries($dataSet, $results); } // notify here $dataSet->notify(); } else { $this->appendRunMessage(__('No results for %s', $dataSet->dataSet)); } $dataSet->saveLastSync($runTime); } else { $this->log->debug('Sync not required for ' . $dataSet->dataSetId); } } catch (XiboException $e) { $this->appendRunMessage(__('Error syncing DataSet %s', $dataSet->dataSet)); $this->log->error('Error syncing DataSet ' . $dataSet->dataSetId . '. E = ' . $e->getMessage()); $this->log->debug($e->getTraceAsString()); // Send a notification to the dataSet owner, informing them of the failure. $notification = $this->notificationFactory->createEmpty(); $notification->subject = __('Remote DataSet %s failed to synchronise', $dataSet->dataSet); $notification->body = 'The error is: ' . $e->getMessage(); $notification->createdDt = $this->date->getLocalDate(null, 'U'); $notification->releaseDt = $notification->createdDt; $notification->isEmail = 0; $notification->isInterrupt = 0; $notification->userId = $this->user->userId; // Assign me $dataSetUser = $this->userFactory->getById($dataSet->userId); $notification->assignUserGroup($this->userGroupFactory->getById($dataSetUser->groupId)); // Send $notification->save(); // You might say at this point that if there are other data sets further down the list, we shouldn't // continue because they might depend directly on this one // however, it is my opinion that they should be processed anyway with the current cache of data. // hence continue } } $this->appendRunMessage(__('Done')); } /** * Order the list of DataSets to be processed so that it is dependent aware. * * @param DataSet[] $dataSets Reference to an Array which holds all not yet processed DataSets * @return DataSet[] Ordered list of DataSets to process * * * What is going on here: RemoteDataSets can depend on others, so we have to be sure to fetch * the data from the dependant first. * For Example (id, dependant): (1,4), (2,3), (3,4), (4,1), (5,2), (6,6) * Should be processed like: 4, 1, 3, 2, 5, 6 * */ private function orderDataSetsByDependency(array $dataSets) { // DataSets are in no particular order // sort them according to their dependencies usort($dataSets, function($a, $b) { /** @var DataSet $a */ /** @var DataSet $b */ // if a doesn't have a dependent, then a must be lower in the list (move b up) if ($a->runsAfter === null) return -1; // if b doesn't have a dependent, then a must be higher in the list (move b down) if ($b->runsAfter === null) return 1; // either a or b have a dependent // if they are the same, keep them where they are if ($a->runsAfter === $b->runsAfter) return 0; // the dependents are different. // if a depends on b, then move b up if ($a->runsAfter === $b->dataSetId) return -1; // if b depends on a, then move b down if ($b->runsAfter === $a->dataSetId) return 1; // Unsorted return 0; }); // Process in reverse order (LastIn-FirstOut) return array_reverse($dataSets); } }PK 7qYg" " ScheduleReminderTask.phpnu [ . */ namespace Xibo\XTR; use Xibo\Entity\ScheduleReminder; use Xibo\Exception\NotFoundException; use Xibo\Factory\CampaignFactory; use Xibo\Factory\NotificationFactory; use Xibo\Factory\ScheduleFactory; use Xibo\Factory\ScheduleReminderFactory; use Xibo\Factory\UserFactory; use Xibo\Factory\UserGroupFactory; use Xibo\Service\DateServiceInterface; /** * Class ScheduleReminderTask * @package Xibo\XTR */ class ScheduleReminderTask implements TaskInterface { use TaskTrait; /** @var DateServiceInterface */ private $date; /** @var UserFactory */ private $userFactory; /** @var ScheduleFactory */ private $scheduleFactory; /** @var CampaignFactory */ private $campaignFactory; /** @var ScheduleReminderFactory */ private $scheduleReminderFactory; /** @var NotificationFactory */ private $notificationFactory; /** @var UserGroupFactory */ private $userGroupFactory; /** @inheritdoc */ public function setFactories($container) { $this->date = $container->get('dateService'); $this->userFactory = $container->get('userFactory'); $this->scheduleFactory = $container->get('scheduleFactory'); $this->campaignFactory = $container->get('campaignFactory'); $this->scheduleReminderFactory = $container->get('scheduleReminderFactory'); $this->notificationFactory = $container->get('notificationFactory'); $this->userGroupFactory = $container->get('userGroupFactory'); return $this; } /** @inheritdoc */ public function run() { $this->runMessage = '# ' . __('Schedule reminder') . PHP_EOL . PHP_EOL; $this->runScheduleReminder(); } /** * */ private function runScheduleReminder() { $task = $this->getTask(); $nextRunDate = $task->nextRunDate(); $task->lastRunDt = time(); $task->save(); // Get those reminders that have reminderDt <= nextRunDate && reminderDt > lastReminderDt // Those which have reminderDt < lastReminderDt exclude them $reminders = $this->scheduleReminderFactory->getDueReminders($nextRunDate); foreach($reminders as $reminder) { // Get the schedule $schedule = $this->scheduleFactory->getById($reminder->eventId); $schedule->setCampaignFactory($this->campaignFactory); $title = $schedule->getEventTitle(); switch ($reminder->type) { case ScheduleReminder::$TYPE_MINUTE: $type = ScheduleReminder::$MINUTE; $typeText = 'Minute(s)'; break; case ScheduleReminder::$TYPE_HOUR: $type = ScheduleReminder::$HOUR; $typeText = 'Hour(s)'; break; case ScheduleReminder::$TYPE_DAY: $type = ScheduleReminder::$DAY; $typeText = 'Day(s)'; break; case ScheduleReminder::$TYPE_WEEK: $type = ScheduleReminder::$WEEK; $typeText = 'Week(s)'; break; case ScheduleReminder::$TYPE_MONTH: $type = ScheduleReminder::$MONTH; $typeText = 'Month(s)'; break; default: $this->log->error('Unknown schedule reminder type has been provided'); continue; } switch ($reminder->option) { case ScheduleReminder::$OPTION_BEFORE_START: $typeOptionText = 'starting'; break; case ScheduleReminder::$OPTION_AFTER_START: $typeOptionText = 'started'; break; case ScheduleReminder::$OPTION_BEFORE_END: $typeOptionText = 'ending'; break; case ScheduleReminder::$OPTION_AFTER_END: $typeOptionText = 'ended'; break; default: $this->log->error('Unknown schedule reminder option has been provided'); continue; } // Create a notification $subject = sprintf(__("Reminder for %s"), $title); if ($reminder->option == ScheduleReminder::$OPTION_BEFORE_START || $reminder->option == ScheduleReminder::$OPTION_BEFORE_END) { $body = sprintf(__("The event (%s) is %s in %d %s"), $title, $typeOptionText, $reminder->value, $typeText); } elseif ($reminder->option == ScheduleReminder::$OPTION_AFTER_START || $reminder->option == ScheduleReminder::$OPTION_AFTER_END) { $body = sprintf(__("The event (%s) has %s %d %s ago"), $title, $typeOptionText, $reminder->value, $typeText); } // Is this schedule a recurring event? if ($schedule->recurrenceType != '') { $now = $this->date->parse(); $remindSeconds = $reminder->value * $type; // Get the next reminder date $nextReminderDate = 0; try { $nextReminderDate = $schedule->getNextReminderDate($now, $reminder, $remindSeconds); } catch (NotFoundException $error) { $this->log->error('No next occurrence of reminderDt found.'); } $i = 0; $lastReminderDate = $reminder->reminderDt; while ($nextReminderDate != 0 && $nextReminderDate < $nextRunDate) { // Keep the last reminder date $lastReminderDate = $nextReminderDate; $now = $this->date->parse($nextReminderDate + 1, 'U'); try { $nextReminderDate = $schedule->getNextReminderDate($now, $reminder, $remindSeconds); } catch (NotFoundException $error) { $nextReminderDate = 0; $this->log->debug('No next occurrence of reminderDt found. ReminderDt set to 0.'); } $this->createNotification($subject, $body, $reminder, $schedule, $lastReminderDate); $i++; } if ($i == 0) { // Create only 1 notification as the next event is outside the nextRunDt $this->createNotification($subject, $body, $reminder, $schedule, $reminder->reminderDt); $this->log->debug('Create only 1 notification as the next event is outside the nextRunDt.'); } else { $this->log->debug($i. ' notifications created.'); } $reminder->reminderDt = $nextReminderDate; $reminder->lastReminderDt = $lastReminderDate; $reminder->save(); } else { // one-off event $this->createNotification($subject, $body, $reminder, $schedule, $reminder->reminderDt); // Current reminderDt will be used as lastReminderDt $reminder->lastReminderDt = $reminder->reminderDt; } // Save $reminder->save(); } } private function createNotification($subject, $body, $reminder, $schedule, $releaseDt = null) { $notification = $this->notificationFactory->createEmpty(); $notification->subject = $subject; $notification->body = $body; $notification->createdDt = $this->date->getLocalDate(null, 'U'); $notification->releaseDt = $releaseDt; $notification->isEmail = $reminder->isEmail; $notification->isInterrupt = 0; $notification->userId = $schedule->userId; // event owner // Get user group to create user notification $notificationUser = $this->userFactory->getById($schedule->userId); $notification->assignUserGroup($this->userGroupFactory->getById($notificationUser->groupId)); $notification->save(); } }PK 7qY&U U ImageProcessingTask.phpnu [ . */ namespace Xibo\XTR; use Xibo\Entity\DisplayGroup; use Xibo\Factory\DisplayFactory; use Xibo\Factory\MediaFactory; use Xibo\Factory\ScheduleFactory; use Xibo\Service\DateServiceInterface; use Xibo\Service\ImageProcessingServiceInterface; /** * Class ImageProcessingTask * @package Xibo\XTR */ class ImageProcessingTask implements TaskInterface { use TaskTrait; /** @var DateServiceInterface */ private $date; /** @var ImageProcessingServiceInterface */ private $imageProcessingService; /** @var MediaFactory */ private $mediaFactory; /** @var DisplayFactory */ private $displayFactory; /** @inheritdoc */ public function setFactories($container) { $this->date = $container->get('dateService'); $this->mediaFactory = $container->get('mediaFactory'); $this->displayFactory = $container->get('displayFactory'); $this->imageProcessingService = $container->get('imageProcessingService'); return $this; } /** @inheritdoc */ public function run() { $this->runMessage = '# ' . __('Image Processing') . PHP_EOL . PHP_EOL; // Long running task set_time_limit(0); $this->runImageProcessing(); } /** * */ private function runImageProcessing() { $images = $this->mediaFactory->query(null, ['released' => 0, 'allModules' => 1, 'imageProcessing' => 1]); $libraryLocation = $this->config->getSetting('LIBRARY_LOCATION'); $resizeThreshold = $this->config->getSetting('DEFAULT_RESIZE_THRESHOLD'); $count = 0; // All displayIds $displayIds = []; // Get list of Images foreach ($images as $media) { $filePath = $libraryLocation . $media->storedAs; list($imgWidth, $imgHeight) = @getimagesize($filePath); // Orientation of the image if ($imgWidth > $imgHeight) { // 'landscape'; $this->imageProcessingService->resizeImage($filePath, $resizeThreshold, null); } else { // 'portrait'; $this->imageProcessingService->resizeImage($filePath, null, $resizeThreshold); } // Clears file status cache clearstatcache(true, $filePath); $count++; // Release image and save $media->release(md5_file($filePath), filesize($filePath)); $this->store->commitIfNecessary(); $mediaDisplays= []; $sql = 'SELECT displayId FROM `requiredfile` WHERE itemId = :itemId'; foreach ($this->store->select($sql, ['itemId' => $media->mediaId]) as $row) { $displayIds[] = $row['displayId']; $mediaDisplays[] = $row['displayId']; } // Update Required Files foreach ($mediaDisplays as $displayId) { $this->store->update('UPDATE `requiredfile` SET released = :released, size = :size WHERE `requiredfile`.displayId = :displayId AND `requiredfile`.itemId = :itemId ', [ 'released' => 1, 'size' => $media->fileSize, 'displayId' => $displayId, 'itemId' => $media->mediaId ]); } } // Notify display if ($count > 0) { foreach (array_unique($displayIds) as $displayId) { // Get display $display = $this->displayFactory->getById($displayId); $display->notify(); } } $this->appendRunMessage('Released and modified image count. ' . $count); } }PK 7qYu DataSetConvertTask.phpnu [ dataSetFactory = $container->get('dataSetFactory'); return $this; } /** @inheritdoc */ public function run() { // Protect against us having run before if ($this->store->exists('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = :schema AND TABLE_NAME = :name', [ 'schema' => $_SERVER['MYSQL_DATABASE'], 'name' => 'datasetdata' ])) { // Get all DataSets foreach ($this->dataSetFactory->query() as $dataSet) { /* @var \Xibo\Entity\DataSet $dataSet */ // Rebuild the data table $dataSet->rebuild(); // Load the existing data from datasetdata foreach (self::getExistingData($dataSet) as $row) { $dataSet->addRow($row); } } // Drop data set data $this->store->update('DROP TABLE `datasetdata`;', []); } // Disable the task $this->getTask()->isActive = 0; $this->appendRunMessage('Conversion Completed'); } /** * Data Set Results * @param DataSet $dataSet * @return array * @throws XiboException */ public function getExistingData($dataSet) { $dbh = $this->store->getConnection(); $params = array('dataSetId' => $dataSet->dataSetId); $selectSQL = ''; $outerSelect = ''; foreach ($dataSet->getColumn() as $col) { /* @var \Xibo\Entity\DataSetColumn $col */ if ($col->dataSetColumnTypeId != 1) continue; $selectSQL .= sprintf("MAX(CASE WHEN DataSetColumnID = %d THEN `Value` ELSE null END) AS '%s', ", $col->dataSetColumnId, $col->heading); $outerSelect .= sprintf(' `%s`,', $col->heading); } $outerSelect = rtrim($outerSelect, ','); // We are ready to build the select and from part of the SQL $SQL = "SELECT $outerSelect "; $SQL .= " FROM ( "; $SQL .= " SELECT $outerSelect ,"; $SQL .= " RowNumber "; $SQL .= " FROM ( "; $SQL .= " SELECT $selectSQL "; $SQL .= " RowNumber "; $SQL .= " FROM ("; $SQL .= " SELECT datasetcolumn.DataSetColumnID, datasetdata.RowNumber, datasetdata.`Value` "; $SQL .= " FROM datasetdata "; $SQL .= " INNER JOIN datasetcolumn "; $SQL .= " ON datasetcolumn.DataSetColumnID = datasetdata.DataSetColumnID "; $SQL .= " WHERE datasetcolumn.DataSetID = :dataSetId "; $SQL .= " ) datasetdatainner "; $SQL .= " GROUP BY RowNumber "; $SQL .= " ) datasetdata "; $SQL .= ' ) finalselect '; $SQL .= " ORDER BY RowNumber "; $sth = $dbh->prepare($SQL); $sth->execute($params); return $sth->fetchAll(\PDO::FETCH_ASSOC); } }PK 7qYTJ" J" LayoutConvertTask.phpnu [ permissionFactory = $container->get('permissionFactory'); $this->layoutFactory = $container->get('layoutFactory'); $this->moduleFactory = $container->get('moduleFactory'); return $this; } /** @inheritdoc */ public function run() { // lklayoutmedia is removed at the end of this task if (!$this->store->exists('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :name', [ 'name' => 'lklayoutmedia' ])) { $this->appendRunMessage('Already converted'); // Disable the task $this->disableTask(); // Don't do anything further return; } // Permissions handling // ------------------- // Layout permissions should remain the same // the lklayoutmediagroup table and lklayoutregiongroup table will be removed // We do not have simple switch for the lklayoutmediagroup table as these are supposed to represent "Widgets" // which did not exist at this point. // Build a keyed array of existing widget permissions $mediaPermissions = []; foreach ($this->store->select(' SELECT `lklayoutmediagroup`.groupId, `lkwidgetmedia`.widgetId, `view`, `edit`, `del` FROM `lklayoutmediagroup` INNER JOIN `lkwidgetmedia` ON `lklayoutmediagroup`.`mediaId` = `lkwidgetmedia`.widgetId WHERE `lkwidgetmedia`.widgetId IN ( SELECT widget.widgetId FROM `widget` INNER JOIN `playlist` ON `playlist`.playlistId = `widget`.playlistId WHERE `playlist`.regionId = `lklayoutmediagroup`.regionId ) ', []) as $row) { $permission = $this->permissionFactory->create( $row['groupId'], Widget::class, $row['widgetId'], $row['view'], $row['edit'], $row['del'] ); $mediaPermissions[$row['mediaId']] = $permission; } // Build a keyed array of existing region permissions $regionPermissions = []; foreach ($this->store->select('SELECT groupId, layoutId, regionId, `view`, `edit`, `del` FROM `lklayoutregiongroup`', []) as $row) { $permission = $this->permissionFactory->create( $row['groupId'], Region::class, $row['regionId'], $row['view'], $row['edit'], $row['del'] ); $regionPermissions[$row['regionId']] = $permission; } // Get the library location to store backups of existing XLF $libraryLocation = $this->config->getSetting('LIBRARY_LOCATION'); // We need to go through each layout, save the XLF as a backup in the library and then upgrade it. // This task applies to Layouts which are schemaVersion 2 or lower. xibosignage/xibo#2056 foreach ($this->store->select('SELECT layoutId, xml FROM `layout` WHERE schemaVersion <= :schemaVersion', [ 'schemaVersion' => 2 ]) as $oldLayout) { $oldLayoutId = intval($oldLayout['layoutId']); try { // Does this layout have any XML associated with it? If not, then it is an empty layout. if (empty($oldLayout['xml'])) { // This is frankly, odd, so we better log it $this->log->critical('Layout upgrade without any existing XLF, i.e. empty. ID = ' . $oldLayoutId); // Pull out the layout record, and set some best guess defaults $layout = $this->layoutFactory->getById($oldLayoutId); // We have to guess something here as we do not have any XML to go by. Default to landscape 1080p. $layout->width = 1920; $layout->height = 1080; } else { // Save off a copy of the XML in the library file_put_contents($libraryLocation . 'archive_' . $oldLayoutId . '.xlf', $oldLayout['xml']); // Create a new layout from the XML $layout = $this->layoutFactory->loadByXlf($oldLayout['xml'], $this->layoutFactory->getById($oldLayoutId)); } // We need one final pass through all widgets on the layout so that we can set the durations properly. foreach ($layout->getWidgets() as $widget) { $module = $this->moduleFactory->createWithWidget($widget); $widget->calculateDuration($module, true); // Get global stat setting of widget to set to on/off/inherit $widget->setOptionValue('enableStat', 'attrib', $this->config->getSetting('WIDGET_STATS_ENABLED_DEFAULT')); } // Save the layout $layout->schemaVersion = Environment::$XLF_VERSION; $layout->save(['notify' => false, 'audit' => false]); // Now that we have new ID's we need to cross reference them with the old IDs and recreate the permissions foreach ($layout->regions as $region) { /* @var \Xibo\Entity\Region $region */ if (array_key_exists($region->tempId, $regionPermissions)) { $permission = $regionPermissions[$region->tempId]; /* @var \Xibo\Entity\Permission $permission */ // Double check we are for the same layout if ($permission->objectId == $layout->layoutId) { $permission = clone $permission; $permission->objectId = $region->regionId; $permission->save(); } } /* @var \Xibo\Entity\Playlist $playlist */ foreach ($region->getPlaylist()->widgets as $widget) { /* @var \Xibo\Entity\Widget $widget */ if (array_key_exists($widget->tempId, $mediaPermissions)) { $permission = $mediaPermissions[$widget->tempId]; /* @var \Xibo\Entity\Permission $permission */ if ($permission->objectId == $layout->layoutId && $region->tempId == $permission->objectIdString) { $permission = clone $permission; $permission->objectId = $widget->widgetId; $permission->save(); } } } } } catch (\Exception $e) { $this->appendRunMessage('Error upgrading Layout, this should be checked post-upgrade. ID: ' . $oldLayoutId); $this->log->critical('Error upgrading Layout, this should be checked post-upgrade. ID: ' . $oldLayoutId); $this->log->error($e->getMessage() . ' - ' . $e->getTraceAsString()); } } $this->appendRunMessage('Finished converting, dropping unnecessary tables.'); // Drop the permissions $this->store->update('DROP TABLE `lklayoutmediagroup`;', []); $this->store->update('DROP TABLE `lklayoutregiongroup`;', []); $this->store->update('DROP TABLE lklayoutmedia', []); $this->store->update('ALTER TABLE `layout` DROP `xml`;', []); // Disable the task $this->disableTask(); $this->appendRunMessage('Conversion Completed'); } /** * Disables and saves this task immediately */ private function disableTask() { $this->getTask()->isActive = 0; $this->getTask()->save(); $this->store->commitIfNecessary(); } }PK 7qYk2 2 AuditLogArchiveTask.phpnu [ mediaFactory = $container->get('mediaFactory'); $this->userFactory = $container->get('userFactory'); return $this; } /** @inheritdoc */ public function run() { // Archive tasks by week. $maxPeriods = $this->getOption('maxPeriods', 1); $this->setArchiveOwner(); // Delete or Archive if ($this->getOption('deleteInstead', 1) == 1) { $this->runMessage = '# ' . __('AuditLog Delete') . PHP_EOL . PHP_EOL; // Delete all audit log messages older than 1 month $this->store->update('DELETE FROM `auditlog` WHERE logDate < :logDate', [ 'logDate' => $this->date->parse()->subMonth(1)->setTime(0, 0, 0)->format('U') ]); } else { $this->runMessage = '# ' . __('AuditLog Archive') . PHP_EOL . PHP_EOL; // Get the earliest $earliestDate = $this->store->select('SELECT MIN(logDate) AS minDate FROM `auditlog`', []); if (count($earliestDate) <= 0) { $this->runMessage = __('Nothing to archive'); return; } /** @var Date $earliestDate */ $earliestDate = $this->date->parse($earliestDate[0]['minDate'], 'U')->setTime(0, 0, 0); // Take the earliest date and roll forward until the current time /** @var Date $now */ $now = $this->date->parse()->subMonth(1)->setTime(0, 0, 0); $i = 0; while ($earliestDate < $now && $i <= $maxPeriods) { $i++; $this->log->debug('Running archive number ' . $i); // Push forward $fromDt = $earliestDate->copy(); $earliestDate->addMonth(1); $this->exportAuditLogToLibrary($fromDt, $earliestDate); $this->store->commitIfNecessary(); } } $this->runMessage .= __('Done') . PHP_EOL . PHP_EOL; } /** * Export stats to the library * @param Date $fromDt * @param Date $toDt */ private function exportAuditLogToLibrary($fromDt, $toDt) { $this->runMessage .= ' - ' . $fromDt . ' / ' . $toDt . PHP_EOL; $sql = ' SELECT * FROM `auditlog` WHERE 1 = 1 AND logDate >= :fromDt AND logDate < :toDt '; $params = [ 'fromDt' => $this->date->getLocalDate($fromDt, 'U'), 'toDt' => $this->date->getLocalDate($toDt, 'U') ]; $sql .= " ORDER BY 1 "; $records = $this->store->select($sql, $params); if (count($records) <= 0) { $this->runMessage .= __('No audit log found for these dates') . PHP_EOL; return; } // Create a temporary file for this $fileName = $this->config->getSetting('LIBRARY_LOCATION') . 'temp/auditlog.csv'; $out = fopen($fileName, 'w'); fputcsv($out, ['logId', 'logDate', 'userId', 'message', 'entity', 'entityId', 'objectAfter']); // Do some post processing foreach ($records as $row) { // Read the columns fputcsv($out, [ $this->sanitizer->int($row['logId']), $this->sanitizer->int($row['logDate']), $this->sanitizer->int($row['userId']), $this->sanitizer->string($row['message']), $this->sanitizer->string($row['entity']), $this->sanitizer->int($row['entityId']), $this->sanitizer->string($row['objectAfter']) ]); } fclose($out); $zip = new \ZipArchive(); $result = $zip->open($fileName . '.zip', \ZipArchive::CREATE | \ZipArchive::OVERWRITE); if ($result !== true) throw new \InvalidArgumentException(__('Can\'t create ZIP. Error Code: %s', $result)); $zip->addFile($fileName, 'auditlog.csv'); $zip->close(); // Remove the CSV file unlink($fileName); // Upload to the library $media = $this->mediaFactory->create(__('AuditLog Export %s to %s', $fromDt->format('Y-m-d'), $toDt->format('Y-m-d')), 'auditlog.csv.zip', 'genericfile', $this->archiveOwner->getId()); $media->save(); // Delete the logs $this->store->update('DELETE FROM `auditlog` WHERE logDate >= :fromDt AND logDate < :toDt', $params); } /** * Set the archive owner * @throws TaskRunException */ private function setArchiveOwner() { $archiveOwner = $this->getOption('archiveOwner', null); if ($archiveOwner == null) { $admins = $this->userFactory->getSuperAdmins(); if (count($admins) <= 0) throw new TaskRunException(__('No super admins to use as the archive owner, please set one in the configuration.')); $this->archiveOwner = $admins[0]; } else { try { $this->archiveOwner = $this->userFactory->getByName($archiveOwner); } catch (NotFoundException $e) { throw new TaskRunException(__('Archive Owner not found')); } } } }PK 7qY(Te e StatsMigrationTask.phpnu [ . */ namespace Xibo\XTR; use Xibo\Entity\Task; use Xibo\Exception\NotFoundException; use Xibo\Factory\DisplayFactory; use Xibo\Factory\LayoutFactory; use Xibo\Factory\TaskFactory; /** * Class StatsMigrationTask * @package Xibo\XTR */ class StatsMigrationTask implements TaskInterface { use TaskTrait; /** @var TaskFactory */ private $taskFactory; /** @var DisplayFactory */ private $displayFactory; /** @var LayoutFactory */ private $layoutFactory; /** @var bool Does the Archive Table exist? */ private $archiveExist; /** @var array Cache of displayIds found */ private $displays = []; /** @var array Cache of displayIds not found */ private $displaysNotFound = []; /** @var Task The Stats Archive Task */ private $archiveTask; /** @inheritdoc */ public function setFactories($container) { $this->taskFactory = $container->get('taskFactory'); $this->layoutFactory = $container->get('layoutFactory'); $this->displayFactory = $container->get('displayFactory'); return $this; } /** @inheritdoc */ public function run() { $this->migrateStats(); } /** * Migrate Stats * @throws \Xibo\Exception\NotFoundException */ public function migrateStats() { // Config options $options = [ 'killSwitch' => (int)$this->getOption('killSwitch', 0), 'numberOfRecords' => (int)$this->getOption('numberOfRecords', 10000), 'numberOfLoops' => (int)$this->getOption('numberOfLoops', 1000), 'pauseBetweenLoops' => (int)$this->getOption('pauseBetweenLoops', 10), 'optimiseOnComplete' => (int)$this->getOption('optimiseOnComplete', 1), ]; // read configOverride $configOverrideFile = $this->getOption('configOverride', ''); if (!empty($configOverrideFile) && file_exists($configOverrideFile)) { $options = array_merge($options, json_decode(file_get_contents($configOverrideFile), true)); } if ($options['killSwitch'] == 0) { // Stat Archive Task $this->archiveTask = $this->taskFactory->getByClass('\Xibo\XTR\\StatsArchiveTask'); // Check stat_archive table exists $this->archiveExist = $this->store->exists('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :name', [ 'name' => 'stat_archive' ]); // Get timestore engine $timeSeriesStore = $this->timeSeriesStore->getEngine(); if ($timeSeriesStore == 'mongodb') { // If no records in both the stat and stat_archive then disable the task $statSql = $this->store->getConnection()->prepare('SELECT statId FROM stat LIMIT 1'); $statSql->execute(); $statArchiveSqlCount = 0; if ( $this->archiveExist === true) { /** @noinspection SqlResolve */ $statArchiveSql = $this->store->getConnection()->prepare('SELECT statId FROM stat_archive LIMIT 1'); $statArchiveSql->execute(); $statArchiveSqlCount = $statArchiveSql->rowCount(); } if(($statSql->rowCount() == 0) && ($statArchiveSqlCount == 0)) { $this->runMessage = '## Stat migration to Mongo' . PHP_EOL ; $this->appendRunMessage('- Both stat_archive and stat is empty. '. PHP_EOL); // Disable the task and Enable the StatsArchiver task $this->log->debug('Stats migration task is disabled as stat_archive and stat is empty'); $this->disableTask(); if ($this->archiveTask->isActive == 0) { $this->archiveTask->isActive = 1; $this->archiveTask->save(); $this->store->commitIfNecessary(); $this->appendRunMessage('Enabling Stats Archive Task.'); $this->log->debug('Enabling Stats Archive Task.'); } } else { // if any of the two tables contain any records $this->quitMigrationTaskOrDisableStatArchiveTask(); } $this->moveStatsToMongoDb($options); } // If when the task runs it finds that MongoDB is disabled, // and there isn't a stat_archive table, then it should disable itself and not run again // (work is considered to be done at that point). else { if ($this->archiveExist == true) { $this->runMessage = '## Moving from stat_archive to stat (MySQL)' . PHP_EOL; $this->quitMigrationTaskOrDisableStatArchiveTask(); // Start migration $this->moveStatsFromStatArchiveToStatMysql($options); } else { // Disable the task $this->runMessage = '## Moving from stat_archive to stat (MySQL)' . PHP_EOL ; $this->appendRunMessage('- Table stat_archive does not exist.' . PHP_EOL); $this->log->debug('Table stat_archive does not exist.'); $this->disableTask(); // Enable the StatsArchiver task if ($this->archiveTask->isActive == 0) { $this->archiveTask->isActive = 1; $this->archiveTask->save(); $this->store->commitIfNecessary(); $this->appendRunMessage('Enabling Stats Archive Task.'); $this->log->debug('Enabling Stats Archive Task.'); } } } } } public function moveStatsFromStatArchiveToStatMysql($options) { $fileName = $this->config->getSetting('LIBRARY_LOCATION') . '.watermark_stat_archive_mysql.txt'; // Get low watermark from file $watermark = $this->getWatermarkFromFile($fileName, 'stat_archive'); $numberOfLoops = 0; while ($watermark > 0) { $count = 0; /** @noinspection SqlResolve */ $stats = $this->store->getConnection()->prepare(' SELECT statId, type, statDate, scheduleId, displayId, layoutId, mediaId, widgetId, start, `end`, tag FROM stat_archive WHERE statId < :watermark ORDER BY statId DESC LIMIT :limit '); $stats->bindParam(':watermark', $watermark, \PDO::PARAM_INT); $stats->bindParam(':limit', $options['numberOfRecords'], \PDO::PARAM_INT); // Run the select $stats->execute(); // Keep count how many stats we've inserted $recordCount = $stats->rowCount(); $count+= $recordCount; // End of records if ($this->checkEndOfRecords($recordCount, $fileName) === true) { $this->appendRunMessage(PHP_EOL. '# End of records.' . PHP_EOL. '- Dropping stat_archive.'); $this->log->debug('End of records in stat_archive (migration to MYSQL). Dropping table.'); // Drop the stat_archive table /** @noinspection SqlResolve */ $this->store->update('DROP TABLE `stat_archive`;', []); $this->appendRunMessage(__('Done.'. PHP_EOL)); // Disable the task $this->disableTask(); // Enable the StatsArchiver task if ($this->archiveTask->isActive == 0) { $this->archiveTask->isActive = 1; $this->archiveTask->save(); $this->store->commitIfNecessary(); $this->appendRunMessage('Enabling Stats Archive Task.'); $this->log->debug('Enabling Stats Archive Task.'); } break; } // Loops limit end - task will need to rerun again to start from the saved watermark if ($this->checkLoopLimits($numberOfLoops, $options['numberOfLoops'], $fileName, $watermark) === true) { break; } $numberOfLoops++; $temp = []; $statIgnoredCount = 0; foreach ($stats->fetchAll() as $stat) { $watermark = $stat['statId']; $columns = 'type, statDate, scheduleId, displayId, campaignId, layoutId, mediaId, widgetId, `start`, `end`, tag, duration, `count`'; $values = ':type, :statDate, :scheduleId, :displayId, :campaignId, :layoutId, :mediaId, :widgetId, :start, :end, :tag, :duration, :count'; // Get campaignId if (($stat['type'] != 'event') && ($stat['layoutId'] != null)) { try { // Search the campaignId in the temp array first to reduce query in layouthistory if (array_key_exists($stat['layoutId'], $temp) ) { $campaignId = $temp[$stat['layoutId']]; } else { $campaignId = $this->layoutFactory->getCampaignIdFromLayoutHistory($stat['layoutId']); $temp[$stat['layoutId']] = $campaignId; } } catch (NotFoundException $error) { $statIgnoredCount+= 1; $count = $count - 1; continue; } } else { $campaignId = 0; } $params = [ 'type' => $stat['type'], 'statDate' => $this->date->parse($stat['statDate'])->format('U'), 'scheduleId' => (int) $stat['scheduleId'], 'displayId' => (int) $stat['displayId'], 'campaignId' => $campaignId, 'layoutId' => (int) $stat['layoutId'], 'mediaId' => (int) $stat['mediaId'], 'widgetId' => (int) $stat['widgetId'], 'start' => $this->date->parse($stat['start'])->format('U'), 'end' => $this->date->parse($stat['end'])->format('U'), 'tag' => $stat['tag'], 'duration' => isset($stat['duration']) ? (int) $stat['duration'] : $this->date->parse($stat['end'])->format('U') - $this->date->parse($stat['start'])->format('U'), 'count' => isset($stat['count']) ? (int) $stat['count'] : 1, ]; // Do the insert $this->store->insert('INSERT INTO `stat` (' . $columns . ') VALUES (' . $values . ')', $params); $this->store->commitIfNecessary(); } if ($statIgnoredCount > 0) { $this->appendRunMessage($statIgnoredCount. ' stat(s) were ignored while migrating'); } // Give SQL time to recover if ($watermark > 0) { $this->appendRunMessage('- '. $count. ' rows migrated.'); $this->log->debug('MYSQL stats migration from stat_archive to stat. '.$count.' rows effected, sleeping.'); sleep($options['pauseBetweenLoops']); } } } public function moveStatsToMongoDb($options) { // Migration from stat table to Mongo $this->migrationStatToMongo($options); // Migration from stat_archive table to Mongo // After migration delete only stat_archive if ($this->archiveExist == true) { $this->migrationStatArchiveToMongo($options); } } function migrationStatToMongo($options) { $this->appendRunMessage('## Moving from stat to Mongo'); $fileName = $this->config->getSetting('LIBRARY_LOCATION') . '.watermark_stat_mongo.txt'; // Get low watermark from file $watermark = $this->getWatermarkFromFile($fileName, 'stat'); $numberOfLoops = 0; while ($watermark > 0) { $count = 0; $stats = $this->store->getConnection() ->prepare('SELECT * FROM stat WHERE statId < :watermark ORDER BY statId DESC LIMIT :limit'); $stats->bindParam(':watermark', $watermark, \PDO::PARAM_INT); $stats->bindParam(':limit', $options['numberOfRecords'], \PDO::PARAM_INT); // Run the select $stats->execute(); // Keep count how many stats we've inserted $recordCount = $stats->rowCount(); $count+= $recordCount; // End of records if ($this->checkEndOfRecords($recordCount, $fileName) === true) { $this->appendRunMessage(PHP_EOL. '# End of records.' . PHP_EOL. '- Truncating and Optimising stat.'); $this->log->debug('End of records in stat table. Truncate and Optimise.'); // Truncate stat table $this->store->update('TRUNCATE TABLE stat', []); // Optimize stat table if ($options['optimiseOnComplete'] == 1) { $this->store->update('OPTIMIZE TABLE stat', []); } $this->appendRunMessage(__('Done.'. PHP_EOL)); break; } // Loops limit end - task will need to rerun again to start from the saved watermark if ($this->checkLoopLimits($numberOfLoops, $options['numberOfLoops'], $fileName, $watermark) === true) { break; } $numberOfLoops++; $statCount = 0; foreach ($stats->fetchAll() as $stat) { // We get the watermark now because if we skip the records our watermark will never reach 0 $watermark = $stat['statId']; // Get display $display = $this->getDisplay((int) $stat['displayId']); if (empty($display)) { $this->log->error('Display not found. Display Id: '. $stat['displayId']); continue; } $entry = []; $entry['statDate'] = $this->date->parse($stat['statDate'], 'U'); $entry['type'] = $stat['type']; $entry['fromDt'] = $this->date->parse($stat['start'], 'U'); $entry['toDt'] = $this->date->parse($stat['end'], 'U'); $entry['scheduleId'] = (int) $stat['scheduleId']; $entry['mediaId'] = (int) $stat['mediaId']; $entry['layoutId'] = (int) $stat['layoutId']; $entry['display'] = $display; $entry['campaignId'] = (int) $stat['campaignId']; $entry['tag'] = $stat['tag']; $entry['widgetId'] = (int) $stat['widgetId']; $entry['duration'] = (int) $stat['duration']; $entry['count'] = (int) $stat['count']; // Add stats in store $this->stats $this->timeSeriesStore->addStat($entry); $statCount++; } // Write stats if ($statCount > 0) { $this->timeSeriesStore->addStatFinalize(); } else { $this->appendRunMessage('No stat to migrate from stat to mongo'); $this->log->debug('No stat to migrate from stat to mongo'); } // Give Mongo time to recover if ($watermark > 0) { $this->appendRunMessage('- '. $count. ' rows migrated.'); $this->log->debug('Mongo stats migration from stat. '.$count.' rows effected, sleeping.'); sleep($options['pauseBetweenLoops']); } } } function migrationStatArchiveToMongo($options) { $this->appendRunMessage(PHP_EOL. '## Moving from stat_archive to Mongo'); $fileName = $this->config->getSetting('LIBRARY_LOCATION') . '.watermark_stat_archive_mongo.txt'; // Get low watermark from file $watermark = $this->getWatermarkFromFile($fileName, 'stat_archive'); $numberOfLoops = 0; while ($watermark > 0) { $count = 0; /** @noinspection SqlResolve */ $stats = $this->store->getConnection()->prepare(' SELECT statId, type, statDate, scheduleId, displayId, layoutId, mediaId, widgetId, start, `end`, tag FROM stat_archive WHERE statId < :watermark ORDER BY statId DESC LIMIT :limit '); $stats->bindParam(':watermark', $watermark, \PDO::PARAM_INT); $stats->bindParam(':limit', $options['numberOfRecords'], \PDO::PARAM_INT); // Run the select $stats->execute(); // Keep count how many stats we've inserted $recordCount = $stats->rowCount(); $count+= $recordCount; // End of records if ($this->checkEndOfRecords($recordCount, $fileName) === true) { $this->appendRunMessage(PHP_EOL. '# End of records.' . PHP_EOL. '- Dropping stat_archive.'); $this->log->debug('End of records in stat_archive (migration to Mongo). Dropping table.'); // Drop the stat_archive table /** @noinspection SqlResolve */ $this->store->update('DROP TABLE `stat_archive`;', []); $this->appendRunMessage(__('Done.'. PHP_EOL)); break; } // Loops limit end - task will need to rerun again to start from the saved watermark if ($this->checkLoopLimits($numberOfLoops, $options['numberOfLoops'], $fileName, $watermark) === true) { break; } $numberOfLoops++; $temp = []; $statIgnoredCount = 0; $statCount = 0; foreach ($stats->fetchAll() as $stat) { // We get the watermark now because if we skip the records our watermark will never reach 0 $watermark = $stat['statId']; // Get display $display = $this->getDisplay((int) $stat['displayId']); if (empty($display)) { $this->log->error('Display not found. Display Id: '. $stat['displayId']); continue; } $entry = []; // Get campaignId if (($stat['type'] != 'event') && ($stat['layoutId'] != null)) { try { // Search the campaignId in the temp array first to reduce query in layouthistory if (array_key_exists($stat['layoutId'], $temp) ) { $campaignId = $temp[$stat['layoutId']]; } else { $campaignId = $this->layoutFactory->getCampaignIdFromLayoutHistory($stat['layoutId']); $temp[$stat['layoutId']] = $campaignId; } } catch (NotFoundException $error) { $statIgnoredCount+= 1; $count = $count - 1; continue; } } else { $campaignId = 0; } $statDate = $this->date->parse($stat['statDate']); $start = $this->date->parse($stat['start']); $end = $this->date->parse($stat['end']); $entry['statDate'] = $statDate; $entry['type'] = $stat['type']; $entry['fromDt'] = $start; $entry['toDt'] = $end; $entry['scheduleId'] = (int) $stat['scheduleId']; $entry['display'] = $display; $entry['campaignId'] = (int) $campaignId; $entry['layoutId'] = (int) $stat['layoutId']; $entry['mediaId'] = (int) $stat['mediaId']; $entry['tag'] = $stat['tag']; $entry['widgetId'] = (int) $stat['widgetId']; $entry['duration'] = $end->diffInSeconds($start); $entry['count'] = isset($stat['count']) ? (int) $stat['count'] : 1; // Add stats in store $this->stats $this->timeSeriesStore->addStat($entry); $statCount++; } if ($statIgnoredCount > 0) { $this->appendRunMessage($statIgnoredCount. ' stat(s) were ignored while migrating'); } // Write stats if ($statCount > 0) { $this->timeSeriesStore->addStatFinalize(); } else { $this->appendRunMessage('No stat to migrate from stat archive to mongo'); $this->log->debug('No stat to migrate from stat archive to mongo'); } // Give Mongo time to recover if ($watermark > 0) { if($statCount > 0 ) { $this->appendRunMessage('- '. $count. ' rows migrated.'); $this->log->debug('Mongo stats migration from stat_archive. '.$count.' rows effected, sleeping.'); } sleep($options['pauseBetweenLoops']); } } } // Get low watermark from file function getWatermarkFromFile($fileName, $tableName) { if (file_exists($fileName)) { $file = fopen($fileName, 'r'); $line = fgets($file); fclose($file); $watermark = (int) $line; } else { // Save mysql low watermark in file if .watermark.txt file is not found /** @noinspection SqlResolve */ $statId = $this->store->select('SELECT MAX(statId) as statId FROM '.$tableName, []); $watermark = (int) $statId[0]['statId']; $out = fopen($fileName, 'w'); fwrite($out, $watermark); fclose($out); } // We need to increase it $watermark+= 1; $this->appendRunMessage('- Initial watermark is '.$watermark); return $watermark; } // Check if end of records function checkEndOfRecords($recordCount, $fileName) { if($recordCount == 0) { // No records in stat, save watermark in file $watermark = -1; $out = fopen($fileName, 'w'); fwrite($out, $watermark); fclose($out); return true; } return false; } // Check loop limits function checkLoopLimits($numberOfLoops, $optionsNumberOfLoops, $fileName, $watermark) { if($numberOfLoops == $optionsNumberOfLoops) { // Save watermark in file $watermark = $watermark - 1; $this->log->debug(' Loop reached limit. Watermark is now '.$watermark); $out = fopen($fileName, 'w'); fwrite($out, $watermark); fclose($out); return true; } return false; } // Disable the task function disableTask() { $this->appendRunMessage('# Disabling task.'); $this->log->debug('Disabling task.'); $this->getTask()->isActive = 0; $this->getTask()->save(); $this->appendRunMessage(__('Done.'. PHP_EOL)); return; } // Disable the task function quitMigrationTaskOrDisableStatArchiveTask() { // Quit the migration task if stat archive task is running if ($this->archiveTask->status == Task::$STATUS_RUNNING) { $this->appendRunMessage('Quitting the stat migration task as stat archive task is running'); $this->log->debug('Quitting the stat migration task as stat archive task is running.'); return; } // Mark the Stats Archiver as disabled if it is active if ($this->archiveTask->isActive == 1) { $this->archiveTask->isActive = 0; $this->archiveTask->save(); $this->store->commitIfNecessary(); $this->appendRunMessage('Disabling Stats Archive Task.'); $this->log->debug('Disabling Stats Archive Task.'); } return; } // Cahce/Get display function getDisplay($displayId) { // Get display if in memory if (array_key_exists($displayId, $this->displays)) { $display = $this->displays[$displayId]; } else if (array_key_exists($displayId, $this->displaysNotFound)) { // Display not found return false; } else { try { $display = $this->displayFactory->getById($displayId); // Cache display $this->displays[$displayId] = $display; } catch (NotFoundException $error) { // Cache display not found $this->displaysNotFound[$displayId] = $displayId; return false; } } return $display; } }PK 7qY{ f f WidgetSyncTask.phpnu [ . */ namespace Xibo\XTR; use Xibo\Entity\Region; use Xibo\Exception\XiboException; use Xibo\Factory\LayoutFactory; use Xibo\Factory\ModuleFactory; /** * Class WidgetSyncTask * @package Xibo\XTR */ class WidgetSyncTask implements TaskInterface { use TaskTrait; /** @var ModuleFactory */ private $moduleFactory; /** @var LayoutFactory */ private $layoutFactory; /** @inheritdoc */ public function setFactories($container) { $this->moduleFactory = $container->get('moduleFactory'); $this->layoutFactory = $container->get('layoutFactory'); return $this; } /** @inheritdoc */ public function run() { // Get an array of modules to use $modules = $this->moduleFactory->get(); $currentLayoutId = 0; $layout = null; $countWidgets = 0; $countLayouts = 0; $widgetsDone = []; $sql = ' SELECT requiredfile.itemId, requiredfile.displayId FROM `requiredfile` INNER JOIN `layout` ON layout.layoutId = requiredfile.itemId INNER JOIN `display` ON display.displayId = requiredfile.displayId WHERE requiredfile.type = \'L\' AND display.loggedIn = 1 ORDER BY itemId, displayId '; $smt = $this->store->getConnection()->prepare($sql); $smt->execute(); // Track the total time we've spent caching (excluding all other operations, etc) $timeCaching = 0.0; // Get a list of Layouts which are currently active, along with the display they are active on // get the widgets from each layout and call get resource on them while ($row = $smt->fetch(\PDO::FETCH_ASSOC)) { try { // We have a Layout $layoutId = (int)$row['itemId']; $displayId = (int)$row['displayId']; $this->log->debug('Found layout to keep in sync ' . $layoutId); if ($layoutId !== $currentLayoutId) { $countLayouts++; // Add a little break in here if ($currentLayoutId !== 0) { usleep(10000); } // We've changed layout // load in the new one $layout = $this->layoutFactory->getById($layoutId); $layout->load(); // Update pointer $currentLayoutId = $layoutId; // Clear out the list of widgets we've done $widgetsDone = []; } // Load the layout XML and work out if we have any ticker / text / dataset media items foreach ($layout->regions as $region) { /* @var Region $region */ $playlist = $region->getPlaylist(); $playlist->setModuleFactory($this->moduleFactory); foreach ($playlist->expandWidgets() as $widget) { // See if we have a cache if ($widget->type == 'ticker' || $widget->type == 'text' || $widget->type == 'datasetview' || $widget->type == 'webpage' || $widget->type == 'embedded' || $modules[$widget->type]->renderAs == 'html' ) { $countWidgets++; // Make me a module from the widget $module = $this->moduleFactory->createWithWidget($widget, $region); // Have we done this widget before? if (in_array($widget->widgetId, $widgetsDone) && !$module->isCacheDisplaySpecific()) { $this->log->debug('This widgetId ' . $widget->widgetId . ' has been done before and is not display specific, so we skip'); continue; } // Record start time $startTime = microtime(true); // Cache the widget $module->getResourceOrCache($displayId); // Record we have done this widget $widgetsDone[] = $widget->widgetId; // Record end time and aggregate for final total $duration = (microtime(true) - $startTime); $timeCaching = $timeCaching + $duration; $this->log->debug('Took ' . $duration . ' seconds to check and/or cache widgetId ' . $widget->widgetId . ' for displayId ' . $displayId); // Commit so that any images we've downloaded have their cache times updated for the next request // this makes sense because we've got a file cache that is already written out. $this->store->commitIfNecessary(); } } } } catch (XiboException $xiboException) { // Log and skip to the next layout $this->log->debug($xiboException->getTraceAsString()); $this->log->error('Cannot process layoutId ' . $layoutId . ', E = ' . $xiboException->getMessage()); } } $this->log->info('Total time spent caching is ' . $timeCaching); $this->appendRunMessage('Synced ' . $countWidgets . ' widgets across ' . $countLayouts . ' layouts.'); } }PK 7qYɜn TaskTrait.phpnu [ config = $config; return $this; } /** @inheritdoc */ public function setLogger($logger) { $this->log = $logger; return $this; } /** @inheritdoc */ public function setSanitizer($sanitizer) { $this->sanitizer = $sanitizer; return $this; } /** @inheritdoc */ public function setDate($date) { $this->date = $date; return $this; } /** @inheritdoc */ public function setTask($task) { $options = $task->options; if (property_exists($this, 'defaultConfig')) $options = array_merge($this->defaultConfig, $options); $this->task = $task; $this->options = $options; return $this; } /** @inheritdoc */ public function setStore($store) { $this->store = $store; return $this; } /** @inheritdoc */ public function setTimeSeriesStore($timeSeriesStore) { $this->timeSeriesStore = $timeSeriesStore; return $this; } /** @inheritdoc */ public function setPool($pool) { $this->pool = $pool; return $this; } /** @inheritdoc */ public function setUser($user) { $this->user = $user; return $this; } /** @inheritdoc */ public function getRunMessage() { return $this->runMessage; } /** * Get task * @return Task */ private function getTask() { return $this->task; } /** * @param $option * @param $default * @return mixed */ private function getOption($option, $default) { return isset($this->options[$option]) ? $this->options[$option] : $default; } /** * Append Run Message * @param $message */ private function appendRunMessage($message) { if ($this->runMessage === null) $this->runMessage = ''; $this->runMessage .= $message . PHP_EOL; } }PK 7qYD--12 12 ReportScheduleTask.phpnu [ . */ namespace Xibo\XTR; use Xibo\Controller\Library; use Xibo\Factory\MediaFactory; use Xibo\Factory\NotificationFactory; use Xibo\Factory\ReportScheduleFactory; use Xibo\Factory\SavedReportFactory; use Xibo\Factory\UserFactory; use Xibo\Factory\UserGroupFactory; use Xibo\Service\DateServiceInterface; use Xibo\Service\ReportServiceInterface; use Slim\View; use Slim\Slim; /** * Class ReportScheduleTask * @package Xibo\XTR */ class ReportScheduleTask implements TaskInterface { use TaskTrait; /** @var View */ private $view; /** @var DateServiceInterface */ private $date; /** @var MediaFactory */ private $mediaFactory; /** @var SavedReportFactory */ private $savedReportFactory; /** @var UserGroupFactory */ private $userGroupFactory; /** @var UserFactory */ private $userFactory; /** @var ReportScheduleFactory */ private $reportScheduleFactory; /** @var ReportServiceInterface */ private $reportService; /** @var NotificationFactory */ private $notificationFactory; /** @inheritdoc */ public function setFactories($container) { $this->view = $container->get('view'); $this->date = $container->get('dateService'); $this->userFactory = $container->get('userFactory'); $this->mediaFactory = $container->get('mediaFactory'); $this->savedReportFactory = $container->get('savedReportFactory'); $this->userGroupFactory = $container->get('userGroupFactory'); $this->reportScheduleFactory = $container->get('reportScheduleFactory'); $this->reportService = $container->get('reportService'); $this->notificationFactory = $container->get('notificationFactory'); return $this; } /** @inheritdoc */ public function run() { $this->runMessage = '# ' . __('Report schedule') . PHP_EOL . PHP_EOL; // Long running task set_time_limit(0); $this->runReportSchedule(); } /** * Run report schedule */ private function runReportSchedule() { // Make sure the library exists Library::ensureLibraryExists($this->config->getSetting('LIBRARY_LOCATION')); $reportSchedules = $this->reportScheduleFactory->query(null, ['isActive' => 1]); // Get list of ReportSchedule foreach($reportSchedules as $reportSchedule) { $cron = \Cron\CronExpression::factory($reportSchedule->schedule); $nextRunDt = $cron->getNextRunDate(\DateTime::createFromFormat('U', $reportSchedule->lastRunDt))->format('U'); $now = time(); if ($nextRunDt <= $now) { // random run of report schedules $skip = $this->skipReportRun($now, $nextRunDt); if ($skip == true) { continue; } // execute the report $rs = $this->reportScheduleFactory->getById($reportSchedule->reportScheduleId); $rs->previousRunDt = $rs->lastRunDt; $rs->lastRunDt = time(); $this->log->debug('Last run date is updated to '. $rs->lastRunDt); try { // Get the generated saved as report name $saveAs = $this->reportService->generateSavedReportName($reportSchedule->reportName, $reportSchedule->filterCriteria); // Run the report to get results $result = $this->reportService->runReport($reportSchedule->reportName, $reportSchedule->filterCriteria, $reportSchedule->userId); $this->log->debug(__('Run report results: %s.', json_encode($result, JSON_PRETTY_PRINT))); // Save the result in a json file $fileName = tempnam($this->config->getSetting('LIBRARY_LOCATION') . '/temp/','reportschedule'); $out = fopen($fileName, 'w'); fwrite($out, json_encode($result)); fclose($out); // Create a ZIP file and add our temporary file $zipName = $this->config->getSetting('LIBRARY_LOCATION') . 'temp/reportschedule.json.zip'; $zip = new \ZipArchive(); $result = $zip->open($zipName, \ZipArchive::CREATE | \ZipArchive::OVERWRITE); if ($result !== true) { throw new \InvalidArgumentException(__('Can\'t create ZIP. Error Code: %s', $result)); } $zip->addFile($fileName, 'reportschedule.json'); $zip->close(); // Remove the JSON file unlink($fileName); $runDateTimestamp = $this->date->parse()->format('U'); // Upload to the library $media = $this->mediaFactory->create(__('reportschedule_' . $reportSchedule->reportScheduleId . '_' . $runDateTimestamp ), 'reportschedule.json.zip', 'savedreport', $reportSchedule->userId); $media->save(); // Save Saved report $savedReport = $this->savedReportFactory->create($saveAs, $reportSchedule->reportScheduleId, $media->mediaId, time(), $reportSchedule->userId); $savedReport->save(); $this->createPdfAndNotification($reportSchedule, $savedReport, $media); // Add the last savedreport in Report Schedule $this->log->debug('Last savedReportId in Report Schedule: '. $savedReport->savedReportId); $rs->lastSavedReportId = $savedReport->savedReportId; $rs->message = null; } catch (\Exception $error) { $rs->isActive = 0; $rs->message = $error->getMessage(); $this->log->error('Error: ' . $error->getMessage()); } // Finally save schedule report $rs->save(); } } } /** * Create the PDF and save a notification * @param $reportSchedule * @param $savedReport * @param $media */ private function createPdfAndNotification($reportSchedule, $savedReport, $media) { $savedReportData = $this->reportService->getSavedReportResults($savedReport->savedReportId, $reportSchedule->reportName); // Get the report config $report = $this->reportService->getReportByName($reportSchedule->reportName); if ($report->output_type == 'chart') { $quickChartUrl = $this->config->getSetting('QUICK_CHART_URL'); if (!empty($quickChartUrl)) { $script = $this->reportService->getReportChartScript($savedReport->savedReportId, $reportSchedule->reportName); $src = $quickChartUrl. "/chart?width=1000&height=300&c=".$script; } else { $placeholder = __('Chart could not be drawn because the CMS has not been configured with a Quick Chart URL.'); } } else { // only for tablebased report $result = $savedReportData['chartData']['result']; $tableData =json_decode($result, true); } // Get report email template $emailTemplate = $this->reportService->getReportEmailTemplate($reportSchedule->reportName); if(!empty($emailTemplate)) { // Save PDF attachment ob_start(); $this->view->display($emailTemplate, [ 'header' => $report->description, 'logo' => $this->config->uri('img/xibologo.png', true), 'title' => $savedReport->saveAs, 'periodStart' => $savedReportData['chartData']['periodStart'], 'periodEnd' => $savedReportData['chartData']['periodEnd'], 'generatedOn' => $this->date->parse($savedReport->generatedOn, 'U')->format('Y-m-d H:i:s'), 'tableData' => isset($tableData) ? $tableData : null, 'src' => isset($src) ? $src : null, 'placeholder' => isset($placeholder) ? $placeholder : null ]); $body = ob_get_contents(); ob_end_clean(); try { $mpdf = new \Mpdf\Mpdf([ 'tempDir' => $this->config->getSetting('LIBRARY_LOCATION') . '/temp', 'orientation' => 'L', 'mode' => 'c', 'margin_left' => 20, 'margin_right' => 20, 'margin_top' => 20, 'margin_bottom' => 20, 'margin_header' => 5, 'margin_footer' => 15 ]); $mpdf->setFooter('Page {PAGENO}') ; $mpdf->SetDisplayMode('fullpage'); $stylesheet = file_get_contents($this->config->uri('css/email-report.css', true)); $mpdf->WriteHTML($stylesheet, 1); $mpdf->WriteHTML($body); $mpdf->Output($this->config->getSetting('LIBRARY_LOCATION'). 'attachment/filename-'.$media->mediaId.'.pdf', \Mpdf\Output\Destination::FILE); // Create email notification with attachment $filters = json_decode($reportSchedule->filterCriteria, true); $sendEmail = isset($filters['sendEmail']) ? $filters['sendEmail'] : null; $nonusers = isset($filters['nonusers']) ? $filters['nonusers'] : null; if ($sendEmail) { $notification = $this->notificationFactory->createEmpty(); $notification->subject = $report->description; $notification->body = __('Attached please find the report for %s', $savedReport->saveAs); $notification->createdDt = $this->date->getLocalDate(null, 'U'); $notification->releaseDt = time(); $notification->isEmail = 1; $notification->isInterrupt = 0; $notification->userId = $savedReport->userId; // event owner $notification->filename = 'filename-'.$media->mediaId.'.pdf'; $notification->originalFileName = 'saved_report.pdf'; $notification->nonusers = $nonusers; // Get user group to create user notification $notificationUser = $this->userFactory->getById($savedReport->userId); $notification->assignUserGroup($this->userGroupFactory->getById($notificationUser->groupId)); $notification->save(); } } catch (\Exception $error) { $this->log->error($error->getMessage()); $this->runMessage .= $error->getMessage() . PHP_EOL . PHP_EOL; } } } private function skipReportRun($now, $nextRunDt) { $fourHoursInSeconds = 4 * 3600; $threeHoursInSeconds = 3 * 3600; $twoHoursInSeconds = 2 * 3600; $oneHourInSeconds = 1 * 3600; $diffFromNow = $now - $nextRunDt; $range = 100; $random = rand(1, $range); if ($diffFromNow < $oneHourInSeconds) { // don't run the report if ($random <= 70 ) { // 70% chance of skipping return true; } } elseif ($diffFromNow < $twoHoursInSeconds) { // don't run the report if ($random <= 50 ) { // 50% chance of skipping return true; } } elseif ($diffFromNow < $threeHoursInSeconds) { // don't run the report if ($random <= 40 ) { // 40% chance of skipping return true; } } elseif ($diffFromNow < $fourHoursInSeconds) { // don't run the report if ($random <= 25 ) { // 25% chance of skipping return true; } } return false; } }PK 7qY* NotificationTidyTask.phpnu [ getOption('maxAgeDays', 7)); $systemOnly = intval($this->getOption('systemOnly', 1)); $readOnly = intval($this->getOption('readOnly', 0)); $this->runMessage = '# ' . __('Notification Tidy') . PHP_EOL . PHP_EOL; $this->log->info('Deleting notifications older than ' . $maxAgeDays . ' days. System Only: ' . $systemOnly . '. Read Only' . $readOnly ); // Where clause $where = ' WHERE `releaseDt` < :releaseDt '; if ($systemOnly == 1) { $where .= ' AND `isSystem` = 1 '; } // Params for all deletes $params = [ 'releaseDt' => $this->date->parse()->subDays($maxAgeDays)->format('U') ]; // Delete all notifications older than now minus X days $sql = ' DELETE FROM `lknotificationdg` WHERE `notificationId` IN (SELECT DISTINCT `notificationId` FROM `notification` ' . $where . ') '; if ($readOnly == 1) { $sql .= ' AND `notificationId` IN (SELECT `notificationId` FROM `lknotificationuser` WHERE read <> 0) '; } $this->store->update($sql, $params); // Delete notification groups $sql = ' DELETE FROM `lknotificationgroup` WHERE `notificationId` IN (SELECT DISTINCT `notificationId` FROM `notification` ' . $where . ') '; if ($readOnly == 1) { $sql .= ' AND `notificationId` IN (SELECT `notificationId` FROM `lknotificationuser` WHERE read <> 0) '; } $this->store->update($sql, $params); // Delete from notification user $sql = ' DELETE FROM `lknotificationuser` WHERE `notificationId` IN (SELECT DISTINCT `notificationId` FROM `notification` ' . $where . ') '; if ($readOnly == 1) { $sql .= ' AND `read` <> 0 '; } $this->store->update($sql, $params); // Remove the attached file $sql = 'SELECT filename FROM `notification` ' . $where; foreach ($this->store->select($sql, $params) as $row) { $filename = $row['filename']; /*Delete the attachment*/ if (!empty($filename)) { // Library location $attachmentLocation = $this->config->getSetting('LIBRARY_LOCATION'). 'attachment/'; if (file_exists($attachmentLocation . $filename)) { unlink($attachmentLocation . $filename); } } } // Delete from notification $sql = 'DELETE FROM `notification` ' . $where; if ($readOnly == 1) { $sql .= ' AND `notificationId` NOT IN (SELECT `notificationId` FROM `lknotificationuser`) '; } $this->store->update($sql, $params); $this->runMessage .= __('Done') . PHP_EOL . PHP_EOL; } }PK 7qY DropPlayerCacheTask.phpnu [ pool->deleteItem('display'); } }PK 7qYX+ EmailNotificationsTask.phpnu [ view = $container->get('view'); $this->userNotificationFactory = $container->get('userNotificationFactory'); return $this; } /** @inheritdoc */ public function run() { $this->runMessage = '# ' . __('Email Notifications') . PHP_EOL . PHP_EOL; $this->processQueue(); } /** Process Queue of emails */ private function processQueue() { // Handle queue of notifications to email. $this->runMessage .= '## ' . __('Email Notifications') . PHP_EOL; $msgFrom = $this->config->getSetting('mail_from'); $msgFromName = $this->config->getSetting('mail_from_name'); $this->log->debug('Notification Queue sending from ' . $msgFrom); foreach ($this->userNotificationFactory->getEmailQueue() as $notification) { /** @var UserNotification $notification */ $this->log->debug('Notification found: ' . $notification->notificationId); // System notification for the system user if ($notification->isSystem == 1 && $notification->userId == 0) $notification->email = $this->user->email; if ($notification->email != '') { $this->log->debug('Sending Notification email to ' . $notification->email); // Send them an email $mail = new \PHPMailer\PHPMailer\PHPMailer(); $mail->CharSet = 'UTF-8'; $mail->Encoding = 'base64'; $mail->From = $msgFrom; // Add attachment if ($notification->filename != null) { $mail->addAttachment($this->config->getSetting('LIBRARY_LOCATION'). 'attachment/' . $notification->filename, $notification->originalFileName); } if ($msgFromName != null) $mail->FromName = $msgFromName; $mail->Subject = $notification->subject; $mail->addAddress($notification->email); $addresses = explode(',', $notification->nonusers); foreach ($addresses as $address) { $mail->AddAddress($address); } // Body $mail->isHTML(true); $mail->AltBody = $notification->body; $mail->Body = $this->generateEmailBody($notification->subject, $notification->body); if (!$mail->send()) { $this->log->error('Unable to send email notification mail to ' . $notification->email); $this->runMessage .= ' - E' . PHP_EOL; } else { $this->runMessage .= ' - A' . PHP_EOL; } $this->log->debug('Marking notification as sent'); } else { $this->log->error('Discarding NotificationId ' . $notification->notificationId . ' as no email address could be resolved.'); } // Mark as sent $notification->setEmailed($this->date->getLocalDate(null, 'U')); $notification->save(); } $this->runMessage .= ' - Done' . PHP_EOL; } /** * Generate an email body * @param $subject * @param $body * @return string */ private function generateEmailBody($subject, $body) { // Generate Body // Start an object buffer ob_start(); // Render the template $this->view->display('email-template.twig', ['config' => $this->config, 'subject' => $subject, 'body' => $body]); $body = ob_get_contents(); ob_end_clean(); return $body; } }PK 7qYnl- - DynamicPlaylistSyncTask.phpnu [ . */ namespace Xibo\XTR; use Xibo\Entity\Media; use Xibo\Entity\Playlist; use Xibo\Entity\Task; use Xibo\Exception\NotFoundException; use Xibo\Exception\XiboException; use Xibo\Factory\MediaFactory; use Xibo\Factory\ModuleFactory; use Xibo\Factory\PlaylistFactory; use Xibo\Factory\WidgetFactory; use Xibo\Service\DateServiceInterface; use Xibo\Storage\StorageServiceInterface; /** * Class DynamicPlaylistSyncTask * @package Xibo\XTR * * Keep dynamic Playlists in sync with changes to the Media table. */ class DynamicPlaylistSyncTask implements TaskInterface { use TaskTrait; /** @var StorageServiceInterface */ private $store; /** @var DateServiceInterface */ private $date; /** @var PlaylistFactory */ private $playlistFactory; /** @var MediaFactory */ private $mediaFactory; /** @var ModuleFactory */ private $moduleFactory; /** @var WidgetFactory */ private $widgetFactory; /** @inheritdoc */ public function setFactories($container) { $this->store = $container->get('store'); $this->date = $container->get('dateService'); $this->playlistFactory = $container->get('playlistFactory'); $this->mediaFactory = $container->get('mediaFactory'); $this->moduleFactory = $container->get('moduleFactory'); $this->widgetFactory = $container->get('widgetFactory'); return $this; } /** @inheritdoc */ public function run() { // If we're in the error state, then always run, otherwise check the dates we modified various triggers if ($this->getTask()->lastRunStatus !== Task::$STATUS_ERROR) { // Run a little query to get the last modified date from the media table $lastMediaUpdate = $this->store->select('SELECT MAX(modifiedDt) AS modifiedDt FROM `media` WHERE `type` <> \'module\' AND `type` <> \'genericfile\' AND `type` <> \'playersoftware\' AND `type` <> \'font\';', [])[0]['modifiedDt']; $lastPlaylistUpdate = $this->store->select('SELECT MAX(modifiedDt) AS modifiedDt FROM `playlist`;', [])[0]['modifiedDt']; if (empty($lastMediaUpdate) && empty($lastPlaylistUpdate)) { $this->appendRunMessage('No library media or Playlists to assess'); return; } $this->log->debug('Last media updated date is ' . $lastMediaUpdate); $this->log->debug('Last playlist updated date is ' . $lastPlaylistUpdate); $lastMediaUpdate = $this->date->parse($lastMediaUpdate); $lastPlaylistUpdate = $this->date->parse($lastPlaylistUpdate); $lastTaskRun = $this->date->parse($this->getTask()->lastRunDt, 'U'); if ($lastMediaUpdate->lessThanOrEqualTo($lastTaskRun) && $lastPlaylistUpdate->lessThanOrEqualTo($lastTaskRun)) { $this->appendRunMessage('No library media/playlist updates since we last ran'); return; } } $count = 0; // Get all Dynamic Playlists foreach ($this->playlistFactory->query(null, ['isDynamic' => 1]) as $playlist) { try { // We want to detect any differences in what should be assigned to this Playlist. $playlist->load(); $this->log->debug('Assessing Playlist: ' . $playlist->name); // Query for media which would be assigned to this Playlist and see if there are any differences $media = []; $mediaIds = []; foreach ($this->mediaFactory->query(null, [ 'name' => $playlist->filterMediaName, 'tags' => $playlist->filterMediaTags ]) as $item) { $media[$item->mediaId] = $item; $mediaIds[] = $item->mediaId; } // Work out if the set of widgets is different or not. // This is only the first loose check $different = (count($playlist->widgets) !== count($media)); $this->log->debug('There are ' . count($media) . ' that should be assigned and ' . count($playlist->widgets) . ' currently assigned. First check difference is ' . var_export($different, true)); if (!$different) { // Try a more complete check, using mediaIds $compareMediaIds = $mediaIds; // ordering should be the same, so the first time we get one out of order, we can stop foreach ($playlist->widgets as $widget) { try { $widgetMediaId = $widget->getPrimaryMediaId(); if ($widgetMediaId !== $compareMediaIds[0] || $widget->duration !== $media[$widgetMediaId]->duration) { $different = true; break; } } catch (NotFoundException $notFoundException) { $this->log->error('Playlist ' . $playlist->getId() . ' has a Widget without any associated media. widgetId = ' . $widget->getId()); // We ought to recalculate $different = true; break; } array_shift($compareMediaIds); } } $this->log->debug('Second check difference is ' . var_export($different, true)); if ($different) { // We will update this Playlist $assignmentMade = false; $count++; // Remove the ones no-longer present, add the ones we're missing // we don't delete and re-add the lot to avoid regenerating the widgetIds (makes stats harder to // interpret) foreach ($playlist->widgets as $widget) { try { $widgetMediaId = $widget->getPrimaryMediaId(); if (!in_array($widgetMediaId, $mediaIds)) { $playlist->deleteWidget($widget); } else { // It's present in the array // Check to see if the duration is different if ($widget->duration !== $media[$widgetMediaId]->duration) { // The media duration has changed, so update the widget $widget->useDuration = 1; $widget->duration = $media[$widgetMediaId]->duration; $widget->calculatedDuration = $widget->duration; $widget->save([ 'saveWidgetOptions' => false, 'saveWidgetAudio' => false, 'saveWidgetMedia' => false, 'notify' => false, 'notifyPlaylists' => false, 'notifyDisplays' => false, 'audit' => true, 'alwaysUpdate' => true ]); } // Pop it off the list of ones to assign. $mediaIds = array_diff($mediaIds, [$widgetMediaId]); // We do want to save the Playlist here. $assignmentMade = true; } } catch (NotFoundException $exception) { // Delete it $playlist->deleteWidget($widget); } } // Do we have any mediaId's left which should be assigned and aren't? // Add the ones we have left foreach ($media as $item) { if (in_array($item->mediaId, $mediaIds)) { $assignmentMade = true; $this->createAndAssign($playlist, $item, $count); } } if ($assignmentMade) { // We've made an assignment change, so audit this change // don't audit any downstream save operations $playlist->save([ 'auditPlaylist' => true, 'audit' => false ]); } } else { $this->log->debug('No differences detected'); } } catch (XiboException $exception) { $this->log->debug($exception->getTraceAsString()); $this->log->error('Problem with PlaylistId: ' . $playlist->getId() . ', e = ' . $exception->getMessage()); $this->appendRunMessage('Error with Playlist: ' . $playlist->name); } } $this->appendRunMessage('Updated ' . $count . ' Playlists'); } /** * @param Playlist $playlist * @param Media $media * @param int $displayOrder * @throws NotFoundException */ private function createAndAssign($playlist, $media, $displayOrder) { $this->log->debug('Media Item needs to be assigned ' . $media->name . ' in sequence ' . $displayOrder); // Create a module $module = $this->moduleFactory->create($media->mediaType); if ($module->getModule()->assignable == 1) { // Determine the duration $mediaDuration = ($media->duration == 0) ? $module->determineDuration() : $media->duration; // Create a widget $widget = $this->widgetFactory->create( $playlist->getOwnerId(), $playlist->playlistId, $media->mediaType, $mediaDuration ); $widget->assignMedia($media->mediaId); $widget->displayOrder = $displayOrder; // Assign the widget to the module $module->setWidget($widget); // Set default options (this sets options on the widget) $module->setDefaultWidgetOptions(); // Set the duration to be equal to the Library duration of this media $module->widget->useDuration = 1; $module->widget->duration = $mediaDuration; $module->widget->calculatedDuration = $mediaDuration; // Assign the widget to the playlist $playlist->assignWidget($widget); } } }PK 7qYi"ſ TaskInterface.phpnu [