From 577c2be622b10eadeea0418d73b953a5ca7ad720 Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Tue, 26 May 2015 13:19:13 +0000 Subject: [PATCH 01/14] Create a way to automatically get info about available updates --- README.md | 2 +- src/mibew/README.txt | 2 +- src/mibew/configs/database_schema.yml | 16 + .../Mibew/Maintenance/AvailableUpdate.php | 253 ++++++++++++++ .../classes/Mibew/Maintenance/CronWorker.php | 27 +- .../classes/Mibew/Maintenance/Installer.php | 2 +- .../Mibew/Maintenance/UpdateChecker.php | 328 ++++++++++++++++++ .../classes/Mibew/Maintenance/Updater.php | 12 +- 8 files changed, 635 insertions(+), 7 deletions(-) create mode 100644 src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php create mode 100644 src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php diff --git a/README.md b/README.md index dee16a2d..2a8964f9 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This repository contains the core of Mibew Messenger application. ## Server requirements 1. A webserver or web hosting account running on any major Operating System -2. PHP (5.3.3 and above) with PDO, pdo_mysql and gd extensions +2. PHP (5.3.3 and above) with PDO, pdo_mysql, cURL and gd extensions 3. MySQL 5.0 and above ## Build from sources diff --git a/src/mibew/README.txt b/src/mibew/README.txt index 0452dd06..ed0f5292 100644 --- a/src/mibew/README.txt +++ b/src/mibew/README.txt @@ -6,7 +6,7 @@ REQUIREMENTS * Apache web server 1.3.34 or above with the ability to use local .htaccess files (mod_rewrite module is optional, but recommended) * MySQL database 5.0 or above - * PHP 5.3.3 or above with PDO, pdo_mysql and gd extensions + * PHP 5.3.3 or above with PDO, pdo_mysql, cURL and gd extensions INSTALLATION diff --git a/src/mibew/configs/database_schema.yml b/src/mibew/configs/database_schema.yml index 160c1fea..98d9ef7b 100644 --- a/src/mibew/configs/database_schema.yml +++ b/src/mibew/configs/database_schema.yml @@ -316,3 +316,19 @@ plugin: enabled: "tinyint NOT NULL DEFAULT 0" unique_keys: name: [name] + +# Contains info about all available updates +available_update: + field: + # Artificial ID + id: "INT NOT NULL auto_increment PRIMARY KEY" + # Can be either "core" or fully qualified plugin's name + target: "varchar(255) NOT NULL" + # The latest available version of the plugin + version: "varchar(255) NOT NULL" + # A URL where the new version can be downloaded + url: "text" + # Description of the update + description: "text" + unique_keys: + target: [target] diff --git a/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php b/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php new file mode 100644 index 00000000..27789251 --- /dev/null +++ b/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php @@ -0,0 +1,253 @@ +query( + "SELECT * FROM {available_update} WHERE id = :id", + array(':id' => $id), + array('return_rows' => Database::RETURN_ONE_ROW) + ); + + // There is no update with such id in database + if (!$info) { + return false; + } + + // Create and populate update object + $update = new self(); + $update->populateFromDbFields($info); + + return $update; + } + + /** + * Loads update by its target. + * + * @param string $target Target of the update to load. + * @return boolean|AvailableUpdate Returns an AvailableUpdate instance or + * boolean false on failure. + */ + public static function loadByTarget($target) + { + // Check the target + if (empty($target)) { + return false; + } + + // Load update info + $info = Database::getInstance()->query( + "SELECT * FROM {available_update} WHERE target = :target", + array(':target' => $target), + array('return_rows' => Database::RETURN_ONE_ROW) + ); + + // There is no update with such target in database + if (!$info) { + return false; + } + + // Create and populate update object + $update = new self(); + $update->populateFromDbFields($info); + + return $update; + } + + /** + * Loads available updates. + * + * @return array List of AvailableUpdate instances. + * + * @throws \RuntimeException If something went wrong and the list could not + * be loaded. + */ + public static function all() + { + $rows = Database::getInstance()->query( + "SELECT * FROM {available_updates}", + null, + array('return_rows' => Database::RETURN_ALL_ROWS) + ); + + if ($rows === false) { + throw new \RuntimeException('List of available updates cannot be retrieved.'); + } + + $updates = array(); + foreach ($rows as $item) { + $update = new self(); + $update->populateFromDbFields($item); + $updates[] = $update; + } + + return $updates; + } + + /** + * Class constructor. + */ + public function __construct() + { + // Set default values + $this->id = false; + $this->target = null; + $this->version = null; + $this->url = ''; + $this->description = ''; + } + + /** + * Remove record about available update from the database. + */ + public function delete() + { + if (!$this->id) { + throw new \RuntimeException('You cannot delete an update without id'); + } + + Database::getInstance()->query( + "DELETE FROM {available_update} WHERE id = :id LIMIT 1", + array(':id' => $this->id) + ); + } + + /** + * Save the update to the database. + */ + public function save() + { + $db = Database::getInstance(); + + if (!$this->target) { + throw new \RuntimeException('Update\'s target was not set'); + } + + if (!$this->url) { + throw new \RuntimeException('Update\'s URL was not set'); + } + + if (!$this->version) { + throw new \RuntimeException('Update\'s version was not set'); + } + + if (!$this->id) { + // This update is new. + $db->query( + ("INSERT INTO {available_update} (target, version, url, description) " + . "VALUES (:target, :version, :url, :description)"), + array( + ':target' => $this->target, + ':version' => $this->version, + ':url' => $this->url, + ':description' => $this->description, + ) + ); + $this->id = $db->insertedId(); + } else { + // Update existing update + $db->query( + ("UPDATE {available_update} SET target = :target, url = :url, " + . "version = :version, description = :description " + . "WHERE id = :id"), + array( + ':id' => $this->id, + ':target' => $this->target, + ':version' => $this->version, + ':url' => $this->url, + ':description' => $this->description, + ) + ); + } + } + + /** + * Sets update's fields according to the fields from Database. + * + * @param array $db_fields Associative array of database fields which keys + * are fields names and the values are fields values. + */ + protected function populateFromDbFields($db_fields) + { + $this->id = $db_fields['id']; + $this->target = $db_fields['target']; + $this->version = $db_fields['version']; + $this->url = $db_fields['url']; + $this->description = $db_fields['description']; + } +} diff --git a/src/mibew/libs/classes/Mibew/Maintenance/CronWorker.php b/src/mibew/libs/classes/Mibew/Maintenance/CronWorker.php index 423ddf67..55a6b405 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/CronWorker.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/CronWorker.php @@ -36,6 +36,13 @@ class CronWorker */ protected $cache = null; + /** + * An instance of update checker. + * + * @var UpdateChecker|null + */ + protected $updateChecker = null; + /** * List of errors. * @@ -54,10 +61,14 @@ class CronWorker * Class constructor. * * @param PoolInterface $cache An instance of cache pool. + * @param UpdateChecker $update_checker An instance of update checker. */ - public function __construct(PoolInterface $cache) + public function __construct(PoolInterface $cache, UpdateChecker $update_checker = null) { $this->cache = $cache; + $this->updateChecker = is_null($update_checker) + ? new UpdateChecker() + : $update_checker; } /** @@ -71,6 +82,9 @@ class CronWorker try { set_time_limit(0); + // Update time of last cron run + Settings::set('_last_cron_run', time()); + // Remove stale cached items $this->cache->purge(); @@ -83,8 +97,15 @@ class CronWorker $dispatcher = EventDispatcher::getInstance(); $dispatcher->triggerEvent(Events::CRON_RUN); - // Update time of last cron run - Settings::set('_last_cron_run', time()); + // Run the update checker + if (!$this->updateChecker->run()) { + $this->errors = array_merge( + $this->errors, + $this->updateChecker->getErrors() + ); + + return false; + } } catch (\Exception $e) { $this->log[] = $e->getMessage(); diff --git a/src/mibew/libs/classes/Mibew/Maintenance/Installer.php b/src/mibew/libs/classes/Mibew/Maintenance/Installer.php index 773bbfd7..cb0d8625 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/Installer.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/Installer.php @@ -489,7 +489,7 @@ class Installer */ protected function checkPhpExtensions() { - $extensions = array('PDO', 'pdo_mysql', 'gd'); + $extensions = array('PDO', 'pdo_mysql', 'gd', 'curl'); foreach ($extensions as $ext) { if (!extension_loaded($ext)) { diff --git a/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php b/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php new file mode 100644 index 00000000..d7ac0a4e --- /dev/null +++ b/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php @@ -0,0 +1,328 @@ +url = $url; + } + + /** + * Retrieves URL of updates server. + * + * @return string + */ + public function getUrl() + { + return is_null($this->url) + ? 'https://mibew.org/api/auto-updates' + : $this->url; + } + + /** + * Retrieves list of errors that took place during update checking process. + * + * @return array List of errors. Each item in the list is a error string. + */ + public function getErrors() + { + return $this->errors; + } + + /** + * Runs update checking process. + * + * @return boolean False on error and true otherwise. To get more info about + * error call {@link UpdateChecker::getErrors()} method. + */ + public function run() + { + $ch = curl_init($this->getUrl()); + + // TODO: set timeouts + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 5); + + $json = json_encode($this->getSystemInfo()); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Accept: application/json', + 'Content-Type: application/json', + 'Content-Length: ' . strlen($json) + )); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $body = curl_exec($ch); + $response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curl_errno = curl_errno($ch); + $curl_error = curl_error($ch); + curl_close($ch); + + if ($curl_errno !== 0) { + // cURL request failed. + $this->errors[] = sprintf( + 'cURL error (#%u): %s', + $curl_errno, + $curl_error + ); + + return false; + } + + if ($response_code != 200) { + // Unexpected HTTP recieved. + $this->errors[] = sprintf( + 'Update server returns %u HTTP code instead of 200', + $response_code + ); + + return false; + } + + $updates = json_decode($body, true); + $json_error = json_last_error(); + if ($json_error !== JSON_ERROR_NONE) { + // Cannot parse JSON result. + $this->errors[] = $this->formatJsonError($json_error); + + return false; + } + + if (!$updates) { + // There are no available updates. + return true; + } + + return $this->processUpdates($updates); + } + + /** + * Retrieves set of system info that will be sent to updates server. + * + * @return array + */ + protected function getSystemInfo() + { + return array( + 'core' => MIBEW_VERSION, + 'plugins' => $this->getPluginsInfo(), + ); + } + + /** + * Retrieves info about plugins available in the system. + * + * @return array Associative array of plugins info. Each key of the array is + * fully qualified plugin's name and each value is an array with the + * fillowing keys: + * - "version": string, version of the plugin which presents in the system. + * - "installed": boolean, indicates if the plugin is installed. + * - "enabled": boolean, indicates if the plugin is enabled. + */ + protected function getPluginsInfo() + { + if (is_null($this->pluginsInfo)) { + $this->pluginsInfo = []; + $names = PluginUtils::discoverPlugins(); + foreach ($names as $plugin_name) { + $info = new PluginInfo($plugin_name); + $this->pluginsInfo[$plugin_name] = array( + 'version' => $info->getVersion(), + 'installed' => $info->getState()->installed, + 'enabled' => $info->getState()->enabled, + ); + } + } + + return $this->pluginsInfo; + } + + /** + * Performs all actions that are needed to prepare store available updates. + * + * @param array $updates Asscociative array of available updates that is + * retrieved from the updates server. + * @return boolean False on error and true otherwise. To get more info about + * error call {@link UpdateChecker::getErrors()} method. + */ + protected function processUpdates($updates) + { + // Process updates of the core. + if (version_compare($updates['core']['version'], MIBEW_VERSION) > 0) { + $update = $updates['core']; + // Save info about update for the core only if its version changed + $success = $this->saveUpdate( + 'core', + $update['version'], + $update['download'], + empty($update['description']) ? '' : $update['description'] + ); + if (!$success) { + // Something went wrong. The error is already logged so just + // notify the outer code. + return false; + } + } + + // Process plugins updates. + $plugins_info = $this->getPluginsInfo(); + foreach ($updates['plugins'] as $plugin_name => $update) { + if (!isset($plugins_info[$plugin_name])) { + // It's strange. We recieve update info for a plugin that does + // not exist in the system. Just do nothing. + continue; + } + + $info = $plugins_info[$plugin_name]; + + if (version_compare($update['version'], $info['version']) <= 0) { + // Version of the plugin is not updated. Just do nothing. + continue; + } + + // Save the update + $success = $this->saveUpdate( + $plugin_name, + $update['version'], + $update['download'], + empty($update['description']) ? '' : $update['description'] + ); + if (!$success) { + // Something went wrong. The error is already logged so just + // notify the outer code. + return false; + } + } + + return true; + } + + /** + * Saves record about available update in the database. + * + * @param string $target Update's target. Can be either "core" or fully + * qualified plugin's name. + * @param string $version The latest version at the updates server. + * @param string $url URL of the page where the update can be downloaded. + * @param string $description Arbitrary update's description. + * @return boolean False on failure and true otherwise. To get more info + * about the error call {@link UpdateChecker::getErrors()} method. + */ + protected function saveUpdate($target, $version, $url, $description = '') + { + try { + $update = AvailableUpdate::loadByTarget($target); + if (!$update) { + // There is no such update in the database. Create a new one. + $update = new AvailableUpdate(); + $update->target = $target; + } + + $update->version = $version; + $update->url = $url; + $update->description = $description; + + $update->save(); + } catch (\Exception $e) { + $this->errors[] = 'Cannot save available update: ' + $e->getMessage(); + + return false; + } + + return true; + } + + /** + * Builds human-readable message about error in json_* PHP's function. + * + * @param int $error_code Error code returned by json_last_error + * @return string Human-readable error message. + */ + protected function formatJsonError($error_code) + { + $errors = array( + JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH', + JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH', + JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR', + JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX', + ); + + // Following constants may be unavailable for the current PHP version. + if (defined('JSON_ERROR_UTF8')) { + $errors[JSON_ERROR_UTF8] = 'JSON_ERROR_UTF8'; + } + + if (defined('JSON_ERROR_RECURSION')) { + $errors[JSON_ERROR_RECURSION] = 'JSON_ERROR_RECURSION'; + } + + if (defined('JSON_ERROR_INF_OR_NAN')) { + $errors[JSON_ERROR_INF_OR_NAN] = 'JSON_ERROR_INF_OR_NAN'; + } + + if (defined('JSON_ERROR_UNSUPPORTED_TYPE')) { + $errors[JSON_ERROR_UNSUPPORTED_TYPE] = 'JSON_ERROR_UNSUPPORTED_TYPE'; + } + + $msg = isset($errors[$error_code]) ? $errors[$error_code] : 'UNKNOWN'; + + return sprintf( + 'Could not parse response from update server. The error is: "%s"', + $msg + ); + } +} diff --git a/src/mibew/libs/classes/Mibew/Maintenance/Updater.php b/src/mibew/libs/classes/Mibew/Maintenance/Updater.php index 5552af41..6936f3e5 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/Updater.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/Updater.php @@ -332,14 +332,24 @@ class Updater return false; } - // Alter locale table. try { + // Alter locale table. $db->query('ALTER TABLE {locale} ADD COLUMN name varchar(128) NOT NULL DEFAULT "" AFTER code'); $db->query('ALTER TABLE {locale} ADD COLUMN rtl tinyint NOT NULL DEFAULT 0'); $db->query('ALTER TABLE {locale} ADD COLUMN time_locale varchar(128) NOT NULL DEFAULT "en_US"'); $db->query('ALTER TABLE {locale} ADD COLUMN date_format text'); $db->query('ALTER TABLE {locale} ADD UNIQUE KEY code (code)'); + + // Create a table for available updates. + $db->query('CREATE TABLE {available_update} ( ' + . 'id INT NOT NULL auto_increment PRIMARY KEY, ' + . 'target varchar(255) NOT NULL, ' + . 'version varchar(255) NOT NULL, ' + . 'url text, ' + . 'description text, ' + . 'UNIQUE KEY target (target) ' + . ') charset utf8 ENGINE=InnoDb'); } catch (\Exception $e) { $this->errors[] = getlocal('Cannot update tables: {0}', $e->getMessage()); From e9d41c486975331d2c4f7d6be092e61619ee3294 Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Tue, 26 May 2015 13:34:58 +0000 Subject: [PATCH 02/14] Fix name of "available_update" table --- src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php b/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php index 27789251..e1ead19c 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php @@ -140,7 +140,7 @@ class AvailableUpdate public static function all() { $rows = Database::getInstance()->query( - "SELECT * FROM {available_updates}", + "SELECT * FROM {available_update}", null, array('return_rows' => Database::RETURN_ALL_ROWS) ); From d4ebd680522362250dc0503bfc3ffccd79648d21 Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Tue, 26 May 2015 13:53:22 +0000 Subject: [PATCH 03/14] Show list of available updates at "About" page --- src/mibew/js/source/about.js | 40 ----------------- .../Mibew/Controller/AboutController.php | 44 ++++++++++++++++--- .../server_side/about.handlebars | 22 +++++++--- 3 files changed, 54 insertions(+), 52 deletions(-) delete mode 100644 src/mibew/js/source/about.js diff --git a/src/mibew/js/source/about.js b/src/mibew/js/source/about.js deleted file mode 100644 index 7636e2e8..00000000 --- a/src/mibew/js/source/about.js +++ /dev/null @@ -1,40 +0,0 @@ -/*! - * This file is a part of Mibew Messenger. - * - * Copyright 2005-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -(function (Mibew, $) { - Mibew.updateVersion = function(data) { - if (!data.core || !data.core.stable) { - return; - } - - $(document).ready(function() { - var currentVersion = $("#current-version").html(), - core = data.core.stable; - - if (currentVersion != core.version) { - if (currentVersion < core.version) { - $("#current-version").css("color", "red"); - } - $("#latest-version").html(core.version + ", Download " + core.title + ""); - } else { - $("#current-version").css("color", "green"); - $("#latest-version").html(core.version); - } - }); - } -})(Mibew, jQuery); diff --git a/src/mibew/libs/classes/Mibew/Controller/AboutController.php b/src/mibew/libs/classes/Mibew/Controller/AboutController.php index b9ac3c12..49a1be2c 100644 --- a/src/mibew/libs/classes/Mibew/Controller/AboutController.php +++ b/src/mibew/libs/classes/Mibew/Controller/AboutController.php @@ -19,7 +19,7 @@ namespace Mibew\Controller; -use Mibew\Asset\AssetManagerInterface; +use Mibew\Maintenance\AvailableUpdate; use Symfony\Component\HttpFoundation\Request; /** @@ -43,16 +43,11 @@ class AboutController extends AbstractController 'version' => MIBEW_VERSION, 'title' => getlocal('About'), 'menuid' => 'about', + 'availableUpdates' => $this->getAvailableUpdates(), ), prepare_menu($this->getOperator()) ); - $this->getAssetManager()->attachJs('js/compiled/about.js'); - $this->getAssetManager()->attachJs( - 'https://mibew.org/api/updates', - AssetManagerInterface::ABSOLUTE_URL - ); - return $this->render('about', $page); } @@ -86,4 +81,39 @@ class AboutController extends AbstractController return $info; } + + /** + * Builds list of available updates to display in the template. + * + * @return array List of updates data. Each item of the list is associative + * array with the following keys: + * - "title": string, title of the update. + * - "version": string, the latest available version. + * - "url": string, URL of the page the updated version can be downloaded + * from. + * - "description": string, description of the update. + */ + protected function getAvailableUpdates() + { + $updates = AvailableUpdate::all(); + if (!$updates) { + return array(); + } + + $data = array(); + foreach ($updates as $update) { + $title = ($update->target == 'core') + ? 'Mibew' + : getlocal('{0} plugin', array($update->target)); + + $data[] = array( + 'title' => $title, + 'version' => $update->version, + 'url' => $update->url, + 'description' => $update->description, + ); + } + + return $data; + } } diff --git a/src/mibew/styles/pages/default/templates_src/server_side/about.handlebars b/src/mibew/styles/pages/default/templates_src/server_side/about.handlebars index 6396699a..52f02513 100644 --- a/src/mibew/styles/pages/default/templates_src/server_side/about.handlebars +++ b/src/mibew/styles/pages/default/templates_src/server_side/about.handlebars @@ -22,11 +22,6 @@
-

{{l10n "Latest version:"}}

-
- -
-

{{l10n "Installed localizations:"}}

{{#each localizations}} {{this}} @@ -36,6 +31,23 @@

{{l10n "Environment:"}}

PHP {{phpVersion}} {{#each extensions}}{{@key}}{{#if loaded}}{{#if version}}/{{version}}{{/if}}{{else}}/absent{{/if}} {{/each}} + +

+ + {{#if availableUpdates}} +

{{l10n "Available updates"}}

+ {{#each availableUpdates}} +

{{title}} ({{version}})

+ {{#if description}} +
{{description}}
+ {{/if}} +
+ Download +
+ +
+ {{/each}} + {{/if}} +
+ +
+ +
+ +
+
+ {{#if canmodify}}
From 5397f7014ad41c7ffe0735fcf9fdafd00df7ba9c Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Thu, 4 Jun 2015 10:16:51 +0000 Subject: [PATCH 07/14] Fix default URL of updates server --- src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php b/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php index d7ac0a4e..4bcf5008 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php @@ -68,7 +68,7 @@ class UpdateChecker public function getUrl() { return is_null($this->url) - ? 'https://mibew.org/api/auto-updates' + ? 'https://mibew.org/api/updates.json' : $this->url; } From fbe53e3b29c998e235fae84d881c627f23d7e5f7 Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Fri, 5 Jun 2015 09:45:25 +0000 Subject: [PATCH 08/14] Create getter for update checker inside the cron worker --- .../classes/Mibew/Maintenance/CronWorker.php | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/mibew/libs/classes/Mibew/Maintenance/CronWorker.php b/src/mibew/libs/classes/Mibew/Maintenance/CronWorker.php index 1120909d..c49e6259 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/CronWorker.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/CronWorker.php @@ -66,9 +66,10 @@ class CronWorker public function __construct(PoolInterface $cache, UpdateChecker $update_checker = null) { $this->cache = $cache; - $this->updateChecker = is_null($update_checker) - ? new UpdateChecker() - : $update_checker; + + if (!is_null($update_checker)) { + $this->updateChecker = $update_checker; + } } /** @@ -99,10 +100,11 @@ class CronWorker if (Settings::get('autocheckupdates') == '1') { // Run the update checker - if (!$this->updateChecker->run()) { + $update_checker = $this->getUpdateChecker(); + if (!$update_checker->run()) { $this->errors = array_merge( $this->errors, - $this->updateChecker->getErrors() + $update_checker->getErrors() ); return false; @@ -137,4 +139,20 @@ class CronWorker { return $this->log; } + + /** + * Retrives an instance of Update Checker attached to the worker. + * + * If there was no attached checker it creates a new one. + * + * @return UpdateChecker + */ + protected function getUpdateChecker() + { + if (is_null($this->updateChecker)) { + $this->updateChecker = new UpdateChecker(); + } + + return $this->updateChecker; + } } From 25054c4e0fd6686172e3ed9c6fb1a85f1bc09078 Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Fri, 5 Jun 2015 11:33:19 +0000 Subject: [PATCH 09/14] Add "_instance_id" setting --- .../classes/Mibew/Maintenance/Installer.php | 40 +++++++++++++++++++ .../classes/Mibew/Maintenance/Updater.php | 9 +++++ .../libs/classes/Mibew/Maintenance/Utils.php | 34 ++++++++++++++++ src/mibew/libs/classes/Mibew/Settings.php | 4 ++ 4 files changed, 87 insertions(+) diff --git a/src/mibew/libs/classes/Mibew/Maintenance/Installer.php b/src/mibew/libs/classes/Mibew/Maintenance/Installer.php index cb0d8625..12b9551d 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/Installer.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/Installer.php @@ -440,6 +440,46 @@ class Installer return false; } + // Generate Unique ID for Mibew Instance + try { + list($count) = $db->query( + 'SELECT COUNT(*) FROM {config} WHERE vckey = :key', + array(':key' => '_instance_id'), + array( + 'return_rows' => Database::RETURN_ONE_ROW, + 'fetch_type' => Database::FETCH_NUM, + ) + ); + + if ($count == 0) { + $db->query( + 'INSERT INTO {config} (vckey, vcvalue) VALUES (:key, :value)', + array( + ':key' => '_instance_id', + ':value' => Utils::generateInstanceId(), + ) + ); + } else { + // The option is already in the database. It seems that + // something went wrong with the previous installation attempt. + // Just update the instance ID. + $db->query( + 'UPDATE {config} SET vcvalue = :value WHERE vckey = :key', + array( + ':key' => '_instance_id', + ':value' => Utils::generateInstanceId(), + ) + ); + } + } catch (\Exception $e) { + $this->errors[] = getlocal( + 'Cannot store instance ID. Error {0}', + array($e->getMessage()) + ); + + return false; + } + return true; } diff --git a/src/mibew/libs/classes/Mibew/Maintenance/Updater.php b/src/mibew/libs/classes/Mibew/Maintenance/Updater.php index 6936f3e5..a66c6f2c 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/Updater.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/Updater.php @@ -350,6 +350,15 @@ class Updater . 'description text, ' . 'UNIQUE KEY target (target) ' . ') charset utf8 ENGINE=InnoDb'); + + // Generate Unique ID of Mibew instance. + $db->query( + 'INSERT INTO {config} (vckey, vcvalue) VALUES (:key, :value)', + array( + ':key' => '_instance_id', + ':value' => Utils::generateInstanceId(), + ) + ); } catch (\Exception $e) { $this->errors[] = getlocal('Cannot update tables: {0}', $e->getMessage()); diff --git a/src/mibew/libs/classes/Mibew/Maintenance/Utils.php b/src/mibew/libs/classes/Mibew/Maintenance/Utils.php index 603be88e..ce122abb 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/Utils.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/Utils.php @@ -101,6 +101,40 @@ class Utils return $updates; } + /** + * Generates random unique 64 characters length ID for Mibew instance. + * + * WARNING: This ID should not be used for any security/cryptographic. If + * you need an ID for such purpose you have to use PHP's + * {@link openssl_random_pseudo_bytes()} function instead. + * + * @return string + */ + public static function generateInstanceId() + { + $chars = '0123456789abcdefghijklmnopqrstuvwxyz'; + $rnd = (string)microtime(true); + + // Add ten random characters before and after the timestamp + $max_char = strlen($chars) - 1; + for ($i = 0; $i < 10; $i++) { + $rnd = $chars[rand(0, $max_char)] . $rnd . $chars[rand(0, $max_char)]; + } + + if (function_exists('hash')) { + // There is hash function that can give us 64-length hash. + return hash('sha256', $rnd); + } + + // We should build random 64 character length hash using old'n'good md5 + // function. + $middle = (int)floor(strlen($rnd) / 2); + $rnd_left = substr($rnd, 0, $middle); + $rnd_right = substr($rnd, $middle); + + return md5($rnd_left) . md5($rnd_right); + } + /** * This class should not be instantiated */ diff --git a/src/mibew/libs/classes/Mibew/Settings.php b/src/mibew/libs/classes/Mibew/Settings.php index dca40a87..2a547748 100644 --- a/src/mibew/libs/classes/Mibew/Settings.php +++ b/src/mibew/libs/classes/Mibew/Settings.php @@ -115,6 +115,10 @@ class Settings // underscore sign(_). // Unix timestamp when cron job ran last time. '_last_cron_run' => 0, + // Random unique ID which is used for getting info about new + // updates. This value is initialized during Installation or Update + // process. + '_instance_id' => '', ); // Load values from database From 4b74ceb854136064f39b3aae50afb66a6492e120 Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Fri, 5 Jun 2015 11:34:13 +0000 Subject: [PATCH 10/14] Fix "available_update" table schema --- src/mibew/configs/database_schema.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mibew/configs/database_schema.yml b/src/mibew/configs/database_schema.yml index 98d9ef7b..a6a47e0e 100644 --- a/src/mibew/configs/database_schema.yml +++ b/src/mibew/configs/database_schema.yml @@ -319,7 +319,7 @@ plugin: # Contains info about all available updates available_update: - field: + fields: # Artificial ID id: "INT NOT NULL auto_increment PRIMARY KEY" # Can be either "core" or fully qualified plugin's name From 6bf331b355eee66f1af2070343914f632fa86a8f Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Fri, 5 Jun 2015 11:39:44 +0000 Subject: [PATCH 11/14] Rename "available_update" table to "availableupdate" This is needed to follow naming convention --- src/mibew/configs/database_schema.yml | 2 +- .../classes/Mibew/Maintenance/AvailableUpdate.php | 12 ++++++------ src/mibew/libs/classes/Mibew/Maintenance/Updater.php | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/mibew/configs/database_schema.yml b/src/mibew/configs/database_schema.yml index a6a47e0e..3f3d5ef9 100644 --- a/src/mibew/configs/database_schema.yml +++ b/src/mibew/configs/database_schema.yml @@ -318,7 +318,7 @@ plugin: name: [name] # Contains info about all available updates -available_update: +availableupdate: fields: # Artificial ID id: "INT NOT NULL auto_increment PRIMARY KEY" diff --git a/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php b/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php index e1ead19c..5e08f80b 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php @@ -79,7 +79,7 @@ class AvailableUpdate // Load update's info $info = Database::getInstance()->query( - "SELECT * FROM {available_update} WHERE id = :id", + "SELECT * FROM {availableupdate} WHERE id = :id", array(':id' => $id), array('return_rows' => Database::RETURN_ONE_ROW) ); @@ -112,7 +112,7 @@ class AvailableUpdate // Load update info $info = Database::getInstance()->query( - "SELECT * FROM {available_update} WHERE target = :target", + "SELECT * FROM {availableupdate} WHERE target = :target", array(':target' => $target), array('return_rows' => Database::RETURN_ONE_ROW) ); @@ -140,7 +140,7 @@ class AvailableUpdate public static function all() { $rows = Database::getInstance()->query( - "SELECT * FROM {available_update}", + "SELECT * FROM {availableupdate}", null, array('return_rows' => Database::RETURN_ALL_ROWS) ); @@ -182,7 +182,7 @@ class AvailableUpdate } Database::getInstance()->query( - "DELETE FROM {available_update} WHERE id = :id LIMIT 1", + "DELETE FROM {availableupdate} WHERE id = :id LIMIT 1", array(':id' => $this->id) ); } @@ -209,7 +209,7 @@ class AvailableUpdate if (!$this->id) { // This update is new. $db->query( - ("INSERT INTO {available_update} (target, version, url, description) " + ("INSERT INTO {availableupdate} (target, version, url, description) " . "VALUES (:target, :version, :url, :description)"), array( ':target' => $this->target, @@ -222,7 +222,7 @@ class AvailableUpdate } else { // Update existing update $db->query( - ("UPDATE {available_update} SET target = :target, url = :url, " + ("UPDATE {availableupdate} SET target = :target, url = :url, " . "version = :version, description = :description " . "WHERE id = :id"), array( diff --git a/src/mibew/libs/classes/Mibew/Maintenance/Updater.php b/src/mibew/libs/classes/Mibew/Maintenance/Updater.php index a66c6f2c..763a4aa3 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/Updater.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/Updater.php @@ -342,7 +342,7 @@ class Updater $db->query('ALTER TABLE {locale} ADD UNIQUE KEY code (code)'); // Create a table for available updates. - $db->query('CREATE TABLE {available_update} ( ' + $db->query('CREATE TABLE {availableupdate} ( ' . 'id INT NOT NULL auto_increment PRIMARY KEY, ' . 'target varchar(255) NOT NULL, ' . 'version varchar(255) NOT NULL, ' From 5af9f9210269068a5fd97a2646e595bfddaa365a Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Fri, 5 Jun 2015 11:53:39 +0000 Subject: [PATCH 12/14] Send instance ID to the updates server --- .../classes/Mibew/Maintenance/CronWorker.php | 4 ++ .../Mibew/Maintenance/UpdateChecker.php | 45 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/mibew/libs/classes/Mibew/Maintenance/CronWorker.php b/src/mibew/libs/classes/Mibew/Maintenance/CronWorker.php index c49e6259..731754ee 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/CronWorker.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/CronWorker.php @@ -151,6 +151,10 @@ class CronWorker { if (is_null($this->updateChecker)) { $this->updateChecker = new UpdateChecker(); + $id = Settings::get('_instance_id'); + if ($id) { + $this->updateChecker->setInstanceId($id); + } } return $this->updateChecker; diff --git a/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php b/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php index 4bcf5008..134450fd 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php @@ -34,6 +34,13 @@ class UpdateChecker */ private $url = null; + /** + * Unique 64 character length ID of the Mibew instance. + * + * @var string + */ + private $instanceId = ''; + /** * A cache for plugins info array. * @@ -72,6 +79,34 @@ class UpdateChecker : $this->url; } + /** + * Sets Unique ID of the Mibew instance. + * + * @param string $id Unique ID that is 64 characters length at most. + * @throws \InvalidArgumentException + */ + public function setInstanceId($id) + { + if (strlen($id) > 64) { + throw new \InvalidArgumentException( + 'The ID is too long. It can be 64 characters length at most.' + ); + } + + // Make sure the ID is always a string. + $this->instanceId = $id ?: ''; + } + + /** + * Retrieve Unique ID of the Mibew instance. + * + * @return string + */ + public function getInstanceId() + { + return $this->instanceId; + } + /** * Retrieves list of errors that took place during update checking process. * @@ -157,10 +192,18 @@ class UpdateChecker */ protected function getSystemInfo() { - return array( + $info = array( 'core' => MIBEW_VERSION, 'plugins' => $this->getPluginsInfo(), ); + + // Attach Instance ID to the info but only if it's not empty. + $id = $this->getInstanceId(); + if ($id) { + $info['uid'] = $id; + } + + return $info; } /** From 8c574ec88cec9d5ffea799e63f13d1136802a050 Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Fri, 5 Jun 2015 14:28:12 +0000 Subject: [PATCH 13/14] Update Updates Server URL one more time --- src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php b/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php index 134450fd..f32e7e42 100644 --- a/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php +++ b/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php @@ -75,7 +75,7 @@ class UpdateChecker public function getUrl() { return is_null($this->url) - ? 'https://mibew.org/api/updates.json' + ? 'https://mibew.org/api2/updates.json' : $this->url; } From 2cbb0c41345d6d30f03f1cacc2d0d5edd617a794 Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Fri, 5 Jun 2015 14:38:18 +0000 Subject: [PATCH 14/14] Set correct instance ID when checking updates manually --- src/mibew/libs/classes/Mibew/Controller/UpdateController.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mibew/libs/classes/Mibew/Controller/UpdateController.php b/src/mibew/libs/classes/Mibew/Controller/UpdateController.php index 43913712..7bc5a4bc 100644 --- a/src/mibew/libs/classes/Mibew/Controller/UpdateController.php +++ b/src/mibew/libs/classes/Mibew/Controller/UpdateController.php @@ -21,6 +21,7 @@ namespace Mibew\Controller; use Mibew\Maintenance\UpdateChecker; use Mibew\Maintenance\Updater; +use Mibew\Settings; use Mibew\Style\PageStyle; use Symfony\Component\HttpFoundation\Request; @@ -135,6 +136,10 @@ class UpdateController extends AbstractController { if (is_null($this->updateChecker)) { $this->updateChecker = new UpdateChecker(); + $id = Settings::get('_instance_id'); + if ($id) { + $this->updateChecker->setInstanceId($id); + } } return $this->updateChecker;