From bcf056c9d92e5240e645c76a4cdc8ae159693f9a Mon Sep 17 00:00:00 2001 From: VirtualTam Date: Mon, 3 Dec 2018 23:49:20 +0100 Subject: namespacing: \Shaarli\Updater Signed-off-by: VirtualTam --- application/Updater.php | 637 ---------------- application/config/ConfigPhp.php | 2 +- application/updater/Updater.php | 553 ++++++++++++++ application/updater/UpdaterUtils.php | 39 + application/updater/exception/UpdaterException.php | 60 ++ composer.json | 4 +- index.php | 3 +- tests/Updater/DummyUpdater.php | 70 -- tests/Updater/UpdaterTest.php | 810 -------------------- tests/updater/DummyUpdater.php | 73 ++ tests/updater/UpdaterTest.php | 815 +++++++++++++++++++++ tests/utils/config/configPhp.php | 2 +- 12 files changed, 1547 insertions(+), 1521 deletions(-) delete mode 100644 application/Updater.php create mode 100644 application/updater/Updater.php create mode 100644 application/updater/UpdaterUtils.php create mode 100644 application/updater/exception/UpdaterException.php delete mode 100644 tests/Updater/DummyUpdater.php delete mode 100644 tests/Updater/UpdaterTest.php create mode 100644 tests/updater/DummyUpdater.php create mode 100644 tests/updater/UpdaterTest.php diff --git a/application/Updater.php b/application/Updater.php deleted file mode 100644 index ca05ecc2..00000000 --- a/application/Updater.php +++ /dev/null @@ -1,637 +0,0 @@ -doneUpdates = $doneUpdates; - $this->linkDB = $linkDB; - $this->conf = $conf; - $this->isLoggedIn = $isLoggedIn; - $this->session = &$session; - - // Retrieve all update methods. - $class = new ReflectionClass($this); - $this->methods = $class->getMethods(); - } - - /** - * Run all new updates. - * Update methods have to start with 'updateMethod' and return true (on success). - * - * @return array An array containing ran updates. - * - * @throws UpdaterException If something went wrong. - */ - public function update() - { - $updatesRan = array(); - - // If the user isn't logged in, exit without updating. - if ($this->isLoggedIn !== true) { - return $updatesRan; - } - - if ($this->methods === null) { - throw new UpdaterException(t('Couldn\'t retrieve Updater class methods.')); - } - - foreach ($this->methods as $method) { - // Not an update method or already done, pass. - if (! startsWith($method->getName(), 'updateMethod') - || in_array($method->getName(), $this->doneUpdates) - ) { - continue; - } - - try { - $method->setAccessible(true); - $res = $method->invoke($this); - // Update method must return true to be considered processed. - if ($res === true) { - $updatesRan[] = $method->getName(); - } - } catch (Exception $e) { - throw new UpdaterException($method, $e); - } - } - - $this->doneUpdates = array_merge($this->doneUpdates, $updatesRan); - - return $updatesRan; - } - - /** - * @return array Updates methods already processed. - */ - public function getDoneUpdates() - { - return $this->doneUpdates; - } - - /** - * Move deprecated options.php to config.php. - * - * Milestone 0.9 (old versioning) - shaarli/Shaarli#41: - * options.php is not supported anymore. - */ - public function updateMethodMergeDeprecatedConfigFile() - { - if (is_file($this->conf->get('resource.data_dir') . '/options.php')) { - include $this->conf->get('resource.data_dir') . '/options.php'; - - // Load GLOBALS into config - $allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS); - $allowedKeys[] = 'config'; - foreach ($GLOBALS as $key => $value) { - if (in_array($key, $allowedKeys)) { - $this->conf->set($key, $value); - } - } - $this->conf->write($this->isLoggedIn); - unlink($this->conf->get('resource.data_dir').'/options.php'); - } - - return true; - } - - /** - * Move old configuration in PHP to the new config system in JSON format. - * - * Will rename 'config.php' into 'config.save.php' and create 'config.json.php'. - * It will also convert legacy setting keys to the new ones. - */ - public function updateMethodConfigToJson() - { - // JSON config already exists, nothing to do. - if ($this->conf->getConfigIO() instanceof ConfigJson) { - return true; - } - - $configPhp = new ConfigPhp(); - $configJson = new ConfigJson(); - $oldConfig = $configPhp->read($this->conf->getConfigFile() . '.php'); - rename($this->conf->getConfigFileExt(), $this->conf->getConfigFile() . '.save.php'); - $this->conf->setConfigIO($configJson); - $this->conf->reload(); - - $legacyMap = array_flip(ConfigPhp::$LEGACY_KEYS_MAPPING); - foreach (ConfigPhp::$ROOT_KEYS as $key) { - $this->conf->set($legacyMap[$key], $oldConfig[$key]); - } - - // Set sub config keys (config and plugins) - $subConfig = array('config', 'plugins'); - foreach ($subConfig as $sub) { - foreach ($oldConfig[$sub] as $key => $value) { - if (isset($legacyMap[$sub .'.'. $key])) { - $configKey = $legacyMap[$sub .'.'. $key]; - } else { - $configKey = $sub .'.'. $key; - } - $this->conf->set($configKey, $value); - } - } - - try { - $this->conf->write($this->isLoggedIn); - return true; - } catch (IOException $e) { - error_log($e->getMessage()); - return false; - } - } - - /** - * Escape settings which have been manually escaped in every request in previous versions: - * - general.title - * - general.header_link - * - redirector.url - * - * @return bool true if the update is successful, false otherwise. - */ - public function updateMethodEscapeUnescapedConfig() - { - try { - $this->conf->set('general.title', escape($this->conf->get('general.title'))); - $this->conf->set('general.header_link', escape($this->conf->get('general.header_link'))); - $this->conf->set('redirector.url', escape($this->conf->get('redirector.url'))); - $this->conf->write($this->isLoggedIn); - } catch (Exception $e) { - error_log($e->getMessage()); - return false; - } - return true; - } - - /** - * Update the database to use the new ID system, which replaces linkdate primary keys. - * Also, creation and update dates are now DateTime objects (done by LinkDB). - * - * Since this update is very sensitve (changing the whole database), the datastore will be - * automatically backed up into the file datastore..php. - * - * LinkDB also adds the field 'shorturl' with the precedent format (linkdate smallhash), - * which will be saved by this method. - * - * @return bool true if the update is successful, false otherwise. - */ - public function updateMethodDatastoreIds() - { - // up to date database - if (isset($this->linkDB[0])) { - return true; - } - - $save = $this->conf->get('resource.data_dir') .'/datastore.'. date('YmdHis') .'.php'; - copy($this->conf->get('resource.datastore'), $save); - - $links = array(); - foreach ($this->linkDB as $offset => $value) { - $links[] = $value; - unset($this->linkDB[$offset]); - } - $links = array_reverse($links); - $cpt = 0; - foreach ($links as $l) { - unset($l['linkdate']); - $l['id'] = $cpt; - $this->linkDB[$cpt++] = $l; - } - - $this->linkDB->save($this->conf->get('resource.page_cache')); - $this->linkDB->reorder(); - - return true; - } - - /** - * Rename tags starting with a '-' to work with tag exclusion search. - */ - public function updateMethodRenameDashTags() - { - $linklist = $this->linkDB->filterSearch(); - foreach ($linklist as $key => $link) { - $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']); - $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true))); - $this->linkDB[$key] = $link; - } - $this->linkDB->save($this->conf->get('resource.page_cache')); - return true; - } - - /** - * Initialize API settings: - * - api.enabled: true - * - api.secret: generated secret - */ - public function updateMethodApiSettings() - { - if ($this->conf->exists('api.secret')) { - return true; - } - - $this->conf->set('api.enabled', true); - $this->conf->set( - 'api.secret', - generate_api_secret( - $this->conf->get('credentials.login'), - $this->conf->get('credentials.salt') - ) - ); - $this->conf->write($this->isLoggedIn); - return true; - } - - /** - * New setting: theme name. If the default theme is used, nothing to do. - * - * If the user uses a custom theme, raintpl_tpl dir is updated to the parent directory, - * and the current theme is set as default in the theme setting. - * - * @return bool true if the update is successful, false otherwise. - */ - public function updateMethodDefaultTheme() - { - // raintpl_tpl isn't the root template directory anymore. - // We run the update only if this folder still contains the template files. - $tplDir = $this->conf->get('resource.raintpl_tpl'); - $tplFile = $tplDir . '/linklist.html'; - if (! file_exists($tplFile)) { - return true; - } - - $parent = dirname($tplDir); - $this->conf->set('resource.raintpl_tpl', $parent); - $this->conf->set('resource.theme', trim(str_replace($parent, '', $tplDir), '/')); - $this->conf->write($this->isLoggedIn); - - // Dependency injection gore - RainTPL::$tpl_dir = $tplDir; - - return true; - } - - /** - * Move the file to inc/user.css to data/user.css. - * - * Note: Due to hardcoded paths, it's not unit testable. But one line of code should be fine. - * - * @return bool true if the update is successful, false otherwise. - */ - public function updateMethodMoveUserCss() - { - if (! is_file('inc/user.css')) { - return true; - } - - return rename('inc/user.css', 'data/user.css'); - } - - /** - * * `markdown_escape` is a new setting, set to true as default. - * - * If the markdown plugin was already enabled, escaping is disabled to avoid - * breaking existing entries. - */ - public function updateMethodEscapeMarkdown() - { - if ($this->conf->exists('security.markdown_escape')) { - return true; - } - - if (in_array('markdown', $this->conf->get('general.enabled_plugins'))) { - $this->conf->set('security.markdown_escape', false); - } else { - $this->conf->set('security.markdown_escape', true); - } - $this->conf->write($this->isLoggedIn); - - return true; - } - - /** - * Add 'http://' to Piwik URL the setting is set. - * - * @return bool true if the update is successful, false otherwise. - */ - public function updateMethodPiwikUrl() - { - if (! $this->conf->exists('plugins.PIWIK_URL') || startsWith($this->conf->get('plugins.PIWIK_URL'), 'http')) { - return true; - } - - $this->conf->set('plugins.PIWIK_URL', 'http://'. $this->conf->get('plugins.PIWIK_URL')); - $this->conf->write($this->isLoggedIn); - - return true; - } - - /** - * Use ATOM feed as default. - */ - public function updateMethodAtomDefault() - { - if (!$this->conf->exists('feed.show_atom') || $this->conf->get('feed.show_atom') === true) { - return true; - } - - $this->conf->set('feed.show_atom', true); - $this->conf->write($this->isLoggedIn); - - return true; - } - - /** - * Update updates.check_updates_branch setting. - * - * If the current major version digit matches the latest branch - * major version digit, we set the branch to `latest`, - * otherwise we'll check updates on the `stable` branch. - * - * No update required for the dev version. - * - * Note: due to hardcoded URL and lack of dependency injection, this is not unit testable. - * - * FIXME! This needs to be removed when we switch to first digit major version - * instead of the second one since the versionning process will change. - */ - public function updateMethodCheckUpdateRemoteBranch() - { - if (SHAARLI_VERSION === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') { - return true; - } - - // Get latest branch major version digit - $latestVersion = ApplicationUtils::getLatestGitVersionCode( - 'https://raw.githubusercontent.com/shaarli/Shaarli/latest/shaarli_version.php', - 5 - ); - if (preg_match('/(\d+)\.\d+$/', $latestVersion, $matches) === false) { - return false; - } - $latestMajor = $matches[1]; - - // Get current major version digit - preg_match('/(\d+)\.\d+$/', SHAARLI_VERSION, $matches); - $currentMajor = $matches[1]; - - if ($currentMajor === $latestMajor) { - $branch = 'latest'; - } else { - $branch = 'stable'; - } - $this->conf->set('updates.check_updates_branch', $branch); - $this->conf->write($this->isLoggedIn); - return true; - } - - /** - * Reset history store file due to date format change. - */ - public function updateMethodResetHistoryFile() - { - if (is_file($this->conf->get('resource.history'))) { - unlink($this->conf->get('resource.history')); - } - return true; - } - - /** - * Save the datastore -> the link order is now applied when links are saved. - */ - public function updateMethodReorderDatastore() - { - $this->linkDB->save($this->conf->get('resource.page_cache')); - return true; - } - - /** - * Change privateonly session key to visibility. - */ - public function updateMethodVisibilitySession() - { - if (isset($_SESSION['privateonly'])) { - unset($_SESSION['privateonly']); - $_SESSION['visibility'] = 'private'; - } - return true; - } - - /** - * Add download size and timeout to the configuration file - * - * @return bool true if the update is successful, false otherwise. - */ - public function updateMethodDownloadSizeAndTimeoutConf() - { - if ($this->conf->exists('general.download_max_size') - && $this->conf->exists('general.download_timeout') - ) { - return true; - } - - if (! $this->conf->exists('general.download_max_size')) { - $this->conf->set('general.download_max_size', 1024*1024*4); - } - - if (! $this->conf->exists('general.download_timeout')) { - $this->conf->set('general.download_timeout', 30); - } - - $this->conf->write($this->isLoggedIn); - return true; - } - - /** - * * Move thumbnails management to WebThumbnailer, coming with new settings. - */ - public function updateMethodWebThumbnailer() - { - if ($this->conf->exists('thumbnails.mode')) { - return true; - } - - $thumbnailsEnabled = extension_loaded('gd') && $this->conf->get('thumbnail.enable_thumbnails', true); - $this->conf->set('thumbnails.mode', $thumbnailsEnabled ? Thumbnailer::MODE_ALL : Thumbnailer::MODE_NONE); - $this->conf->set('thumbnails.width', 125); - $this->conf->set('thumbnails.height', 90); - $this->conf->remove('thumbnail'); - $this->conf->write(true); - - if ($thumbnailsEnabled) { - $this->session['warnings'][] = t( - 'You have enabled or changed thumbnails mode. Please synchronize them.' - ); - } - - return true; - } - - /** - * Set sticky = false on all links - * - * @return bool true if the update is successful, false otherwise. - */ - public function updateMethodSetSticky() - { - foreach ($this->linkDB as $key => $link) { - if (isset($link['sticky'])) { - return true; - } - $link['sticky'] = false; - $this->linkDB[$key] = $link; - } - - $this->linkDB->save($this->conf->get('resource.page_cache')); - - return true; - } -} - -/** - * Class UpdaterException. - */ -class UpdaterException extends Exception -{ - /** - * @var string Method where the error occurred. - */ - protected $method; - - /** - * @var Exception The parent exception. - */ - protected $previous; - - /** - * Constructor. - * - * @param string $message Force the error message if set. - * @param string $method Method where the error occurred. - * @param Exception|bool $previous Parent exception. - */ - public function __construct($message = '', $method = '', $previous = false) - { - $this->method = $method; - $this->previous = $previous; - $this->message = $this->buildMessage($message); - } - - /** - * Build the exception error message. - * - * @param string $message Optional given error message. - * - * @return string The built error message. - */ - private function buildMessage($message) - { - $out = ''; - if (! empty($message)) { - $out .= $message . PHP_EOL; - } - - if (! empty($this->method)) { - $out .= t('An error occurred while running the update ') . $this->method . PHP_EOL; - } - - if (! empty($this->previous)) { - $out .= ' '. $this->previous->getMessage(); - } - - return $out; - } -} - -/** - * Read the updates file, and return already done updates. - * - * @param string $updatesFilepath Updates file path. - * - * @return array Already done update methods. - */ -function read_updates_file($updatesFilepath) -{ - if (! empty($updatesFilepath) && is_file($updatesFilepath)) { - $content = file_get_contents($updatesFilepath); - if (! empty($content)) { - return explode(';', $content); - } - } - return array(); -} - -/** - * Write updates file. - * - * @param string $updatesFilepath Updates file path. - * @param array $updates Updates array to write. - * - * @throws Exception Couldn't write version number. - */ -function write_updates_file($updatesFilepath, $updates) -{ - if (empty($updatesFilepath)) { - throw new Exception(t('Updates file path is not set, can\'t write updates.')); - } - - $res = file_put_contents($updatesFilepath, implode(';', $updates)); - if ($res === false) { - throw new Exception(t('Unable to write updates in '. $updatesFilepath . '.')); - } -} diff --git a/application/config/ConfigPhp.php b/application/config/ConfigPhp.php index 9ed5d31f..cad34594 100644 --- a/application/config/ConfigPhp.php +++ b/application/config/ConfigPhp.php @@ -27,7 +27,7 @@ class ConfigPhp implements ConfigIO /** * Map legacy config keys with the new ones. * If ConfigPhp is used, getting will actually look for . - * The Updater will use this array to transform keys when switching to JSON. + * The updater will use this array to transform keys when switching to JSON. * * @var array current key => legacy key. */ diff --git a/application/updater/Updater.php b/application/updater/Updater.php new file mode 100644 index 00000000..55251a30 --- /dev/null +++ b/application/updater/Updater.php @@ -0,0 +1,553 @@ +doneUpdates = $doneUpdates; + $this->linkDB = $linkDB; + $this->conf = $conf; + $this->isLoggedIn = $isLoggedIn; + $this->session = &$session; + + // Retrieve all update methods. + $class = new ReflectionClass($this); + $this->methods = $class->getMethods(); + } + + /** + * Run all new updates. + * Update methods have to start with 'updateMethod' and return true (on success). + * + * @return array An array containing ran updates. + * + * @throws UpdaterException If something went wrong. + */ + public function update() + { + $updatesRan = array(); + + // If the user isn't logged in, exit without updating. + if ($this->isLoggedIn !== true) { + return $updatesRan; + } + + if ($this->methods === null) { + throw new UpdaterException(t('Couldn\'t retrieve updater class methods.')); + } + + foreach ($this->methods as $method) { + // Not an update method or already done, pass. + if (!startsWith($method->getName(), 'updateMethod') + || in_array($method->getName(), $this->doneUpdates) + ) { + continue; + } + + try { + $method->setAccessible(true); + $res = $method->invoke($this); + // Update method must return true to be considered processed. + if ($res === true) { + $updatesRan[] = $method->getName(); + } + } catch (Exception $e) { + throw new UpdaterException($method, $e); + } + } + + $this->doneUpdates = array_merge($this->doneUpdates, $updatesRan); + + return $updatesRan; + } + + /** + * @return array Updates methods already processed. + */ + public function getDoneUpdates() + { + return $this->doneUpdates; + } + + /** + * Move deprecated options.php to config.php. + * + * Milestone 0.9 (old versioning) - shaarli/Shaarli#41: + * options.php is not supported anymore. + */ + public function updateMethodMergeDeprecatedConfigFile() + { + if (is_file($this->conf->get('resource.data_dir') . '/options.php')) { + include $this->conf->get('resource.data_dir') . '/options.php'; + + // Load GLOBALS into config + $allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS); + $allowedKeys[] = 'config'; + foreach ($GLOBALS as $key => $value) { + if (in_array($key, $allowedKeys)) { + $this->conf->set($key, $value); + } + } + $this->conf->write($this->isLoggedIn); + unlink($this->conf->get('resource.data_dir') . '/options.php'); + } + + return true; + } + + /** + * Move old configuration in PHP to the new config system in JSON format. + * + * Will rename 'config.php' into 'config.save.php' and create 'config.json.php'. + * It will also convert legacy setting keys to the new ones. + */ + public function updateMethodConfigToJson() + { + // JSON config already exists, nothing to do. + if ($this->conf->getConfigIO() instanceof ConfigJson) { + return true; + } + + $configPhp = new ConfigPhp(); + $configJson = new ConfigJson(); + $oldConfig = $configPhp->read($this->conf->getConfigFile() . '.php'); + rename($this->conf->getConfigFileExt(), $this->conf->getConfigFile() . '.save.php'); + $this->conf->setConfigIO($configJson); + $this->conf->reload(); + + $legacyMap = array_flip(ConfigPhp::$LEGACY_KEYS_MAPPING); + foreach (ConfigPhp::$ROOT_KEYS as $key) { + $this->conf->set($legacyMap[$key], $oldConfig[$key]); + } + + // Set sub config keys (config and plugins) + $subConfig = array('config', 'plugins'); + foreach ($subConfig as $sub) { + foreach ($oldConfig[$sub] as $key => $value) { + if (isset($legacyMap[$sub . '.' . $key])) { + $configKey = $legacyMap[$sub . '.' . $key]; + } else { + $configKey = $sub . '.' . $key; + } + $this->conf->set($configKey, $value); + } + } + + try { + $this->conf->write($this->isLoggedIn); + return true; + } catch (IOException $e) { + error_log($e->getMessage()); + return false; + } + } + + /** + * Escape settings which have been manually escaped in every request in previous versions: + * - general.title + * - general.header_link + * - redirector.url + * + * @return bool true if the update is successful, false otherwise. + */ + public function updateMethodEscapeUnescapedConfig() + { + try { + $this->conf->set('general.title', escape($this->conf->get('general.title'))); + $this->conf->set('general.header_link', escape($this->conf->get('general.header_link'))); + $this->conf->set('redirector.url', escape($this->conf->get('redirector.url'))); + $this->conf->write($this->isLoggedIn); + } catch (Exception $e) { + error_log($e->getMessage()); + return false; + } + return true; + } + + /** + * Update the database to use the new ID system, which replaces linkdate primary keys. + * Also, creation and update dates are now DateTime objects (done by LinkDB). + * + * Since this update is very sensitve (changing the whole database), the datastore will be + * automatically backed up into the file datastore..php. + * + * LinkDB also adds the field 'shorturl' with the precedent format (linkdate smallhash), + * which will be saved by this method. + * + * @return bool true if the update is successful, false otherwise. + */ + public function updateMethodDatastoreIds() + { + // up to date database + if (isset($this->linkDB[0])) { + return true; + } + + $save = $this->conf->get('resource.data_dir') . '/datastore.' . date('YmdHis') . '.php'; + copy($this->conf->get('resource.datastore'), $save); + + $links = array(); + foreach ($this->linkDB as $offset => $value) { + $links[] = $value; + unset($this->linkDB[$offset]); + } + $links = array_reverse($links); + $cpt = 0; + foreach ($links as $l) { + unset($l['linkdate']); + $l['id'] = $cpt; + $this->linkDB[$cpt++] = $l; + } + + $this->linkDB->save($this->conf->get('resource.page_cache')); + $this->linkDB->reorder(); + + return true; + } + + /** + * Rename tags starting with a '-' to work with tag exclusion search. + */ + public function updateMethodRenameDashTags() + { + $linklist = $this->linkDB->filterSearch(); + foreach ($linklist as $key => $link) { + $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']); + $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true))); + $this->linkDB[$key] = $link; + } + $this->linkDB->save($this->conf->get('resource.page_cache')); + return true; + } + + /** + * Initialize API settings: + * - api.enabled: true + * - api.secret: generated secret + */ + public function updateMethodApiSettings() + { + if ($this->conf->exists('api.secret')) { + return true; + } + + $this->conf->set('api.enabled', true); + $this->conf->set( + 'api.secret', + generate_api_secret( + $this->conf->get('credentials.login'), + $this->conf->get('credentials.salt') + ) + ); + $this->conf->write($this->isLoggedIn); + return true; + } + + /** + * New setting: theme name. If the default theme is used, nothing to do. + * + * If the user uses a custom theme, raintpl_tpl dir is updated to the parent directory, + * and the current theme is set as default in the theme setting. + * + * @return bool true if the update is successful, false otherwise. + */ + public function updateMethodDefaultTheme() + { + // raintpl_tpl isn't the root template directory anymore. + // We run the update only if this folder still contains the template files. + $tplDir = $this->conf->get('resource.raintpl_tpl'); + $tplFile = $tplDir . '/linklist.html'; + if (!file_exists($tplFile)) { + return true; + } + + $parent = dirname($tplDir); + $this->conf->set('resource.raintpl_tpl', $parent); + $this->conf->set('resource.theme', trim(str_replace($parent, '', $tplDir), '/')); + $this->conf->write($this->isLoggedIn); + + // Dependency injection gore + RainTPL::$tpl_dir = $tplDir; + + return true; + } + + /** + * Move the file to inc/user.css to data/user.css. + * + * Note: Due to hardcoded paths, it's not unit testable. But one line of code should be fine. + * + * @return bool true if the update is successful, false otherwise. + */ + public function updateMethodMoveUserCss() + { + if (!is_file('inc/user.css')) { + return true; + } + + return rename('inc/user.css', 'data/user.css'); + } + + /** + * * `markdown_escape` is a new setting, set to true as default. + * + * If the markdown plugin was already enabled, escaping is disabled to avoid + * breaking existing entries. + */ + public function updateMethodEscapeMarkdown() + { + if ($this->conf->exists('security.markdown_escape')) { + return true; + } + + if (in_array('markdown', $this->conf->get('general.enabled_plugins'))) { + $this->conf->set('security.markdown_escape', false); + } else { + $this->conf->set('security.markdown_escape', true); + } + $this->conf->write($this->isLoggedIn); + + return true; + } + + /** + * Add 'http://' to Piwik URL the setting is set. + * + * @return bool true if the update is successful, false otherwise. + */ + public function updateMethodPiwikUrl() + { + if (!$this->conf->exists('plugins.PIWIK_URL') || startsWith($this->conf->get('plugins.PIWIK_URL'), 'http')) { + return true; + } + + $this->conf->set('plugins.PIWIK_URL', 'http://' . $this->conf->get('plugins.PIWIK_URL')); + $this->conf->write($this->isLoggedIn); + + return true; + } + + /** + * Use ATOM feed as default. + */ + public function updateMethodAtomDefault() + { + if (!$this->conf->exists('feed.show_atom') || $this->conf->get('feed.show_atom') === true) { + return true; + } + + $this->conf->set('feed.show_atom', true); + $this->conf->write($this->isLoggedIn); + + return true; + } + + /** + * Update updates.check_updates_branch setting. + * + * If the current major version digit matches the latest branch + * major version digit, we set the branch to `latest`, + * otherwise we'll check updates on the `stable` branch. + * + * No update required for the dev version. + * + * Note: due to hardcoded URL and lack of dependency injection, this is not unit testable. + * + * FIXME! This needs to be removed when we switch to first digit major version + * instead of the second one since the versionning process will change. + */ + public function updateMethodCheckUpdateRemoteBranch() + { + if (SHAARLI_VERSION === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') { + return true; + } + + // Get latest branch major version digit + $latestVersion = ApplicationUtils::getLatestGitVersionCode( + 'https://raw.githubusercontent.com/shaarli/Shaarli/latest/shaarli_version.php', + 5 + ); + if (preg_match('/(\d+)\.\d+$/', $latestVersion, $matches) === false) { + return false; + } + $latestMajor = $matches[1]; + + // Get current major version digit + preg_match('/(\d+)\.\d+$/', SHAARLI_VERSION, $matches); + $currentMajor = $matches[1]; + + if ($currentMajor === $latestMajor) { + $branch = 'latest'; + } else { + $branch = 'stable'; + } + $this->conf->set('updates.check_updates_branch', $branch); + $this->conf->write($this->isLoggedIn); + return true; + } + + /** + * Reset history store file due to date format change. + */ + public function updateMethodResetHistoryFile() + { + if (is_file($this->conf->get('resource.history'))) { + unlink($this->conf->get('resource.history')); + } + return true; + } + + /** + * Save the datastore -> the link order is now applied when links are saved. + */ + public function updateMethodReorderDatastore() + { + $this->linkDB->save($this->conf->get('resource.page_cache')); + return true; + } + + /** + * Change privateonly session key to visibility. + */ + public function updateMethodVisibilitySession() + { + if (isset($_SESSION['privateonly'])) { + unset($_SESSION['privateonly']); + $_SESSION['visibility'] = 'private'; + } + return true; + } + + /** + * Add download size and timeout to the configuration file + * + * @return bool true if the update is successful, false otherwise. + */ + public function updateMethodDownloadSizeAndTimeoutConf() + { + if ($this->conf->exists('general.download_max_size') + && $this->conf->exists('general.download_timeout') + ) { + return true; + } + + if (!$this->conf->exists('general.download_max_size')) { + $this->conf->set('general.download_max_size', 1024 * 1024 * 4); + } + + if (!$this->conf->exists('general.download_timeout')) { + $this->conf->set('general.download_timeout', 30); + } + + $this->conf->write($this->isLoggedIn); + return true; + } + + /** + * * Move thumbnails management to WebThumbnailer, coming with new settings. + */ + public function updateMethodWebThumbnailer() + { + if ($this->conf->exists('thumbnails.mode')) { + return true; + } + + $thumbnailsEnabled = extension_loaded('gd') && $this->conf->get('thumbnail.enable_thumbnails', true); + $this->conf->set('thumbnails.mode', $thumbnailsEnabled ? Thumbnailer::MODE_ALL : Thumbnailer::MODE_NONE); + $this->conf->set('thumbnails.width', 125); + $this->conf->set('thumbnails.height', 90); + $this->conf->remove('thumbnail'); + $this->conf->write(true); + + if ($thumbnailsEnabled) { + $this->session['warnings'][] = t( + 'You have enabled or changed thumbnails mode. Please synchronize them.' + ); + } + + return true; + } + + /** + * Set sticky = false on all links + * + * @return bool true if the update is successful, false otherwise. + */ + public function updateMethodSetSticky() + { + foreach ($this->linkDB as $key => $link) { + if (isset($link['sticky'])) { + return true; + } + $link['sticky'] = false; + $this->linkDB[$key] = $link; + } + + $this->linkDB->save($this->conf->get('resource.page_cache')); + + return true; + } +} diff --git a/application/updater/UpdaterUtils.php b/application/updater/UpdaterUtils.php new file mode 100644 index 00000000..34d4f422 --- /dev/null +++ b/application/updater/UpdaterUtils.php @@ -0,0 +1,39 @@ +method = $method; + $this->previous = $previous; + $this->message = $this->buildMessage($message); + } + + /** + * Build the exception error message. + * + * @param string $message Optional given error message. + * + * @return string The built error message. + */ + private function buildMessage($message) + { + $out = ''; + if (!empty($message)) { + $out .= $message . PHP_EOL; + } + + if (!empty($this->method)) { + $out .= t('An error occurred while running the update ') . $this->method . PHP_EOL; + } + + if (!empty($this->previous)) { + $out .= ' ' . $this->previous->getMessage(); + } + + return $out; + } +} diff --git a/composer.json b/composer.json index 4c14e794..af763472 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,9 @@ "Shaarli\\Feed\\": "application/feed", "Shaarli\\Http\\": "application/http", "Shaarli\\Render\\": "application/render", - "Shaarli\\Security\\": "application/security" + "Shaarli\\Security\\": "application/security", + "Shaarli\\Updater\\": "application/updater", + "Shaarli\\Updater\\Exception\\": "application/updater/exception" } } } diff --git a/index.php b/index.php index 146b4457..ce0373e1 100644 --- a/index.php +++ b/index.php @@ -62,6 +62,7 @@ require_once 'application/config/ConfigPlugin.php'; require_once 'application/feed/Cache.php'; require_once 'application/http/HttpUtils.php'; require_once 'application/http/UrlUtils.php'; +require_once 'application/updater/UpdaterUtils.php'; require_once 'application/FileUtils.php'; require_once 'application/History.php'; require_once 'application/NetscapeBookmarkUtils.php'; @@ -69,7 +70,6 @@ require_once 'application/TimeZone.php'; require_once 'application/Utils.php'; require_once 'application/PluginManager.php'; require_once 'application/Router.php'; -require_once 'application/Updater.php'; use \Shaarli\Bookmark\Exception\LinkNotFoundException; use \Shaarli\Bookmark\LinkDB; @@ -83,6 +83,7 @@ use \Shaarli\Render\ThemeUtils; use \Shaarli\Security\LoginManager; use \Shaarli\Security\SessionManager; use \Shaarli\Thumbnailer; +use Shaarli\Updater\Updater; // Ensure the PHP version is supported try { diff --git a/tests/Updater/DummyUpdater.php b/tests/Updater/DummyUpdater.php deleted file mode 100644 index 3c74b4ff..00000000 --- a/tests/Updater/DummyUpdater.php +++ /dev/null @@ -1,70 +0,0 @@ -methods = $class->getMethods(ReflectionMethod::IS_FINAL); - } - - /** - * Update method 1. - * - * @return bool true. - */ - final private function updateMethodDummy1() - { - return true; - } - - /** - * Update method 2. - * - * @return bool true. - */ - final private function updateMethodDummy2() - { - return true; - } - - /** - * Update method 3. - * - * @return bool true. - */ - final private function updateMethodDummy3() - { - return true; - } - - /** - * Update method 4, raise an exception. - * - * @throws Exception error. - */ - final private function updateMethodException() - { - throw new Exception('whatever'); - } -} diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php deleted file mode 100644 index f910e054..00000000 --- a/tests/Updater/UpdaterTest.php +++ /dev/null @@ -1,810 +0,0 @@ -conf = new ConfigManager(self::$configFile); - } - - /** - * Test read_updates_file with an empty/missing file. - */ - public function testReadEmptyUpdatesFile() - { - $this->assertEquals(array(), read_updates_file('')); - $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; - touch($updatesFile); - $this->assertEquals(array(), read_updates_file($updatesFile)); - unlink($updatesFile); - } - - /** - * Test read/write updates file. - */ - public function testReadWriteUpdatesFile() - { - $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; - $updatesMethods = array('m1', 'm2', 'm3'); - - write_updates_file($updatesFile, $updatesMethods); - $readMethods = read_updates_file($updatesFile); - $this->assertEquals($readMethods, $updatesMethods); - - // Update - $updatesMethods[] = 'm4'; - write_updates_file($updatesFile, $updatesMethods); - $readMethods = read_updates_file($updatesFile); - $this->assertEquals($readMethods, $updatesMethods); - unlink($updatesFile); - } - - /** - * Test errors in write_updates_file(): empty updates file. - * - * @expectedException Exception - * @expectedExceptionMessageRegExp /Updates file path is not set(.*)/ - */ - public function testWriteEmptyUpdatesFile() - { - write_updates_file('', array('test')); - } - - /** - * Test errors in write_updates_file(): not writable updates file. - * - * @expectedException Exception - * @expectedExceptionMessageRegExp /Unable to write(.*)/ - */ - public function testWriteUpdatesFileNotWritable() - { - $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; - touch($updatesFile); - chmod($updatesFile, 0444); - try { - @write_updates_file($updatesFile, array('test')); - } catch (Exception $e) { - unlink($updatesFile); - throw $e; - } - } - - /** - * Test the update() method, with no update to run. - * 1. Everything already run. - * 2. User is logged out. - */ - public function testNoUpdates() - { - $updates = array( - 'updateMethodDummy1', - 'updateMethodDummy2', - 'updateMethodDummy3', - 'updateMethodException', - ); - $updater = new DummyUpdater($updates, array(), $this->conf, true); - $this->assertEquals(array(), $updater->update()); - - $updater = new DummyUpdater(array(), array(), $this->conf, false); - $this->assertEquals(array(), $updater->update()); - } - - /** - * Test the update() method, with all updates to run (except the failing one). - */ - public function testUpdatesFirstTime() - { - $updates = array('updateMethodException',); - $expectedUpdates = array( - 'updateMethodDummy1', - 'updateMethodDummy2', - 'updateMethodDummy3', - ); - $updater = new DummyUpdater($updates, array(), $this->conf, true); - $this->assertEquals($expectedUpdates, $updater->update()); - } - - /** - * Test the update() method, only one update to run. - */ - public function testOneUpdate() - { - $updates = array( - 'updateMethodDummy1', - 'updateMethodDummy3', - 'updateMethodException', - ); - $expectedUpdate = array('updateMethodDummy2'); - - $updater = new DummyUpdater($updates, array(), $this->conf, true); - $this->assertEquals($expectedUpdate, $updater->update()); - } - - /** - * Test Update failed. - * - * @expectedException UpdaterException - */ - public function testUpdateFailed() - { - $updates = array( - 'updateMethodDummy1', - 'updateMethodDummy2', - 'updateMethodDummy3', - ); - - $updater = new DummyUpdater($updates, array(), $this->conf, true); - $updater->update(); - } - - /** - * Test update mergeDeprecatedConfig: - * 1. init a config file. - * 2. init a options.php file with update value. - * 3. merge. - * 4. check updated value in config file. - */ - public function testUpdateMergeDeprecatedConfig() - { - $this->conf->setConfigFile('tests/utils/config/configPhp'); - $this->conf->reset(); - - $optionsFile = 'tests/Updater/options.php'; - $options = 'conf->setConfigFile('tests/Updater/config'); - - // merge configs - $updater = new Updater(array(), array(), $this->conf, true); - // This writes a new config file in tests/Updater/config.php - $updater->updateMethodMergeDeprecatedConfigFile(); - - // make sure updated field is changed - $this->conf->reload(); - $this->assertTrue($this->conf->get('privacy.default_private_links')); - $this->assertFalse(is_file($optionsFile)); - // Delete the generated file. - unlink($this->conf->getConfigFileExt()); - } - - /** - * Test mergeDeprecatedConfig in without options file. - */ - public function testMergeDeprecatedConfigNoFile() - { - $updater = new Updater(array(), array(), $this->conf, true); - $updater->updateMethodMergeDeprecatedConfigFile(); - - $this->assertEquals('root', $this->conf->get('credentials.login')); - } - - /** - * Test renameDashTags update method. - */ - public function testRenameDashTags() - { - $refDB = new ReferenceLinkDB(); - $refDB->write(self::$testDatastore); - $linkDB = new LinkDB(self::$testDatastore, true, false); - - $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); - $updater = new Updater(array(), $linkDB, $this->conf, true); - $updater->updateMethodRenameDashTags(); - $this->assertNotEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); - } - - /** - * Convert old PHP config file to JSON config. - */ - public function testConfigToJson() - { - $configFile = 'tests/utils/config/configPhp'; - $this->conf->setConfigFile($configFile); - $this->conf->reset(); - - // The ConfigIO is initialized with ConfigPhp. - $this->assertTrue($this->conf->getConfigIO() instanceof ConfigPhp); - - $updater = new Updater(array(), array(), $this->conf, false); - $done = $updater->updateMethodConfigToJson(); - $this->assertTrue($done); - - // The ConfigIO has been updated to ConfigJson. - $this->assertTrue($this->conf->getConfigIO() instanceof ConfigJson); - $this->assertTrue(file_exists($this->conf->getConfigFileExt())); - - // Check JSON config data. - $this->conf->reload(); - $this->assertEquals('root', $this->conf->get('credentials.login')); - $this->assertEquals('lala', $this->conf->get('redirector.url')); - $this->assertEquals('data/datastore.php', $this->conf->get('resource.datastore')); - $this->assertEquals('1', $this->conf->get('plugins.WALLABAG_VERSION')); - - rename($configFile . '.save.php', $configFile . '.php'); - unlink($this->conf->getConfigFileExt()); - } - - /** - * Launch config conversion update with an existing JSON file => nothing to do. - */ - public function testConfigToJsonNothingToDo() - { - $filetime = filemtime($this->conf->getConfigFileExt()); - $updater = new Updater(array(), array(), $this->conf, false); - $done = $updater->updateMethodConfigToJson(); - $this->assertTrue($done); - $expected = filemtime($this->conf->getConfigFileExt()); - $this->assertEquals($expected, $filetime); - } - - /** - * Test escapeUnescapedConfig with valid data. - */ - public function testEscapeConfig() - { - $sandbox = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandbox . '.json.php'); - $this->conf = new ConfigManager($sandbox); - $title = ''; - $headerLink = ''; - $redirectorUrl = ''; - $this->conf->set('general.title', $title); - $this->conf->set('general.header_link', $headerLink); - $this->conf->set('redirector.url', $redirectorUrl); - $updater = new Updater(array(), array(), $this->conf, true); - $done = $updater->updateMethodEscapeUnescapedConfig(); - $this->assertTrue($done); - $this->conf->reload(); - $this->assertEquals(escape($title), $this->conf->get('general.title')); - $this->assertEquals(escape($headerLink), $this->conf->get('general.header_link')); - $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url')); - unlink($sandbox . '.json.php'); - } - - /** - * Test updateMethodApiSettings(): create default settings for the API (enabled + secret). - */ - public function testUpdateApiSettings() - { - $confFile = 'sandbox/config'; - copy(self::$configFile .'.json.php', $confFile .'.json.php'); - $conf = new ConfigManager($confFile); - $updater = new Updater(array(), array(), $conf, true); - - $this->assertFalse($conf->exists('api.enabled')); - $this->assertFalse($conf->exists('api.secret')); - $updater->updateMethodApiSettings(); - $conf->reload(); - $this->assertTrue($conf->get('api.enabled')); - $this->assertTrue($conf->exists('api.secret')); - unlink($confFile .'.json.php'); - } - - /** - * Test updateMethodApiSettings(): already set, do nothing. - */ - public function testUpdateApiSettingsNothingToDo() - { - $confFile = 'sandbox/config'; - copy(self::$configFile .'.json.php', $confFile .'.json.php'); - $conf = new ConfigManager($confFile); - $conf->set('api.enabled', false); - $conf->set('api.secret', ''); - $updater = new Updater(array(), array(), $conf, true); - $updater->updateMethodApiSettings(); - $this->assertFalse($conf->get('api.enabled')); - $this->assertEmpty($conf->get('api.secret')); - unlink($confFile .'.json.php'); - } - - /** - * Test updateMethodDatastoreIds(). - */ - public function testDatastoreIds() - { - $links = array( - '20121206_182539' => array( - 'linkdate' => '20121206_182539', - 'title' => 'Geek and Poke', - 'url' => 'http://geek-and-poke.com/', - 'description' => 'desc', - 'tags' => 'dev cartoon tag1 tag2 tag3 tag4 ', - 'updated' => '20121206_190301', - 'private' => false, - ), - '20121206_172539' => array( - 'linkdate' => '20121206_172539', - 'title' => 'UserFriendly - Samba', - 'url' => 'http://ars.userfriendly.org/cartoons/?id=20010306', - 'description' => '', - 'tags' => 'samba cartoon web', - 'private' => false, - ), - '20121206_142300' => array( - 'linkdate' => '20121206_142300', - 'title' => 'UserFriendly - Web Designer', - 'url' => 'http://ars.userfriendly.org/cartoons/?id=20121206', - 'description' => 'Naming conventions... #private', - 'tags' => 'samba cartoon web', - 'private' => true, - ), - ); - $refDB = new ReferenceLinkDB(); - $refDB->setLinks($links); - $refDB->write(self::$testDatastore); - $linkDB = new LinkDB(self::$testDatastore, true, false); - - $checksum = hash_file('sha1', self::$testDatastore); - - $this->conf->set('resource.data_dir', 'sandbox'); - $this->conf->set('resource.datastore', self::$testDatastore); - - $updater = new Updater(array(), $linkDB, $this->conf, true); - $this->assertTrue($updater->updateMethodDatastoreIds()); - - $linkDB = new LinkDB(self::$testDatastore, true, false); - - $backup = glob($this->conf->get('resource.data_dir') . '/datastore.'. date('YmdH') .'*.php'); - $backup = $backup[0]; - - $this->assertFileExists($backup); - $this->assertEquals($checksum, hash_file('sha1', $backup)); - unlink($backup); - - $this->assertEquals(3, count($linkDB)); - $this->assertTrue(isset($linkDB[0])); - $this->assertFalse(isset($linkDB[0]['linkdate'])); - $this->assertEquals(0, $linkDB[0]['id']); - $this->assertEquals('UserFriendly - Web Designer', $linkDB[0]['title']); - $this->assertEquals('http://ars.userfriendly.org/cartoons/?id=20121206', $linkDB[0]['url']); - $this->assertEquals('Naming conventions... #private', $linkDB[0]['description']); - $this->assertEquals('samba cartoon web', $linkDB[0]['tags']); - $this->assertTrue($linkDB[0]['private']); - $this->assertEquals( - DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'), - $linkDB[0]['created'] - ); - - $this->assertTrue(isset($linkDB[1])); - $this->assertFalse(isset($linkDB[1]['linkdate'])); - $this->assertEquals(1, $linkDB[1]['id']); - $this->assertEquals('UserFriendly - Samba', $linkDB[1]['title']); - $this->assertEquals( - DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'), - $linkDB[1]['created'] - ); - - $this->assertTrue(isset($linkDB[2])); - $this->assertFalse(isset($linkDB[2]['linkdate'])); - $this->assertEquals(2, $linkDB[2]['id']); - $this->assertEquals('Geek and Poke', $linkDB[2]['title']); - $this->assertEquals( - DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'), - $linkDB[2]['created'] - ); - $this->assertEquals( - DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_190301'), - $linkDB[2]['updated'] - ); - } - - /** - * Test updateMethodDatastoreIds() with the update already applied: nothing to do. - */ - public function testDatastoreIdsNothingToDo() - { - $refDB = new ReferenceLinkDB(); - $refDB->write(self::$testDatastore); - $linkDB = new LinkDB(self::$testDatastore, true, false); - - $this->conf->set('resource.data_dir', 'sandbox'); - $this->conf->set('resource.datastore', self::$testDatastore); - - $checksum = hash_file('sha1', self::$testDatastore); - $updater = new Updater(array(), $linkDB, $this->conf, true); - $this->assertTrue($updater->updateMethodDatastoreIds()); - $this->assertEquals($checksum, hash_file('sha1', self::$testDatastore)); - } - - /** - * Test defaultTheme update with default settings: nothing to do. - */ - public function testDefaultThemeWithDefaultSettings() - { - $sandbox = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandbox . '.json.php'); - $this->conf = new ConfigManager($sandbox); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodDefaultTheme()); - - $this->assertEquals('tpl/', $this->conf->get('resource.raintpl_tpl')); - $this->assertEquals('default', $this->conf->get('resource.theme')); - $this->conf = new ConfigManager($sandbox); - $this->assertEquals('tpl/', $this->conf->get('resource.raintpl_tpl')); - $this->assertEquals('default', $this->conf->get('resource.theme')); - unlink($sandbox . '.json.php'); - } - - /** - * Test defaultTheme update with a custom theme in a subfolder - */ - public function testDefaultThemeWithCustomTheme() - { - $theme = 'iamanartist'; - $sandbox = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandbox . '.json.php'); - $this->conf = new ConfigManager($sandbox); - mkdir('sandbox/'. $theme); - touch('sandbox/'. $theme .'/linklist.html'); - $this->conf->set('resource.raintpl_tpl', 'sandbox/'. $theme .'/'); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodDefaultTheme()); - - $this->assertEquals('sandbox', $this->conf->get('resource.raintpl_tpl')); - $this->assertEquals($theme, $this->conf->get('resource.theme')); - $this->conf = new ConfigManager($sandbox); - $this->assertEquals('sandbox', $this->conf->get('resource.raintpl_tpl')); - $this->assertEquals($theme, $this->conf->get('resource.theme')); - unlink($sandbox . '.json.php'); - unlink('sandbox/'. $theme .'/linklist.html'); - rmdir('sandbox/'. $theme); - } - - /** - * Test updateMethodEscapeMarkdown with markdown plugin enabled - * => setting markdown_escape set to false. - */ - public function testEscapeMarkdownSettingToFalse() - { - $sandboxConf = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); - $this->conf = new ConfigManager($sandboxConf); - - $this->conf->set('general.enabled_plugins', ['markdown']); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodEscapeMarkdown()); - $this->assertFalse($this->conf->get('security.markdown_escape')); - - // reload from file - $this->conf = new ConfigManager($sandboxConf); - $this->assertFalse($this->conf->get('security.markdown_escape')); - } - - - /** - * Test updateMethodEscapeMarkdown with markdown plugin disabled - * => setting markdown_escape set to true. - */ - public function testEscapeMarkdownSettingToTrue() - { - $sandboxConf = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); - $this->conf = new ConfigManager($sandboxConf); - - $this->conf->set('general.enabled_plugins', []); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodEscapeMarkdown()); - $this->assertTrue($this->conf->get('security.markdown_escape')); - - // reload from file - $this->conf = new ConfigManager($sandboxConf); - $this->assertTrue($this->conf->get('security.markdown_escape')); - } - - /** - * Test updateMethodEscapeMarkdown with nothing to do (setting already enabled) - */ - public function testEscapeMarkdownSettingNothingToDoEnabled() - { - $sandboxConf = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); - $this->conf = new ConfigManager($sandboxConf); - $this->conf->set('security.markdown_escape', true); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodEscapeMarkdown()); - $this->assertTrue($this->conf->get('security.markdown_escape')); - } - - /** - * Test updateMethodEscapeMarkdown with nothing to do (setting already disabled) - */ - public function testEscapeMarkdownSettingNothingToDoDisabled() - { - $this->conf->set('security.markdown_escape', false); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodEscapeMarkdown()); - $this->assertFalse($this->conf->get('security.markdown_escape')); - } - - /** - * Test updateMethodPiwikUrl with valid data - */ - public function testUpdatePiwikUrlValid() - { - $sandboxConf = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); - $this->conf = new ConfigManager($sandboxConf); - $url = 'mypiwik.tld'; - $this->conf->set('plugins.PIWIK_URL', $url); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodPiwikUrl()); - $this->assertEquals('http://'. $url, $this->conf->get('plugins.PIWIK_URL')); - - // reload from file - $this->conf = new ConfigManager($sandboxConf); - $this->assertEquals('http://'. $url, $this->conf->get('plugins.PIWIK_URL')); - } - - /** - * Test updateMethodPiwikUrl without setting - */ - public function testUpdatePiwikUrlEmpty() - { - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodPiwikUrl()); - $this->assertEmpty($this->conf->get('plugins.PIWIK_URL')); - } - - /** - * Test updateMethodPiwikUrl: valid URL, nothing to do - */ - public function testUpdatePiwikUrlNothingToDo() - { - $url = 'https://mypiwik.tld'; - $this->conf->set('plugins.PIWIK_URL', $url); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodPiwikUrl()); - $this->assertEquals($url, $this->conf->get('plugins.PIWIK_URL')); - } - - /** - * Test updateMethodAtomDefault with show_atom set to false - * => update to true. - */ - public function testUpdateMethodAtomDefault() - { - $sandboxConf = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); - $this->conf = new ConfigManager($sandboxConf); - $this->conf->set('feed.show_atom', false); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodAtomDefault()); - $this->assertTrue($this->conf->get('feed.show_atom')); - // reload from file - $this->conf = new ConfigManager($sandboxConf); - $this->assertTrue($this->conf->get('feed.show_atom')); - } - /** - * Test updateMethodAtomDefault with show_atom not set. - * => nothing to do - */ - public function testUpdateMethodAtomDefaultNoExist() - { - $sandboxConf = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); - $this->conf = new ConfigManager($sandboxConf); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodAtomDefault()); - $this->assertTrue($this->conf->get('feed.show_atom')); - } - /** - * Test updateMethodAtomDefault with show_atom set to true. - * => nothing to do - */ - public function testUpdateMethodAtomDefaultAlreadyTrue() - { - $sandboxConf = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); - $this->conf = new ConfigManager($sandboxConf); - $this->conf->set('feed.show_atom', true); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodAtomDefault()); - $this->assertTrue($this->conf->get('feed.show_atom')); - } - - /** - * Test updateMethodDownloadSizeAndTimeoutConf, it should be set if none is already defined. - */ - public function testUpdateMethodDownloadSizeAndTimeoutConf() - { - $sandboxConf = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); - $this->conf = new ConfigManager($sandboxConf); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); - $this->assertEquals(4194304, $this->conf->get('general.download_max_size')); - $this->assertEquals(30, $this->conf->get('general.download_timeout')); - - $this->conf = new ConfigManager($sandboxConf); - $this->assertEquals(4194304, $this->conf->get('general.download_max_size')); - $this->assertEquals(30, $this->conf->get('general.download_timeout')); - } - - /** - * Test updateMethodDownloadSizeAndTimeoutConf, it shouldn't be set if it is already defined. - */ - public function testUpdateMethodDownloadSizeAndTimeoutConfIgnore() - { - $sandboxConf = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); - $this->conf = new ConfigManager($sandboxConf); - $this->conf->set('general.download_max_size', 38); - $this->conf->set('general.download_timeout', 70); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); - $this->assertEquals(38, $this->conf->get('general.download_max_size')); - $this->assertEquals(70, $this->conf->get('general.download_timeout')); - } - - /** - * Test updateMethodDownloadSizeAndTimeoutConf, only the maz size should be set here. - */ - public function testUpdateMethodDownloadSizeAndTimeoutConfOnlySize() - { - $sandboxConf = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); - $this->conf = new ConfigManager($sandboxConf); - $this->conf->set('general.download_max_size', 38); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); - $this->assertEquals(38, $this->conf->get('general.download_max_size')); - $this->assertEquals(30, $this->conf->get('general.download_timeout')); - } - - /** - * Test updateMethodDownloadSizeAndTimeoutConf, only the time out should be set here. - */ - public function testUpdateMethodDownloadSizeAndTimeoutConfOnlyTimeout() - { - $sandboxConf = 'sandbox/config'; - copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); - $this->conf = new ConfigManager($sandboxConf); - $this->conf->set('general.download_timeout', 3); - $updater = new Updater([], [], $this->conf, true); - $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); - $this->assertEquals(4194304, $this->conf->get('general.download_max_size')); - $this->assertEquals(3, $this->conf->get('general.download_timeout')); - } - - /** -<<<<<<< HEAD - * Test updateMethodWebThumbnailer with thumbnails enabled. - */ - public function testUpdateMethodWebThumbnailerEnabled() - { - $this->conf->remove('thumbnails'); - $this->conf->set('thumbnail.enable_thumbnails', true); - $updater = new Updater([], [], $this->conf, true, $_SESSION); - $this->assertTrue($updater->updateMethodWebThumbnailer()); - $this->assertFalse($this->conf->exists('thumbnail')); - $this->assertEquals(\Shaarli\Thumbnailer::MODE_ALL, $this->conf->get('thumbnails.mode')); - $this->assertEquals(125, $this->conf->get('thumbnails.width')); - $this->assertEquals(90, $this->conf->get('thumbnails.height')); - $this->assertContains('You have enabled or changed thumbnails', $_SESSION['warnings'][0]); - } - - /** - * Test updateMethodWebThumbnailer with thumbnails disabled. - */ - public function testUpdateMethodWebThumbnailerDisabled() - { - $this->conf->remove('thumbnails'); - $this->conf->set('thumbnail.enable_thumbnails', false); - $updater = new Updater([], [], $this->conf, true, $_SESSION); - $this->assertTrue($updater->updateMethodWebThumbnailer()); - $this->assertFalse($this->conf->exists('thumbnail')); - $this->assertEquals(Thumbnailer::MODE_NONE, $this->conf->get('thumbnails.mode')); - $this->assertEquals(125, $this->conf->get('thumbnails.width')); - $this->assertEquals(90, $this->conf->get('thumbnails.height')); - $this->assertTrue(empty($_SESSION['warnings'])); - } - - /** - * Test updateMethodWebThumbnailer with thumbnails disabled. - */ - public function testUpdateMethodWebThumbnailerNothingToDo() - { - $updater = new Updater([], [], $this->conf, true, $_SESSION); - $this->assertTrue($updater->updateMethodWebThumbnailer()); - $this->assertFalse($this->conf->exists('thumbnail')); - $this->assertEquals(Thumbnailer::MODE_COMMON, $this->conf->get('thumbnails.mode')); - $this->assertEquals(90, $this->conf->get('thumbnails.width')); - $this->assertEquals(53, $this->conf->get('thumbnails.height')); - $this->assertTrue(empty($_SESSION['warnings'])); - } - - /** - * Test updateMethodSetSticky(). - */ - public function testUpdateStickyValid() - { - $blank = [ - 'id' => 1, - 'url' => 'z', - 'title' => '', - 'description' => '', - 'tags' => '', - 'created' => new DateTime(), - ]; - $links = [ - 1 => ['id' => 1] + $blank, - 2 => ['id' => 2] + $blank, - ]; - $refDB = new ReferenceLinkDB(); - $refDB->setLinks($links); - $refDB->write(self::$testDatastore); - $linkDB = new LinkDB(self::$testDatastore, true, false); - - $updater = new Updater(array(), $linkDB, $this->conf, true); - $this->assertTrue($updater->updateMethodSetSticky()); - - $linkDB = new LinkDB(self::$testDatastore, true, false); - foreach ($linkDB as $link) { - $this->assertFalse($link['sticky']); - } - } - - /** - * Test updateMethodSetSticky(). - */ - public function testUpdateStickyNothingToDo() - { - $blank = [ - 'id' => 1, - 'url' => 'z', - 'title' => '', - 'description' => '', - 'tags' => '', - 'created' => new DateTime(), - ]; - $links = [ - 1 => ['id' => 1, 'sticky' => true] + $blank, - 2 => ['id' => 2] + $blank, - ]; - $refDB = new ReferenceLinkDB(); - $refDB->setLinks($links); - $refDB->write(self::$testDatastore); - $linkDB = new LinkDB(self::$testDatastore, true, false); - - $updater = new Updater(array(), $linkDB, $this->conf, true); - $this->assertTrue($updater->updateMethodSetSticky()); - - $linkDB = new LinkDB(self::$testDatastore, true, false); - $this->assertTrue($linkDB[1]['sticky']); - } -} diff --git a/tests/updater/DummyUpdater.php b/tests/updater/DummyUpdater.php new file mode 100644 index 00000000..9e866f1f --- /dev/null +++ b/tests/updater/DummyUpdater.php @@ -0,0 +1,73 @@ +methods = $class->getMethods(ReflectionMethod::IS_FINAL); + } + + /** + * Update method 1. + * + * @return bool true. + */ + final private function updateMethodDummy1() + { + return true; + } + + /** + * Update method 2. + * + * @return bool true. + */ + final private function updateMethodDummy2() + { + return true; + } + + /** + * Update method 3. + * + * @return bool true. + */ + final private function updateMethodDummy3() + { + return true; + } + + /** + * Update method 4, raise an exception. + * + * @throws Exception error. + */ + final private function updateMethodException() + { + throw new Exception('whatever'); + } +} diff --git a/tests/updater/UpdaterTest.php b/tests/updater/UpdaterTest.php new file mode 100644 index 00000000..d7df5963 --- /dev/null +++ b/tests/updater/UpdaterTest.php @@ -0,0 +1,815 @@ +conf = new ConfigManager(self::$configFile); + } + + /** + * Test read_updates_file with an empty/missing file. + */ + public function testReadEmptyUpdatesFile() + { + $this->assertEquals(array(), read_updates_file('')); + $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; + touch($updatesFile); + $this->assertEquals(array(), read_updates_file($updatesFile)); + unlink($updatesFile); + } + + /** + * Test read/write updates file. + */ + public function testReadWriteUpdatesFile() + { + $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; + $updatesMethods = array('m1', 'm2', 'm3'); + + write_updates_file($updatesFile, $updatesMethods); + $readMethods = read_updates_file($updatesFile); + $this->assertEquals($readMethods, $updatesMethods); + + // Update + $updatesMethods[] = 'm4'; + write_updates_file($updatesFile, $updatesMethods); + $readMethods = read_updates_file($updatesFile); + $this->assertEquals($readMethods, $updatesMethods); + unlink($updatesFile); + } + + /** + * Test errors in write_updates_file(): empty updates file. + * + * @expectedException Exception + * @expectedExceptionMessageRegExp /Updates file path is not set(.*)/ + */ + public function testWriteEmptyUpdatesFile() + { + write_updates_file('', array('test')); + } + + /** + * Test errors in write_updates_file(): not writable updates file. + * + * @expectedException Exception + * @expectedExceptionMessageRegExp /Unable to write(.*)/ + */ + public function testWriteUpdatesFileNotWritable() + { + $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; + touch($updatesFile); + chmod($updatesFile, 0444); + try { + @write_updates_file($updatesFile, array('test')); + } catch (Exception $e) { + unlink($updatesFile); + throw $e; + } + } + + /** + * Test the update() method, with no update to run. + * 1. Everything already run. + * 2. User is logged out. + */ + public function testNoUpdates() + { + $updates = array( + 'updateMethodDummy1', + 'updateMethodDummy2', + 'updateMethodDummy3', + 'updateMethodException', + ); + $updater = new DummyUpdater($updates, array(), $this->conf, true); + $this->assertEquals(array(), $updater->update()); + + $updater = new DummyUpdater(array(), array(), $this->conf, false); + $this->assertEquals(array(), $updater->update()); + } + + /** + * Test the update() method, with all updates to run (except the failing one). + */ + public function testUpdatesFirstTime() + { + $updates = array('updateMethodException',); + $expectedUpdates = array( + 'updateMethodDummy1', + 'updateMethodDummy2', + 'updateMethodDummy3', + ); + $updater = new DummyUpdater($updates, array(), $this->conf, true); + $this->assertEquals($expectedUpdates, $updater->update()); + } + + /** + * Test the update() method, only one update to run. + */ + public function testOneUpdate() + { + $updates = array( + 'updateMethodDummy1', + 'updateMethodDummy3', + 'updateMethodException', + ); + $expectedUpdate = array('updateMethodDummy2'); + + $updater = new DummyUpdater($updates, array(), $this->conf, true); + $this->assertEquals($expectedUpdate, $updater->update()); + } + + /** + * Test Update failed. + * + * @expectedException \Exception + */ + public function testUpdateFailed() + { + $updates = array( + 'updateMethodDummy1', + 'updateMethodDummy2', + 'updateMethodDummy3', + ); + + $updater = new DummyUpdater($updates, array(), $this->conf, true); + $updater->update(); + } + + /** + * Test update mergeDeprecatedConfig: + * 1. init a config file. + * 2. init a options.php file with update value. + * 3. merge. + * 4. check updated value in config file. + */ + public function testUpdateMergeDeprecatedConfig() + { + $this->conf->setConfigFile('tests/utils/config/configPhp'); + $this->conf->reset(); + + $optionsFile = 'tests/updater/options.php'; + $options = 'conf->setConfigFile('tests/updater/config'); + + // merge configs + $updater = new Updater(array(), array(), $this->conf, true); + // This writes a new config file in tests/updater/config.php + $updater->updateMethodMergeDeprecatedConfigFile(); + + // make sure updated field is changed + $this->conf->reload(); + $this->assertTrue($this->conf->get('privacy.default_private_links')); + $this->assertFalse(is_file($optionsFile)); + // Delete the generated file. + unlink($this->conf->getConfigFileExt()); + } + + /** + * Test mergeDeprecatedConfig in without options file. + */ + public function testMergeDeprecatedConfigNoFile() + { + $updater = new Updater(array(), array(), $this->conf, true); + $updater->updateMethodMergeDeprecatedConfigFile(); + + $this->assertEquals('root', $this->conf->get('credentials.login')); + } + + /** + * Test renameDashTags update method. + */ + public function testRenameDashTags() + { + $refDB = new \ReferenceLinkDB(); + $refDB->write(self::$testDatastore); + $linkDB = new LinkDB(self::$testDatastore, true, false); + + $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); + $updater = new Updater(array(), $linkDB, $this->conf, true); + $updater->updateMethodRenameDashTags(); + $this->assertNotEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); + } + + /** + * Convert old PHP config file to JSON config. + */ + public function testConfigToJson() + { + $configFile = 'tests/utils/config/configPhp'; + $this->conf->setConfigFile($configFile); + $this->conf->reset(); + + // The ConfigIO is initialized with ConfigPhp. + $this->assertTrue($this->conf->getConfigIO() instanceof ConfigPhp); + + $updater = new Updater(array(), array(), $this->conf, false); + $done = $updater->updateMethodConfigToJson(); + $this->assertTrue($done); + + // The ConfigIO has been updated to ConfigJson. + $this->assertTrue($this->conf->getConfigIO() instanceof ConfigJson); + $this->assertTrue(file_exists($this->conf->getConfigFileExt())); + + // Check JSON config data. + $this->conf->reload(); + $this->assertEquals('root', $this->conf->get('credentials.login')); + $this->assertEquals('lala', $this->conf->get('redirector.url')); + $this->assertEquals('data/datastore.php', $this->conf->get('resource.datastore')); + $this->assertEquals('1', $this->conf->get('plugins.WALLABAG_VERSION')); + + rename($configFile . '.save.php', $configFile . '.php'); + unlink($this->conf->getConfigFileExt()); + } + + /** + * Launch config conversion update with an existing JSON file => nothing to do. + */ + public function testConfigToJsonNothingToDo() + { + $filetime = filemtime($this->conf->getConfigFileExt()); + $updater = new Updater(array(), array(), $this->conf, false); + $done = $updater->updateMethodConfigToJson(); + $this->assertTrue($done); + $expected = filemtime($this->conf->getConfigFileExt()); + $this->assertEquals($expected, $filetime); + } + + /** + * Test escapeUnescapedConfig with valid data. + */ + public function testEscapeConfig() + { + $sandbox = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandbox . '.json.php'); + $this->conf = new ConfigManager($sandbox); + $title = ''; + $headerLink = ''; + $redirectorUrl = ''; + $this->conf->set('general.title', $title); + $this->conf->set('general.header_link', $headerLink); + $this->conf->set('redirector.url', $redirectorUrl); + $updater = new Updater(array(), array(), $this->conf, true); + $done = $updater->updateMethodEscapeUnescapedConfig(); + $this->assertTrue($done); + $this->conf->reload(); + $this->assertEquals(escape($title), $this->conf->get('general.title')); + $this->assertEquals(escape($headerLink), $this->conf->get('general.header_link')); + $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url')); + unlink($sandbox . '.json.php'); + } + + /** + * Test updateMethodApiSettings(): create default settings for the API (enabled + secret). + */ + public function testUpdateApiSettings() + { + $confFile = 'sandbox/config'; + copy(self::$configFile .'.json.php', $confFile .'.json.php'); + $conf = new ConfigManager($confFile); + $updater = new Updater(array(), array(), $conf, true); + + $this->assertFalse($conf->exists('api.enabled')); + $this->assertFalse($conf->exists('api.secret')); + $updater->updateMethodApiSettings(); + $conf->reload(); + $this->assertTrue($conf->get('api.enabled')); + $this->assertTrue($conf->exists('api.secret')); + unlink($confFile .'.json.php'); + } + + /** + * Test updateMethodApiSettings(): already set, do nothing. + */ + public function testUpdateApiSettingsNothingToDo() + { + $confFile = 'sandbox/config'; + copy(self::$configFile .'.json.php', $confFile .'.json.php'); + $conf = new ConfigManager($confFile); + $conf->set('api.enabled', false); + $conf->set('api.secret', ''); + $updater = new Updater(array(), array(), $conf, true); + $updater->updateMethodApiSettings(); + $this->assertFalse($conf->get('api.enabled')); + $this->assertEmpty($conf->get('api.secret')); + unlink($confFile .'.json.php'); + } + + /** + * Test updateMethodDatastoreIds(). + */ + public function testDatastoreIds() + { + $links = array( + '20121206_182539' => array( + 'linkdate' => '20121206_182539', + 'title' => 'Geek and Poke', + 'url' => 'http://geek-and-poke.com/', + 'description' => 'desc', + 'tags' => 'dev cartoon tag1 tag2 tag3 tag4 ', + 'updated' => '20121206_190301', + 'private' => false, + ), + '20121206_172539' => array( + 'linkdate' => '20121206_172539', + 'title' => 'UserFriendly - Samba', + 'url' => 'http://ars.userfriendly.org/cartoons/?id=20010306', + 'description' => '', + 'tags' => 'samba cartoon web', + 'private' => false, + ), + '20121206_142300' => array( + 'linkdate' => '20121206_142300', + 'title' => 'UserFriendly - Web Designer', + 'url' => 'http://ars.userfriendly.org/cartoons/?id=20121206', + 'description' => 'Naming conventions... #private', + 'tags' => 'samba cartoon web', + 'private' => true, + ), + ); + $refDB = new \ReferenceLinkDB(); + $refDB->setLinks($links); + $refDB->write(self::$testDatastore); + $linkDB = new LinkDB(self::$testDatastore, true, false); + + $checksum = hash_file('sha1', self::$testDatastore); + + $this->conf->set('resource.data_dir', 'sandbox'); + $this->conf->set('resource.datastore', self::$testDatastore); + + $updater = new Updater(array(), $linkDB, $this->conf, true); + $this->assertTrue($updater->updateMethodDatastoreIds()); + + $linkDB = new LinkDB(self::$testDatastore, true, false); + + $backup = glob($this->conf->get('resource.data_dir') . '/datastore.'. date('YmdH') .'*.php'); + $backup = $backup[0]; + + $this->assertFileExists($backup); + $this->assertEquals($checksum, hash_file('sha1', $backup)); + unlink($backup); + + $this->assertEquals(3, count($linkDB)); + $this->assertTrue(isset($linkDB[0])); + $this->assertFalse(isset($linkDB[0]['linkdate'])); + $this->assertEquals(0, $linkDB[0]['id']); + $this->assertEquals('UserFriendly - Web Designer', $linkDB[0]['title']); + $this->assertEquals('http://ars.userfriendly.org/cartoons/?id=20121206', $linkDB[0]['url']); + $this->assertEquals('Naming conventions... #private', $linkDB[0]['description']); + $this->assertEquals('samba cartoon web', $linkDB[0]['tags']); + $this->assertTrue($linkDB[0]['private']); + $this->assertEquals( + DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'), + $linkDB[0]['created'] + ); + + $this->assertTrue(isset($linkDB[1])); + $this->assertFalse(isset($linkDB[1]['linkdate'])); + $this->assertEquals(1, $linkDB[1]['id']); + $this->assertEquals('UserFriendly - Samba', $linkDB[1]['title']); + $this->assertEquals( + DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'), + $linkDB[1]['created'] + ); + + $this->assertTrue(isset($linkDB[2])); + $this->assertFalse(isset($linkDB[2]['linkdate'])); + $this->assertEquals(2, $linkDB[2]['id']); + $this->assertEquals('Geek and Poke', $linkDB[2]['title']); + $this->assertEquals( + DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'), + $linkDB[2]['created'] + ); + $this->assertEquals( + DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_190301'), + $linkDB[2]['updated'] + ); + } + + /** + * Test updateMethodDatastoreIds() with the update already applied: nothing to do. + */ + public function testDatastoreIdsNothingToDo() + { + $refDB = new \ReferenceLinkDB(); + $refDB->write(self::$testDatastore); + $linkDB = new LinkDB(self::$testDatastore, true, false); + + $this->conf->set('resource.data_dir', 'sandbox'); + $this->conf->set('resource.datastore', self::$testDatastore); + + $checksum = hash_file('sha1', self::$testDatastore); + $updater = new Updater(array(), $linkDB, $this->conf, true); + $this->assertTrue($updater->updateMethodDatastoreIds()); + $this->assertEquals($checksum, hash_file('sha1', self::$testDatastore)); + } + + /** + * Test defaultTheme update with default settings: nothing to do. + */ + public function testDefaultThemeWithDefaultSettings() + { + $sandbox = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandbox . '.json.php'); + $this->conf = new ConfigManager($sandbox); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodDefaultTheme()); + + $this->assertEquals('tpl/', $this->conf->get('resource.raintpl_tpl')); + $this->assertEquals('default', $this->conf->get('resource.theme')); + $this->conf = new ConfigManager($sandbox); + $this->assertEquals('tpl/', $this->conf->get('resource.raintpl_tpl')); + $this->assertEquals('default', $this->conf->get('resource.theme')); + unlink($sandbox . '.json.php'); + } + + /** + * Test defaultTheme update with a custom theme in a subfolder + */ + public function testDefaultThemeWithCustomTheme() + { + $theme = 'iamanartist'; + $sandbox = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandbox . '.json.php'); + $this->conf = new ConfigManager($sandbox); + mkdir('sandbox/'. $theme); + touch('sandbox/'. $theme .'/linklist.html'); + $this->conf->set('resource.raintpl_tpl', 'sandbox/'. $theme .'/'); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodDefaultTheme()); + + $this->assertEquals('sandbox', $this->conf->get('resource.raintpl_tpl')); + $this->assertEquals($theme, $this->conf->get('resource.theme')); + $this->conf = new ConfigManager($sandbox); + $this->assertEquals('sandbox', $this->conf->get('resource.raintpl_tpl')); + $this->assertEquals($theme, $this->conf->get('resource.theme')); + unlink($sandbox . '.json.php'); + unlink('sandbox/'. $theme .'/linklist.html'); + rmdir('sandbox/'. $theme); + } + + /** + * Test updateMethodEscapeMarkdown with markdown plugin enabled + * => setting markdown_escape set to false. + */ + public function testEscapeMarkdownSettingToFalse() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + + $this->conf->set('general.enabled_plugins', ['markdown']); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodEscapeMarkdown()); + $this->assertFalse($this->conf->get('security.markdown_escape')); + + // reload from file + $this->conf = new ConfigManager($sandboxConf); + $this->assertFalse($this->conf->get('security.markdown_escape')); + } + + + /** + * Test updateMethodEscapeMarkdown with markdown plugin disabled + * => setting markdown_escape set to true. + */ + public function testEscapeMarkdownSettingToTrue() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + + $this->conf->set('general.enabled_plugins', []); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodEscapeMarkdown()); + $this->assertTrue($this->conf->get('security.markdown_escape')); + + // reload from file + $this->conf = new ConfigManager($sandboxConf); + $this->assertTrue($this->conf->get('security.markdown_escape')); + } + + /** + * Test updateMethodEscapeMarkdown with nothing to do (setting already enabled) + */ + public function testEscapeMarkdownSettingNothingToDoEnabled() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + $this->conf->set('security.markdown_escape', true); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodEscapeMarkdown()); + $this->assertTrue($this->conf->get('security.markdown_escape')); + } + + /** + * Test updateMethodEscapeMarkdown with nothing to do (setting already disabled) + */ + public function testEscapeMarkdownSettingNothingToDoDisabled() + { + $this->conf->set('security.markdown_escape', false); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodEscapeMarkdown()); + $this->assertFalse($this->conf->get('security.markdown_escape')); + } + + /** + * Test updateMethodPiwikUrl with valid data + */ + public function testUpdatePiwikUrlValid() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + $url = 'mypiwik.tld'; + $this->conf->set('plugins.PIWIK_URL', $url); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodPiwikUrl()); + $this->assertEquals('http://'. $url, $this->conf->get('plugins.PIWIK_URL')); + + // reload from file + $this->conf = new ConfigManager($sandboxConf); + $this->assertEquals('http://'. $url, $this->conf->get('plugins.PIWIK_URL')); + } + + /** + * Test updateMethodPiwikUrl without setting + */ + public function testUpdatePiwikUrlEmpty() + { + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodPiwikUrl()); + $this->assertEmpty($this->conf->get('plugins.PIWIK_URL')); + } + + /** + * Test updateMethodPiwikUrl: valid URL, nothing to do + */ + public function testUpdatePiwikUrlNothingToDo() + { + $url = 'https://mypiwik.tld'; + $this->conf->set('plugins.PIWIK_URL', $url); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodPiwikUrl()); + $this->assertEquals($url, $this->conf->get('plugins.PIWIK_URL')); + } + + /** + * Test updateMethodAtomDefault with show_atom set to false + * => update to true. + */ + public function testUpdateMethodAtomDefault() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + $this->conf->set('feed.show_atom', false); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodAtomDefault()); + $this->assertTrue($this->conf->get('feed.show_atom')); + // reload from file + $this->conf = new ConfigManager($sandboxConf); + $this->assertTrue($this->conf->get('feed.show_atom')); + } + /** + * Test updateMethodAtomDefault with show_atom not set. + * => nothing to do + */ + public function testUpdateMethodAtomDefaultNoExist() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodAtomDefault()); + $this->assertTrue($this->conf->get('feed.show_atom')); + } + /** + * Test updateMethodAtomDefault with show_atom set to true. + * => nothing to do + */ + public function testUpdateMethodAtomDefaultAlreadyTrue() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + $this->conf->set('feed.show_atom', true); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodAtomDefault()); + $this->assertTrue($this->conf->get('feed.show_atom')); + } + + /** + * Test updateMethodDownloadSizeAndTimeoutConf, it should be set if none is already defined. + */ + public function testUpdateMethodDownloadSizeAndTimeoutConf() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); + $this->assertEquals(4194304, $this->conf->get('general.download_max_size')); + $this->assertEquals(30, $this->conf->get('general.download_timeout')); + + $this->conf = new ConfigManager($sandboxConf); + $this->assertEquals(4194304, $this->conf->get('general.download_max_size')); + $this->assertEquals(30, $this->conf->get('general.download_timeout')); + } + + /** + * Test updateMethodDownloadSizeAndTimeoutConf, it shouldn't be set if it is already defined. + */ + public function testUpdateMethodDownloadSizeAndTimeoutConfIgnore() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + $this->conf->set('general.download_max_size', 38); + $this->conf->set('general.download_timeout', 70); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); + $this->assertEquals(38, $this->conf->get('general.download_max_size')); + $this->assertEquals(70, $this->conf->get('general.download_timeout')); + } + + /** + * Test updateMethodDownloadSizeAndTimeoutConf, only the maz size should be set here. + */ + public function testUpdateMethodDownloadSizeAndTimeoutConfOnlySize() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + $this->conf->set('general.download_max_size', 38); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); + $this->assertEquals(38, $this->conf->get('general.download_max_size')); + $this->assertEquals(30, $this->conf->get('general.download_timeout')); + } + + /** + * Test updateMethodDownloadSizeAndTimeoutConf, only the time out should be set here. + */ + public function testUpdateMethodDownloadSizeAndTimeoutConfOnlyTimeout() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + $this->conf->set('general.download_timeout', 3); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); + $this->assertEquals(4194304, $this->conf->get('general.download_max_size')); + $this->assertEquals(3, $this->conf->get('general.download_timeout')); + } + + /** +<<<<<<< HEAD + * Test updateMethodWebThumbnailer with thumbnails enabled. + */ + public function testUpdateMethodWebThumbnailerEnabled() + { + $this->conf->remove('thumbnails'); + $this->conf->set('thumbnail.enable_thumbnails', true); + $updater = new Updater([], [], $this->conf, true, $_SESSION); + $this->assertTrue($updater->updateMethodWebThumbnailer()); + $this->assertFalse($this->conf->exists('thumbnail')); + $this->assertEquals(\Shaarli\Thumbnailer::MODE_ALL, $this->conf->get('thumbnails.mode')); + $this->assertEquals(125, $this->conf->get('thumbnails.width')); + $this->assertEquals(90, $this->conf->get('thumbnails.height')); + $this->assertContains('You have enabled or changed thumbnails', $_SESSION['warnings'][0]); + } + + /** + * Test updateMethodWebThumbnailer with thumbnails disabled. + */ + public function testUpdateMethodWebThumbnailerDisabled() + { + $this->conf->remove('thumbnails'); + $this->conf->set('thumbnail.enable_thumbnails', false); + $updater = new Updater([], [], $this->conf, true, $_SESSION); + $this->assertTrue($updater->updateMethodWebThumbnailer()); + $this->assertFalse($this->conf->exists('thumbnail')); + $this->assertEquals(Thumbnailer::MODE_NONE, $this->conf->get('thumbnails.mode')); + $this->assertEquals(125, $this->conf->get('thumbnails.width')); + $this->assertEquals(90, $this->conf->get('thumbnails.height')); + $this->assertTrue(empty($_SESSION['warnings'])); + } + + /** + * Test updateMethodWebThumbnailer with thumbnails disabled. + */ + public function testUpdateMethodWebThumbnailerNothingToDo() + { + $updater = new Updater([], [], $this->conf, true, $_SESSION); + $this->assertTrue($updater->updateMethodWebThumbnailer()); + $this->assertFalse($this->conf->exists('thumbnail')); + $this->assertEquals(Thumbnailer::MODE_COMMON, $this->conf->get('thumbnails.mode')); + $this->assertEquals(90, $this->conf->get('thumbnails.width')); + $this->assertEquals(53, $this->conf->get('thumbnails.height')); + $this->assertTrue(empty($_SESSION['warnings'])); + } + + /** + * Test updateMethodSetSticky(). + */ + public function testUpdateStickyValid() + { + $blank = [ + 'id' => 1, + 'url' => 'z', + 'title' => '', + 'description' => '', + 'tags' => '', + 'created' => new DateTime(), + ]; + $links = [ + 1 => ['id' => 1] + $blank, + 2 => ['id' => 2] + $blank, + ]; + $refDB = new \ReferenceLinkDB(); + $refDB->setLinks($links); + $refDB->write(self::$testDatastore); + $linkDB = new LinkDB(self::$testDatastore, true, false); + + $updater = new Updater(array(), $linkDB, $this->conf, true); + $this->assertTrue($updater->updateMethodSetSticky()); + + $linkDB = new LinkDB(self::$testDatastore, true, false); + foreach ($linkDB as $link) { + $this->assertFalse($link['sticky']); + } + } + + /** + * Test updateMethodSetSticky(). + */ + public function testUpdateStickyNothingToDo() + { + $blank = [ + 'id' => 1, + 'url' => 'z', + 'title' => '', + 'description' => '', + 'tags' => '', + 'created' => new DateTime(), + ]; + $links = [ + 1 => ['id' => 1, 'sticky' => true] + $blank, + 2 => ['id' => 2] + $blank, + ]; + $refDB = new \ReferenceLinkDB(); + $refDB->setLinks($links); + $refDB->write(self::$testDatastore); + $linkDB = new LinkDB(self::$testDatastore, true, false); + + $updater = new Updater(array(), $linkDB, $this->conf, true); + $this->assertTrue($updater->updateMethodSetSticky()); + + $linkDB = new LinkDB(self::$testDatastore, true, false); + $this->assertTrue($linkDB[1]['sticky']); + } +} diff --git a/tests/utils/config/configPhp.php b/tests/utils/config/configPhp.php index 34b11fcd..7dc81e22 100644 --- a/tests/utils/config/configPhp.php +++ b/tests/utils/config/configPhp.php @@ -8,7 +8,7 @@ $GLOBALS['titleLink'] = 'titleLink'; $GLOBALS['redirector'] = 'lala'; $GLOBALS['disablesessionprotection'] = false; $GLOBALS['privateLinkByDefault'] = false; -$GLOBALS['config']['DATADIR'] = 'tests/Updater'; +$GLOBALS['config']['DATADIR'] = 'tests/updater'; $GLOBALS['config']['PAGECACHE'] = 'sandbox/pagecache'; $GLOBALS['config']['DATASTORE'] = 'data/datastore.php'; $GLOBALS['plugins']['WALLABAG_VERSION'] = '1'; -- cgit v1.2.3