diff --git a/lib/command.php b/lib/command.php index 9df0f4c..053af72 100644 --- a/lib/command.php +++ b/lib/command.php @@ -1,6 +1,8 @@ setName('developer:sync') ->setDescription('Synchronizes the developer files') + ->addOption('force-db', null, InputOption::VALUE_NONE, 'Force the current status in db, files will be overridden') + ->addOption('force-files', null, InputOption::VALUE_NONE, 'Force the current status in file system, db data will be overridden') ; } @@ -19,7 +23,21 @@ protected function execute(InputInterface $input, OutputInterface $output) $io->title('Developer Sync'); - rex_developer_manager::start(); + $forceDb = $input->getOption('force-db'); + $forceFiles = $input->getOption('force-files'); + + if ($forceDb && $forceFiles) { + throw new InvalidArgumentException('Options --force-db and --force-files can not be used at once.'); + } + + $force = false; + if ($forceDb) { + $force = rex_developer_synchronizer::FORCE_DB; + } elseif ($forceFiles) { + $force = rex_developer_synchronizer::FORCE_FILES; + } + + rex_developer_manager::start($force); $io->success('Synchronized developer files.'); } diff --git a/lib/manager.php b/lib/manager.php index 8fd7d3f..7e0fd87 100644 --- a/lib/manager.php +++ b/lib/manager.php @@ -55,10 +55,12 @@ private static function registerDefault() array('content' => 'template.php'), array('active' => 'boolean', 'attributes' => 'json') ); - $synchronizer->setEditedCallback(function (rex_developer_synchronizer_item $item) { + $callback = function (rex_developer_synchronizer_item $item) { $template = new rex_template($item->getId()); $template->deleteCache(); - }); + }; + $synchronizer->setEditedCallback($callback); + $synchronizer->setDeletedCallback($callback); self::register( $synchronizer, $page == 'templates' && ((($function == 'add' || $function == 'edit') && $save == 'ja') || $function == 'delete') @@ -71,20 +73,22 @@ private static function registerDefault() rex::getTable('module'), array('input' => 'input.php', 'output' => 'output.php') ); - $synchronizer->setEditedCallback(function (rex_developer_synchronizer_item $item) { + $callback = function (rex_developer_synchronizer_item $item) { $sql = rex_sql::factory(); $sql->setQuery(' SELECT DISTINCT(article.id) - FROM ' . rex::getTable('article') . ' article - LEFT JOIN ' . rex::getTable('article_slice') . ' slice + FROM '.rex::getTable('article').' article + LEFT JOIN '.rex::getTable('article_slice').' slice ON article.id = slice.article_id - WHERE slice.module_id=' . $item->getId() + WHERE slice.module_id='.$item->getId() ); for ($i = 0, $rows = $sql->getRows(); $i < $rows; ++$i) { rex_article_cache::delete($sql->getValue('article.id')); $sql->next(); } - }); + }; + $synchronizer->setEditedCallback($callback); + $synchronizer->setDeletedCallback($callback); self::register( $synchronizer, $page == 'modules/modules' && ((($function == 'add' || $function == 'edit') && $save == '1') || $function == 'delete') @@ -109,27 +113,29 @@ private static function registerDefault() * Starts the main developer process * * The method registers the default synchronizers and the extensions which will start the synchronizer objects + * + * @param bool|int $force Flag, whether the synchronizers should run in force mode (`rex_developer_synchronizer::FORCE_DB/FILES`) */ - public static function start() + public static function start($force = false) { rex_extension::registerPoint(new rex_extension_point('DEVELOPER_MANAGER_START', '', [], true)); self::registerDefault(); if (method_exists('rex', 'getConsole') && rex::getConsole()) { - self::synchronize(); + self::synchronize(null, $force); } elseif (rex_be_controller::getCurrentPagePart(1) === 'backup' && rex_get('function', 'string') === 'dbimport') { rex_extension::register('BACKUP_AFTER_DB_IMPORT', function () { - rex_developer_manager::synchronize(null, true); + rex_developer_manager::synchronize(null, rex_developer_synchronizer::FORCE_DB); }); } elseif (rex_be_controller::getCurrentPagePart(1) === 'developer' && rex_get('function', 'string') === 'update') { rex_extension::register('RESPONSE_SHUTDOWN', function () { - rex_developer_manager::synchronize(null, true); + rex_developer_manager::synchronize(null, rex_developer_synchronizer::FORCE_DB); }); } else { - self::synchronize(self::START_EARLY); - rex_extension::register('RESPONSE_SHUTDOWN', function () { - rex_developer_manager::synchronize(self::START_LATE); + self::synchronize(self::START_EARLY, $force); + rex_extension::register('RESPONSE_SHUTDOWN', function () use ($force) { + rex_developer_manager::synchronize(self::START_LATE, $force); }); } } @@ -138,7 +144,7 @@ public static function start() * Runs the synchronizer objects * * @param int|null $type Flag, which synchronizers should start. If the value is null, all synchronizers will start - * @param bool $force Flag, whether the synchronizers should run in force mode or not + * @param bool|int $force Flag, whether the synchronizers should run in force mode (`rex_developer_synchronizer::FORCE_DB/FILES`) * @see rex_developer_synchronizer::run */ public static function synchronize($type = null, $force = false) diff --git a/lib/synchronizer.php b/lib/synchronizer.php index 2e61711..5f5fb37 100644 --- a/lib/synchronizer.php +++ b/lib/synchronizer.php @@ -10,6 +10,12 @@ abstract class rex_developer_synchronizer const ID_FILE = '.rex_id'; const IGNORE_FILE = '.rex_ignore'; + /** Force the current status in db */ + const FORCE_DB = 1; + + /** Force the current status in file system */ + const FORCE_FILES = 2; + protected $dirname; protected $baseDir; protected $files; @@ -54,10 +60,21 @@ abstract protected function addItem(rex_developer_synchronizer_item $item); */ abstract protected function editItem(rex_developer_synchronizer_item $item); + /** + * The method is called, when an existing item is deleted by the file system (`FORCE_FILES` is activated) + * + * Use the method to delete the item in the base system + * + * @param rex_developer_synchronizer_item $item + */ + protected function deleteItem(rex_developer_synchronizer_item $item) + { + } + /** * Runs the synchronizer * - * @param bool $force Flag, whether all items of the base system should be handled as changed + * @param bool $force Flag, whether the synchronizers should run in force mode (`rex_developer_synchronizer::FORCE_DB/FILES`) */ public function run($force = false) { @@ -113,6 +130,8 @@ private function getNewAndExistingDirs() private function synchronizeReceivedItems(&$idList, &$existing, $force = false) { + $force = $force ? (int) $force : false; + foreach ($this->getItems() as $item) { $id = $item->getId(); $name = $item->getName(); @@ -121,7 +140,13 @@ private function synchronizeReceivedItems(&$idList, &$existing, $force = false) if (isset($existing[$id])) { $existingDir = $existing[$id]; unset($existing[$id]); + } elseif (self::FORCE_FILES === $force) { + $this->deleteItem($item); + unset($idList[$id]); + + continue; } + if (rex_config::get('developer', 'rename') || !$existingDir) { $dirBase = self::getFilename($name); @@ -147,7 +172,7 @@ private function synchronizeReceivedItems(&$idList, &$existing, $force = false) } $lastUpdated = isset($idList[$id]) ? $idList[$id] : 0; - $updated = max(1, $item->getUpdated()); + $updated = self::FORCE_FILES === $force ? 0 : max(1, $item->getUpdated()); $dbUpdated = $updated; $updateFiles = array(); $files = array(); @@ -158,9 +183,9 @@ private function synchronizeReceivedItems(&$idList, &$existing, $force = false) foreach ($this->files as $file) { $filePath = self::getFile($dir, $file, $prefix, rex_config::get('developer', 'rename')); $files[] = $filePath; - $fileUpdated = !$force && file_exists($filePath) ? filemtime($filePath) : 0; + $fileUpdated = self::FORCE_DB !== $force && file_exists($filePath) ? filemtime($filePath) : 0; - if ($dbUpdated > $fileUpdated && $dbUpdated > $lastUpdated) { + if ($dbUpdated > $fileUpdated && $dbUpdated > $lastUpdated || !file_exists($filePath)) { rex_file::put($filePath, $item->getFile($file)); touch($filePath, $updated); } elseif ($fileUpdated > $dbUpdated) { @@ -178,9 +203,13 @@ private function synchronizeReceivedItems(&$idList, &$existing, $force = false) private function removeItems(&$idList, &$existing, $force = false) { + if (self::FORCE_FILES === $force) { + return; + } + foreach ($existing as $id => $dir) { $dir = $this->baseDir . $dir . '/'; - if ($force || isset($idList[$id])) { + if (self::FORCE_DB === $force || isset($idList[$id])) { unset($existing[$id]); unset($idList[$id]); if (rex_config::get('developer', 'delete')) { diff --git a/lib/synchronizer_default.php b/lib/synchronizer_default.php index f1786e5..4bfd7c1 100644 --- a/lib/synchronizer_default.php +++ b/lib/synchronizer_default.php @@ -17,6 +17,7 @@ class rex_developer_synchronizer_default extends rex_developer_synchronizer protected $metadata; protected $addedCallback; protected $editedCallback; + protected $deletedCallback; protected $idColumn = 'id'; protected $nameColumn = 'name'; protected $updatedColumn = 'updatedate'; @@ -59,6 +60,16 @@ public function setEditedCallback($callback) $this->editedCallback = $callback; } + /** + * Sets a callback function which will be called if an existing item is deleted by the file system + * + * @param callable $callback + */ + public function setDeletedCallback($callback) + { + $this->deletedCallback = $callback; + } + /** * Sets the name of the ID column * @@ -224,6 +235,21 @@ protected function editItem(rex_developer_synchronizer_item $item) } } + /** + * {@inheritDoc} + */ + protected function deleteItem(rex_developer_synchronizer_item $item) + { + $sql = rex_sql::factory(); + $sql->setTable($this->table); + $sql->setWhere([$this->idColumn => $item->getId()]); + $sql->delete(); + + if ($this->deletedCallback) { + call_user_func($this->deletedCallback, $item); + } + } + /** * Casts a value by the given type *