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..3f3d5ef9 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
+availableupdate:
+ fields:
+ # 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/configs/routing.yml b/src/mibew/configs/routing.yml
index 872a664b..7f67e159 100644
--- a/src/mibew/configs/routing.yml
+++ b/src/mibew/configs/routing.yml
@@ -759,6 +759,13 @@ update_run:
_access_check: Mibew\AccessControl\Check\PermissionsCheck
_access_permissions: [CAN_ADMINISTRATE]
+update_check:
+ path: /update/check
+ defaults:
+ _controller: Mibew\Controller\UpdateController::checkUpdatesAction
+ _access_check: Mibew\AccessControl\Check\PermissionsCheck
+ _access_permissions: [CAN_ADMINISTRATE]
+
## Users (visitors avaiting page)
users:
path: /operator/users
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..1c907fe8 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);
}
@@ -68,7 +63,7 @@ class AboutController extends AbstractController
*/
protected function getExtensionsInfo()
{
- $required_extensions = array('PDO', 'pdo_mysql', 'gd');
+ $required_extensions = array('PDO', 'pdo_mysql', 'gd', 'curl');
$info = array();
foreach ($required_extensions as $ext) {
if (!extension_loaded($ext)) {
@@ -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/libs/classes/Mibew/Controller/Settings/FeaturesController.php b/src/mibew/libs/classes/Mibew/Controller/Settings/FeaturesController.php
index a7bfa1d1..e0590c59 100644
--- a/src/mibew/libs/classes/Mibew/Controller/Settings/FeaturesController.php
+++ b/src/mibew/libs/classes/Mibew/Controller/Settings/FeaturesController.php
@@ -111,6 +111,7 @@ class FeaturesController extends AbstractController
'showonlineoperators',
'enablecaptcha',
'trackoperators',
+ 'autocheckupdates',
);
}
}
diff --git a/src/mibew/libs/classes/Mibew/Controller/UpdateController.php b/src/mibew/libs/classes/Mibew/Controller/UpdateController.php
index eb28121d..7bc5a4bc 100644
--- a/src/mibew/libs/classes/Mibew/Controller/UpdateController.php
+++ b/src/mibew/libs/classes/Mibew/Controller/UpdateController.php
@@ -19,7 +19,9 @@
namespace Mibew\Controller;
+use Mibew\Maintenance\UpdateChecker;
use Mibew\Maintenance\Updater;
+use Mibew\Settings;
use Mibew\Style\PageStyle;
use Symfony\Component\HttpFoundation\Request;
@@ -33,6 +35,11 @@ class UpdateController extends AbstractController
*/
protected $updater = null;
+ /**
+ * @var UpdateChecker|null
+ */
+ protected $updateChecker = null;
+
/**
* Renders update intro page.
*
@@ -74,6 +81,26 @@ class UpdateController extends AbstractController
return $this->render('update_progress', $parameters);
}
+ /**
+ * Runs the Update checker.
+ *
+ * @param Request $request Incoming request.
+ * @return Response|string Rendered page contents or Symfony's response
+ * object.
+ */
+ public function checkUpdatesAction(Request $request)
+ {
+ $checker = $this->getUpdateChecker();
+ $success = $checker->run();
+ if (!$success) {
+ foreach ($checker->getErrors() as $error) {
+ trigger_error('Update checking failed: ' . $error, E_USER_WARNING);
+ }
+ }
+
+ return $this->redirect($this->generateUrl('about'));
+ }
+
/**
* {@inheritdoc}
*/
@@ -99,4 +126,22 @@ class UpdateController extends AbstractController
return $this->updater;
}
+
+ /**
+ * Returns an instance of Update Checker.
+ *
+ * @return UpdateChecker
+ */
+ protected function getUpdateChecker()
+ {
+ 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/AvailableUpdate.php b/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php
new file mode 100644
index 00000000..5e08f80b
--- /dev/null
+++ b/src/mibew/libs/classes/Mibew/Maintenance/AvailableUpdate.php
@@ -0,0 +1,253 @@
+query(
+ "SELECT * FROM {availableupdate} 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 {availableupdate} 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 {availableupdate}",
+ 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 {availableupdate} 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 {availableupdate} (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 {availableupdate} 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..731754ee 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,15 @@ 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;
+
+ if (!is_null($update_checker)) {
+ $this->updateChecker = $update_checker;
+ }
}
/**
@@ -71,6 +83,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 +98,18 @@ class CronWorker
$dispatcher = EventDispatcher::getInstance();
$dispatcher->triggerEvent(Events::CRON_RUN);
- // Update time of last cron run
- Settings::set('_last_cron_run', time());
+ if (Settings::get('autocheckupdates') == '1') {
+ // Run the update checker
+ $update_checker = $this->getUpdateChecker();
+ if (!$update_checker->run()) {
+ $this->errors = array_merge(
+ $this->errors,
+ $update_checker->getErrors()
+ );
+
+ return false;
+ }
+ }
} catch (\Exception $e) {
$this->log[] = $e->getMessage();
@@ -114,4 +139,24 @@ 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();
+ $id = Settings::get('_instance_id');
+ if ($id) {
+ $this->updateChecker->setInstanceId($id);
+ }
+ }
+
+ return $this->updateChecker;
+ }
}
diff --git a/src/mibew/libs/classes/Mibew/Maintenance/Installer.php b/src/mibew/libs/classes/Mibew/Maintenance/Installer.php
index 773bbfd7..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;
}
@@ -489,7 +529,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..f32e7e42
--- /dev/null
+++ b/src/mibew/libs/classes/Mibew/Maintenance/UpdateChecker.php
@@ -0,0 +1,371 @@
+url = $url;
+ }
+
+ /**
+ * Retrieves URL of updates server.
+ *
+ * @return string
+ */
+ public function getUrl()
+ {
+ return is_null($this->url)
+ ? 'https://mibew.org/api2/updates.json'
+ : $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.
+ *
+ * @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()
+ {
+ $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;
+ }
+
+ /**
+ * 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..763a4aa3 100644
--- a/src/mibew/libs/classes/Mibew/Maintenance/Updater.php
+++ b/src/mibew/libs/classes/Mibew/Maintenance/Updater.php
@@ -332,14 +332,33 @@ 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 {availableupdate} ( '
+ . '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');
+
+ // 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 548d206a..2a547748 100644
--- a/src/mibew/libs/classes/Mibew/Settings.php
+++ b/src/mibew/libs/classes/Mibew/Settings.php
@@ -97,6 +97,7 @@ class Settings
'surveyaskgroup' => '1',
'surveyaskmessage' => '0',
'enablepopupnotification' => '0',
+ 'autocheckupdates' => '1', /* Check updates automatically */
'showonlineoperators' => '0',
'enablecaptcha' => '0',
'online_timeout' => 30, /* Timeout (in seconds) when online operator becomes offline */
@@ -114,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
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..de8c2a27 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 @@
-