diff --git a/src/mibew/configs/routing.yml b/src/mibew/configs/routing.yml index a583da5d..713490c6 100644 --- a/src/mibew/configs/routing.yml +++ b/src/mibew/configs/routing.yml @@ -678,6 +678,20 @@ translations: _access_permissions: [CAN_ADMINISTRATE] ## Updates +update: + path: /update + defaults: + _controller: Mibew\Controller\UpdateController::indexAction + _access_check: Mibew\AccessControl\Check\PermissionsCheck + _access_permissions: [CAN_ADMINISTRATE] + +update_run: + path: /update/run + defaults: + _controller: Mibew\Controller\UpdateController::runUpdateAction + _access_check: Mibew\AccessControl\Check\PermissionsCheck + _access_permissions: [CAN_ADMINISTRATE] + updates: path: /operator/updates defaults: diff --git a/src/mibew/libs/classes/Mibew/Controller/HomeController.php b/src/mibew/libs/classes/Mibew/Controller/HomeController.php index 8c97c429..23be72a2 100644 --- a/src/mibew/libs/classes/Mibew/Controller/HomeController.php +++ b/src/mibew/libs/classes/Mibew/Controller/HomeController.php @@ -52,11 +52,11 @@ class HomeController extends AbstractController $page = array( 'version' => MIBEW_VERSION, 'localeLinks' => get_locale_links(), - 'needUpdate' => Settings::get('dbversion') != DB_VERSION, + 'needUpdate' => version_compare(Settings::get('dbversion'), DB_VERSION, '<'), 'profilePage' => $this->generateUrl('operator_edit', array('operator_id' => $operator['operatorid'])), // Use another entry point as an install URL. // TODO: Use real update route when the System Updater will be ready - 'updateWizard' => $request->getBasePath() . '/install.php', + 'updateWizard' => $this->generateUrl('update'), 'newFeatures' => Settings::get('featuresversion') != FEATURES_VERSION, 'featuresPage' => $this->generateUrl('settings_features'), 'isOnline' => $is_online, diff --git a/src/mibew/libs/classes/Mibew/Controller/UpdateController.php b/src/mibew/libs/classes/Mibew/Controller/UpdateController.php new file mode 100644 index 00000000..74e6279a --- /dev/null +++ b/src/mibew/libs/classes/Mibew/Controller/UpdateController.php @@ -0,0 +1,102 @@ + MIBEW_VERSION, + 'fixedwrap' => true, + 'title' => getlocal('Update'), + ); + + return $this->render('update_intro', $parameters); + } + + /** + * Runs the Updater. + * + * @param Request $request Incoming request. + * @return Response|string Rendered page contents or Symfony's response + * object. + */ + public function runUpdateAction(Request $request) + { + $upd = $this->getUpdater(); + $upd->run(); + + $parameters = array( + 'version' => MIBEW_VERSION, + 'fixedwrap' => true, + 'title' => getlocal('Update'), + 'done' => $this->getUpdater()->getLog(), + 'errors' => $this->getUpdater()->getErrors(), + ); + + return $this->render('update_progress', $parameters); + } + + /** + * {@inheritdoc} + */ + protected function getStyle() + { + if (is_null($this->style)) { + $this->style = $this->prepareStyle(new PageStyle('default')); + } + + return $this->style; + } + + /** + * Returns an instance of Updater. + * + * @return Updater + */ + protected function getUpdater() + { + if (is_null($this->updater)) { + $this->updater = new Updater(); + } + + return $this->updater; + } +} diff --git a/src/mibew/libs/classes/Mibew/Maintenance/Installer.php b/src/mibew/libs/classes/Mibew/Maintenance/Installer.php index 879c1174..5c3d61d9 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/Installer.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/Installer.php @@ -563,7 +563,7 @@ class Installer * * If Mibew is not installed yet boolean false will be returned. * - * @return int|boolean Database structure version or boolean false if the + * @return string|boolean Database structure version or boolean false if the * version cannot be determined. */ protected function getDatabaseVersion() @@ -585,10 +585,10 @@ class Installer if (!$result) { // It seems that database structure version isn't stored in the // database. - return 0; + return '0.0.0'; } - return intval($result['version']); + return $result['version']; } /** @@ -598,7 +598,7 @@ class Installer */ protected function tablesNeedUpdate() { - return ($this->getDatabaseVersion() < DB_VERSION); + return version_compare($this->getDatabaseVersion(), DB_VERSION, '<'); } /** diff --git a/src/mibew/libs/classes/Mibew/Maintenance/Updater.php b/src/mibew/libs/classes/Mibew/Maintenance/Updater.php new file mode 100644 index 00000000..9932d23d --- /dev/null +++ b/src/mibew/libs/classes/Mibew/Maintenance/Updater.php @@ -0,0 +1,271 @@ +errors; + } + + /** + * Returns list of all information messages. + * + * @return string[] + */ + public function getLog() + { + return $this->log; + } + + /** + * Performs all actions that are needed to update database structure. + * + * @return boolean True if the system updated successfully and false + * otherwise. + */ + public function run() + { + $current_version = $this->getDatabaseVersion(); + + if (!preg_match("/^([0-9]{1,2}\.){2}[0-9]{1,2}(-beta\.[0-9]+)?$/", $current_version)) { + $this->errors[] = getlocal( + 'The current version ({0}) is unknown or wrong formated', + array($current_version) + ); + + return false; + } + + if (version_compare($current_version, self::MIN_VERSION) < 0) { + $this->errors[] = getlocal( + 'You can update the system only from {0} and later versions. The current version is {1}', + array( + self::MIN_VERSION, + $current_version + ) + ); + + return false; + } + + // Get list of all available updates + $updates = $this->getUpdates(); + + // Check if updates should be performed + $versions = array_keys($updates); + $last_version = end($versions); + if (version_compare($current_version, $last_version) >= 0) { + $this->log[] = getlocal('The database is already up to date'); + + return true; + } + + try { + // Perform incremental updates + foreach ($updates as $version => $method) { + if (version_compare($version, $current_version) <= 0) { + // Skip updates to lower versions. + continue; + } + + // Run the update + if (!$this->{$method}()) { + $this->errors[] = getlocal('Cannot update to {0}', array($version)); + + return false; + } + + // Store new version number in the database. With this info + // we can rerun the updating process if one of pending + // updates fails. + if (!$this->setDatabaseVersion($version)) { + $this->errors[] = getlocal('Cannot store new version number'); + + return false; + } else { + $this->log[] = getlocal('Updated to {0}', array($version)); + } + + $current_version = $version; + } + } catch (\Exception $e) { + // Something went wrong + $this->errors[] = getlocal( + 'Update failed: {0}', + array($e->getMessage()) + ); + + return false; + } + + return true; + } + + /** + * Returns initialized database object. + * + * @return \Mibew\Database|boolean A database class instance or boolean + * false if something went wrong. + */ + protected function getDatabase() + { + try { + $db = Database::getInstance(); + $db->throwExeptions(true); + + return $db; + } catch (\Exception $e) { + $this->errors[] = getlocal( + "Could not retrieve database instance. Error: {0}", + array($e->getMessage()) + ); + + return false; + } + } + + /** + * Gets version of existing database structure. + * + * If Mibew is not installed yet boolean false will be returned. + * + * @return int|boolean Database structure version or boolean false if the + * version cannot be determined. + */ + protected function getDatabaseVersion() + { + if (!($db = $this->getDatabase())) { + return false; + } + + try { + $result = $db->query( + "SELECT vcvalue AS version FROM {config} WHERE vckey = :key LIMIT 1", + array(':key' => 'dbversion'), + array('return_rows' => Database::RETURN_ONE_ROW) + ); + } catch (\Exception $e) { + return false; + } + + if (!$result) { + // It seems that database structure version isn't stored in the + // database. + return '0.0.0'; + } + + return $result['version']; + } + + /** + * Updates version of database tables tructure. + * + * @param string $version Current version + * @return boolean True if the version is set and false otherwise. + */ + protected function setDatabaseVersion($version) + { + if (!($db = $this->getDatabase())) { + return false; + } + + try { + return $db->query( + 'UPDATE {config} SET vcvalue = :version WHERE vckey = :key LIMIT 1', + array( + ':version' => $version, + ':key' => 'dbversion', + ) + ); + } catch (\Exception $e) { + // The query fails by some reason. + return false; + } + } + + /** + * Gets list of all available updates. + * + * @return array The keys of this array are version numbers and values are + * methods of the {@link \Mibew\Maintenance\Updater} class that should be + * performed. + */ + protected function getUpdates() + { + $updates = array(); + + $self_reflection = new \ReflectionClass($this); + foreach ($self_reflection->getMethods() as $method_reflection) { + // Filter update methods + $name = $method_reflection->getName(); + if (preg_match("/^update([0-9]+)(?:Beta([0-9]+))?$/", $name, $matches)) { + $version = Utils::formatVersionId($matches[1]); + // Check if a beta version is defined. + if (!empty($matches[2])) { + $version .= '-beta.' . $matches[2]; + } + + $updates[$version] = $name; + } + } + + uksort($updates, 'version_compare'); + + return $updates; + } +} diff --git a/src/mibew/libs/common/constants.php b/src/mibew/libs/common/constants.php index 3587a7ab..b31c34a5 100644 --- a/src/mibew/libs/common/constants.php +++ b/src/mibew/libs/common/constants.php @@ -20,17 +20,17 @@ /** * Current version of Mibew Messenger */ -define('MIBEW_VERSION', '2.0'); +define('MIBEW_VERSION', '2.0.0-beta.1'); /** * Current version of database structure */ -define('DB_VERSION', 20000); +define('DB_VERSION', '2.0.0-beta.1'); /** * Current version of implemented features */ -define('FEATURES_VERSION', '2.0'); +define('FEATURES_VERSION', '2.0.0-beta.1'); /** * Prefix for session variables. diff --git a/src/mibew/styles/pages/default/css/default.css b/src/mibew/styles/pages/default/css/default.css index bf5901eb..2e70734c 100644 --- a/src/mibew/styles/pages/default/css/default.css +++ b/src/mibew/styles/pages/default/css/default.css @@ -907,7 +907,7 @@ table.awaiting .no-threads, table.awaiting .no-visitors { z-index:101; } -/* install */ +/* install/update */ #install li { list-style-type: circle; @@ -933,6 +933,10 @@ table.awaiting .no-threads, table.awaiting .no-visitors { font-weight: bold; } +#install .error { + color: #c13030; +} + /* chat */ .message { diff --git a/src/mibew/styles/pages/default/templates_src/server_side/update_intro.handlebars b/src/mibew/styles/pages/default/templates_src/server_side/update_intro.handlebars new file mode 100644 index 00000000..589a4355 --- /dev/null +++ b/src/mibew/styles/pages/default/templates_src/server_side/update_intro.handlebars @@ -0,0 +1,29 @@ +{{#extends "_layout"}} + {{#override "content"}} + {{l10n "Follow the wizard to update your database."}} + +
+
+
+
+
+
+
+ +
+
    +
  1. {{l10n "Backup the database"}}
  2. +
  3. {{l10n "Backup the code"}}
  4. +
  5. {{l10n "Replace all files with ones from the new version"}}
  6. +
  7. {{l10n "Update configs file if needed"}}
  8. +
  9. {{l10n "Run the update wizard" (route "update_run")}}
  10. +
+
+ +
+
+
+
+
+ {{/override}} +{{/extends}} \ No newline at end of file diff --git a/src/mibew/styles/pages/default/templates_src/server_side/update_progress.handlebars b/src/mibew/styles/pages/default/templates_src/server_side/update_progress.handlebars new file mode 100644 index 00000000..a9cfb07c --- /dev/null +++ b/src/mibew/styles/pages/default/templates_src/server_side/update_progress.handlebars @@ -0,0 +1,50 @@ +{{#extends "_layout"}} + {{#if localeLinks}} + {{#override "menu"}}{{> _locales}}{{/override}} + {{/if}} + + {{#override "content"}} + {{l10n "Follow the wizard to update your database."}} + +
+
+ +
+
+
+
+
+ +
+ {{#ifAny done errors}} + {{l10n "Progress:"}} +
    + {{#each done}} +
  • {{{this}}}
  • + {{/each}} + {{#each errors}} +
  • {{{this}}}
  • + {{/each}} +
+
+ {{/ifAny}} + + {{#if errors}} + {{l10n "Update failed."}}
+ {{l10n "You can try to restore database from the backup and run the update wizard again."}} + {{else}} + {{l10n "Application successfully updated."}} {{l10n "Go to home page" (route "home")}} + {{/if}} +
+ +
+
+
+
+
+ + {{#if errors}} + + {{/if}} + {{/override}} +{{/extends}} \ No newline at end of file