. */ namespace Xibo\XTR; use Xibo\Support\Exception\GeneralException; use Xibo\Support\Exception\NotFoundException; /** * Class WidgetCompatibilityTask * Run only once when upgrading widget from v3 to v4 * @package Xibo\XTR */ class WidgetCompatibilityTask implements TaskInterface { use TaskTrait; /** @var \Xibo\Factory\ModuleFactory */ private $moduleFactory; /** @var \Xibo\Factory\WidgetFactory */ private $widgetFactory; /** @var \Xibo\Factory\LayoutFactory */ private $layoutFactory; /** @var \Xibo\Factory\playlistFactory */ private $playlistFactory; /** @var \Xibo\Factory\TaskFactory */ private $taskFactory; /** @var \Xibo\Factory\RegionFactory */ private $regionFactory; /** @var array The cache for layout */ private $layoutCache = []; /** @inheritdoc */ public function setFactories($container) { $this->moduleFactory = $container->get('moduleFactory'); $this->widgetFactory = $container->get('widgetFactory'); $this->layoutFactory = $container->get('layoutFactory'); $this->playlistFactory = $container->get('playlistFactory'); $this->taskFactory = $container->get('taskFactory'); $this->regionFactory = $container->get('regionFactory'); return $this; } /** @inheritdoc */ public function run() { // This task should only be run once when upgrading for the first time from v3 to v4. // If the Widget Compatibility class is defined, it needs to be executed to upgrade the widgets. $this->runMessage = '# ' . __('Widget Compatibility') . PHP_EOL . PHP_EOL; // Get all modules $modules = $this->moduleFactory->getAll(); // For each module we should get all widgets which are < the schema version of the module installed, and // upgrade them to the schema version of the module installed foreach ($modules as $module) { // Run upgrade - Part 1 // Upgrade a widget having the same module type $this->getLogger()->debug('run: finding widgets for ' . $module->type . ' with schema version less than ' . $module->schemaVersion); $statement = $this->executeStatement($module->type, $module->schemaVersion); $this->upgradeWidget($statement); // Run upgrade - Part 2 // Upgrade a widget having the old style module type/legacy type $legacyTypes = []; if (count($module->legacyTypes) > 0) { // Get the name of the module legacy types $legacyTypes = array_column($module->legacyTypes, 'name'); // TODO Make this efficient } // Get module legacy type and update matched widgets foreach ($legacyTypes as $legacyType) { $statement = $this->executeStatement($legacyType, $module->schemaVersion); $this->upgradeWidget($statement); } } // Get Widget Compatibility Task $compatibilityTask = $this->taskFactory->getByClass('\Xibo\XTR\\WidgetCompatibilityTask'); // Mark the task as disabled if it is active if ($compatibilityTask->isActive == 1) { $compatibilityTask->isActive = 0; $compatibilityTask->save(); $this->store->commitIfNecessary(); $this->appendRunMessage('Disabling widget compatibility task.'); $this->log->debug('Disabling widget compatibility task.'); } $this->appendRunMessage(__('Done.'. PHP_EOL)); } /** * * @param string $type * @param int $version * @return false|\PDOStatement */ private function executeStatement(string $type, int $version): bool|\PDOStatement { $sql = ' SELECT widget.widgetId FROM `widget` WHERE `widget`.`type` = :type and `widget`.schemaVersion < :version '; $connection = $this->store->getConnection(); $connection->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); // Prepare the statement $statement = $connection->prepare($sql); // Execute $statement->execute([ 'type' => $type, 'version' => $version ]); return $statement; } private function upgradeWidget(\PDOStatement $statement): void { // Load each widget and its options // Then run upgrade while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) { try { $widget = $this->widgetFactory->getById((int) $row['widgetId']); $widget->loadMinimum(); $this->log->debug('WidgetCompatibilityTask: Get Widget: ' . $row['widgetId']); // Form conditions from the widget's option and value, e.g, templateId==worldclock1 $widgetConditionMatch = []; foreach ($widget->widgetOptions as $option) { $widgetConditionMatch[] = $option->option . '==' . $option->value; } // Get module try { $module = $this->moduleFactory->getByType($widget->type, $widgetConditionMatch); } catch (NotFoundException $e) { $this->log->error('Module not found for widget: ' . $widget->type); $this->appendRunMessage('Module not found for widget: '. $widget->widgetId); continue; } // Run upgrade if ($module->isWidgetCompatibilityAvailable()) { // Grab a widget compatibility interface, if there is one $widgetCompatibilityInterface = $module->getWidgetCompatibilityOrNull(); if ($widgetCompatibilityInterface !== null) { try { // Pass the widget through the compatibility interface. $upgraded = $widgetCompatibilityInterface->upgradeWidget( $widget, $widget->schemaVersion, $module->schemaVersion ); // Save widget version if ($upgraded) { $widget->schemaVersion = $module->schemaVersion; // Assert the module type, unless the widget has already changed it. if (!$widget->hasPropertyChanged('type')) { $widget->type = $module->type; } $widget->save(['alwaysUpdate' => true, 'upgrade' => true]); $this->log->debug('WidgetCompatibilityTask: Upgraded'); } } catch (\Exception $e) { $this->log->error('Failed to upgrade for widgetId: ' . $widget->widgetId . ', message: ' . $e->getMessage()); $this->appendRunMessage('Failed to upgrade for widgetId: : '. $widget->widgetId); } } try { // Get the layout of the widget and set it to rebuild. $playlist = $this->playlistFactory->getById($widget->playlistId); // check if the Widget was assigned to a region playlist if ($playlist->isRegionPlaylist()) { $playlist->load(); $region = $this->regionFactory->getById($playlist->regionId); // set the region type accordingly if ($region->isDrawer === 1) { $regionType = 'drawer'; } else if (count($playlist->widgets) === 1 && $widget->type !== 'subplaylist') { $regionType = 'frame'; } else if (count($playlist->widgets) === 0) { $regionType = 'zone'; } else { $regionType = 'playlist'; } $region->type = $regionType; $region->save(['notify' => false]); } $playlist->notifyLayouts(); } catch (\Exception $e) { $this->log->error('Failed to set layout rebuild for widgetId: ' . $widget->widgetId . ', message: ' . $e->getMessage()); $this->appendRunMessage('Layout rebuild error for widgetId: : '. $widget->widgetId); } } else { $this->getLogger()->debug('upgradeWidget: no compatibility task available for ' . $widget->type); } } catch (GeneralException $e) { $this->log->debug($e->getTraceAsString()); $this->log->error('WidgetCompatibilityTask: Cannot process widget'); } } $this->store->commitIfNecessary(); } }