<?php
+use Shaarli\Config\ConfigJson;
+use Shaarli\Config\ConfigPhp;
+use Shaarli\Config\ConfigManager;
/**
* Class Updater.
*/
protected $linkDB;
+ /**
+ * @var ConfigManager $conf Configuration Manager instance.
+ */
+ protected $conf;
+
/**
* @var bool True if the user is logged in, false otherwise.
*/
/**
* Object constructor.
*
- * @param array $doneUpdates Updates which are already done.
- * @param LinkDB $linkDB LinkDB instance.
- * @param boolean $isLoggedIn True if the user is logged in.
+ * @param array $doneUpdates Updates which are already done.
+ * @param LinkDB $linkDB LinkDB instance.
+ * @param ConfigManager $conf Configuration Manager instance.
+ * @param boolean $isLoggedIn True if the user is logged in.
*/
- public function __construct($doneUpdates, $linkDB, $isLoggedIn)
+ public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn)
{
$this->doneUpdates = $doneUpdates;
$this->linkDB = $linkDB;
+ $this->conf = $conf;
$this->isLoggedIn = $isLoggedIn;
// Retrieve all update methods.
return $updatesRan;
}
- if ($this->methods == null) {
- throw new UpdaterException('Couldn\'t retrieve Updater class methods.');
+ if ($this->methods === null) {
+ throw new UpdaterException(t('Couldn\'t retrieve Updater class methods.'));
}
foreach ($this->methods as $method) {
*/
public function updateMethodMergeDeprecatedConfigFile()
{
- $conf = ConfigManager::getInstance();
-
- if (is_file($conf->get('path.data_dir') . '/options.php')) {
- include $conf->get('path.data_dir') . '/options.php';
+ 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)) {
- $conf->set($key, $value);
+ $this->conf->set($key, $value);
}
}
- $conf->write($this->isLoggedIn);
- unlink($conf->get('path.data_dir').'/options.php');
+ $this->conf->write($this->isLoggedIn);
+ unlink($this->conf->get('resource.data_dir').'/options.php');
}
return true;
}
- /**
- * Rename tags starting with a '-' to work with tag exclusion search.
- */
- public function updateMethodRenameDashTags()
- {
- $conf = ConfigManager::getInstance();
- $linklist = $this->linkDB->filterSearch();
- foreach ($linklist as $link) {
- $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
- $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
- $this->linkDB[$link['linkdate']] = $link;
- }
- $this->linkDB->savedb($conf->get('path.page_cache'));
- return true;
- }
-
/**
* Move old configuration in PHP to the new config system in JSON format.
*
*/
public function updateMethodConfigToJson()
{
- $conf = ConfigManager::getInstance();
-
// JSON config already exists, nothing to do.
- if ($conf->getConfigIO() instanceof ConfigJson) {
+ if ($this->conf->getConfigIO() instanceof ConfigJson) {
return true;
}
$configPhp = new ConfigPhp();
$configJson = new ConfigJson();
- $oldConfig = $configPhp->read($conf::$CONFIG_FILE . '.php');
- rename($conf->getConfigFile(), $conf::$CONFIG_FILE . '.save.php');
- $conf->setConfigIO($configJson);
- $conf->reload();
+ $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) {
- $conf->set($legacyMap[$key], $oldConfig[$key]);
+ $this->conf->set($legacyMap[$key], $oldConfig[$key]);
}
// Set sub config keys (config and plugins)
} else {
$configKey = $sub .'.'. $key;
}
- $conf->set($configKey, $value);
+ $this->conf->set($configKey, $value);
}
}
try{
- $conf->write($this->isLoggedIn);
+ $this->conf->write($this->isLoggedIn);
return true;
} catch (IOException $e) {
error_log($e->getMessage());
* Escape settings which have been manually escaped in every request in previous versions:
* - general.title
* - general.header_link
- * - extras.redirector
+ * - redirector.url
*
* @return bool true if the update is successful, false otherwise.
*/
- public function escapeUnescapedConfig()
+ public function updateMethodEscapeUnescapedConfig()
{
- $conf = ConfigManager::getInstance();
try {
- $conf->set('general.title', escape($conf->get('general.title')));
- $conf->set('general.header_link', escape($conf->get('general.header_link')));
- $conf->set('extras.redirector', escape($conf->get('extras.redirector')));
- $conf->write($this->isLoggedIn);
+ $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.<datetime>.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'));
+ }
}
/**
}
if (! empty($this->method)) {
- $out .= 'An error occurred while running the update '. $this->method . PHP_EOL;
+ $out .= t('An error occurred while running the update ') . $this->method . PHP_EOL;
}
if (! empty($this->previous)) {
function write_updates_file($updatesFilepath, $updates)
{
if (empty($updatesFilepath)) {
- throw new Exception('Updates file path is not set, can\'t write updates.');
+ 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('Unable to write updates in '. $updatesFilepath . '.');
+ throw new Exception(t('Unable to write updates in '. $updatesFilepath . '.'));
}
}