Merge branch 'auto_update'

This commit is contained in:
Dmitriy Simushev 2015-06-05 14:47:12 +00:00
commit b340206d3a
17 changed files with 910 additions and 60 deletions

View File

@ -11,7 +11,7 @@ This repository contains the core of Mibew Messenger application.
## Server requirements ## Server requirements
1. A webserver or web hosting account running on any major Operating System 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 3. MySQL 5.0 and above
## Build from sources ## Build from sources

View File

@ -6,7 +6,7 @@ REQUIREMENTS
* Apache web server 1.3.34 or above with the ability to use local .htaccess * Apache web server 1.3.34 or above with the ability to use local .htaccess
files (mod_rewrite module is optional, but recommended) files (mod_rewrite module is optional, but recommended)
* MySQL database 5.0 or above * 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 INSTALLATION

View File

@ -316,3 +316,19 @@ plugin:
enabled: "tinyint NOT NULL DEFAULT 0" enabled: "tinyint NOT NULL DEFAULT 0"
unique_keys: unique_keys:
name: [name] 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]

View File

@ -759,6 +759,13 @@ update_run:
_access_check: Mibew\AccessControl\Check\PermissionsCheck _access_check: Mibew\AccessControl\Check\PermissionsCheck
_access_permissions: [CAN_ADMINISTRATE] _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 (visitors avaiting page)
users: users:
path: /operator/users path: /operator/users

View File

@ -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 <a href=\"" + core.download + "\">" + core.title + "</a>");
} else {
$("#current-version").css("color", "green");
$("#latest-version").html(core.version);
}
});
}
})(Mibew, jQuery);

View File

@ -19,7 +19,7 @@
namespace Mibew\Controller; namespace Mibew\Controller;
use Mibew\Asset\AssetManagerInterface; use Mibew\Maintenance\AvailableUpdate;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
@ -43,16 +43,11 @@ class AboutController extends AbstractController
'version' => MIBEW_VERSION, 'version' => MIBEW_VERSION,
'title' => getlocal('About'), 'title' => getlocal('About'),
'menuid' => 'about', 'menuid' => 'about',
'availableUpdates' => $this->getAvailableUpdates(),
), ),
prepare_menu($this->getOperator()) 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); return $this->render('about', $page);
} }
@ -68,7 +63,7 @@ class AboutController extends AbstractController
*/ */
protected function getExtensionsInfo() protected function getExtensionsInfo()
{ {
$required_extensions = array('PDO', 'pdo_mysql', 'gd'); $required_extensions = array('PDO', 'pdo_mysql', 'gd', 'curl');
$info = array(); $info = array();
foreach ($required_extensions as $ext) { foreach ($required_extensions as $ext) {
if (!extension_loaded($ext)) { if (!extension_loaded($ext)) {
@ -86,4 +81,39 @@ class AboutController extends AbstractController
return $info; 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;
}
} }

View File

@ -111,6 +111,7 @@ class FeaturesController extends AbstractController
'showonlineoperators', 'showonlineoperators',
'enablecaptcha', 'enablecaptcha',
'trackoperators', 'trackoperators',
'autocheckupdates',
); );
} }
} }

View File

@ -19,7 +19,9 @@
namespace Mibew\Controller; namespace Mibew\Controller;
use Mibew\Maintenance\UpdateChecker;
use Mibew\Maintenance\Updater; use Mibew\Maintenance\Updater;
use Mibew\Settings;
use Mibew\Style\PageStyle; use Mibew\Style\PageStyle;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -33,6 +35,11 @@ class UpdateController extends AbstractController
*/ */
protected $updater = null; protected $updater = null;
/**
* @var UpdateChecker|null
*/
protected $updateChecker = null;
/** /**
* Renders update intro page. * Renders update intro page.
* *
@ -74,6 +81,26 @@ class UpdateController extends AbstractController
return $this->render('update_progress', $parameters); 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} * {@inheritdoc}
*/ */
@ -99,4 +126,22 @@ class UpdateController extends AbstractController
return $this->updater; 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;
}
} }

View File

@ -0,0 +1,253 @@
<?php
/*
* 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.
*/
namespace Mibew\Maintenance;
use Mibew\Database;
/**
* Represents a record about available update with all necessary info.
*/
class AvailableUpdate
{
/**
* Unique (for the current Mibew instance) update ID.
*
* @type int
*/
public $id;
/**
* String representing update target.
*
* It can be equal to either "core" or fully qualified plugin's name.
*
* @var string
*/
public $target;
/**
* The latest version the core/plugin can be updated to.
*
* @type string
*/
public $version;
/**
* The URL the update can be downloaded from.
*
* @type string
*/
public $url;
/**
* Arbitrary description of the update.
*
* @type string
*/
public $description;
/**
* Loads update by its ID.
*
* @param int $id ID of the update to load
* @return boolean|AvailableUpdate Returns an AvailableUpdate instance or
* boolean false on failure.
*/
public static function load($id)
{
// Check $id
if (empty($id)) {
return false;
}
// Load update's info
$info = Database::getInstance()->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'];
}
}

View File

@ -36,6 +36,13 @@ class CronWorker
*/ */
protected $cache = null; protected $cache = null;
/**
* An instance of update checker.
*
* @var UpdateChecker|null
*/
protected $updateChecker = null;
/** /**
* List of errors. * List of errors.
* *
@ -54,10 +61,15 @@ class CronWorker
* Class constructor. * Class constructor.
* *
* @param PoolInterface $cache An instance of cache pool. * @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->cache = $cache;
if (!is_null($update_checker)) {
$this->updateChecker = $update_checker;
}
} }
/** /**
@ -71,6 +83,9 @@ class CronWorker
try { try {
set_time_limit(0); set_time_limit(0);
// Update time of last cron run
Settings::set('_last_cron_run', time());
// Remove stale cached items // Remove stale cached items
$this->cache->purge(); $this->cache->purge();
@ -83,8 +98,18 @@ class CronWorker
$dispatcher = EventDispatcher::getInstance(); $dispatcher = EventDispatcher::getInstance();
$dispatcher->triggerEvent(Events::CRON_RUN); $dispatcher->triggerEvent(Events::CRON_RUN);
// Update time of last cron run if (Settings::get('autocheckupdates') == '1') {
Settings::set('_last_cron_run', time()); // 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) { } catch (\Exception $e) {
$this->log[] = $e->getMessage(); $this->log[] = $e->getMessage();
@ -114,4 +139,24 @@ class CronWorker
{ {
return $this->log; 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;
}
} }

View File

@ -440,6 +440,46 @@ class Installer
return false; 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; return true;
} }
@ -489,7 +529,7 @@ class Installer
*/ */
protected function checkPhpExtensions() protected function checkPhpExtensions()
{ {
$extensions = array('PDO', 'pdo_mysql', 'gd'); $extensions = array('PDO', 'pdo_mysql', 'gd', 'curl');
foreach ($extensions as $ext) { foreach ($extensions as $ext) {
if (!extension_loaded($ext)) { if (!extension_loaded($ext)) {

View File

@ -0,0 +1,371 @@
<?php
/*
* 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.
*/
namespace Mibew\Maintenance;
use Mibew\Plugin\Utils as PluginUtils;
use Mibew\Plugin\PluginInfo;
/**
* Encapsulates available updates checking process.
*/
class UpdateChecker
{
/**
* URL of the updates server.
*
* @var string|null
*/
private $url = null;
/**
* Unique 64 character length ID of the Mibew instance.
*
* @var string
*/
private $instanceId = '';
/**
* A cache for plugins info array.
*
* @var array|null
*/
private $pluginsInfo = null;
/**
* List of errors that took place during updates checking.
*
* Each item of the list is a error string.
*
* @var array
*/
private $errors = [];
/**
* Sets URL of updates server.
*
* @param string $url New updates server's URL
*/
public function setUrl($url)
{
$this->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
);
}
}

View File

@ -332,14 +332,33 @@ class Updater
return false; return false;
} }
// Alter locale table.
try { 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 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 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 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 COLUMN date_format text');
$db->query('ALTER TABLE {locale} ADD UNIQUE KEY code (code)'); $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) { } catch (\Exception $e) {
$this->errors[] = getlocal('Cannot update tables: {0}', $e->getMessage()); $this->errors[] = getlocal('Cannot update tables: {0}', $e->getMessage());

View File

@ -101,6 +101,40 @@ class Utils
return $updates; 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 * This class should not be instantiated
*/ */

View File

@ -97,6 +97,7 @@ class Settings
'surveyaskgroup' => '1', 'surveyaskgroup' => '1',
'surveyaskmessage' => '0', 'surveyaskmessage' => '0',
'enablepopupnotification' => '0', 'enablepopupnotification' => '0',
'autocheckupdates' => '1', /* Check updates automatically */
'showonlineoperators' => '0', 'showonlineoperators' => '0',
'enablecaptcha' => '0', 'enablecaptcha' => '0',
'online_timeout' => 30, /* Timeout (in seconds) when online operator becomes offline */ 'online_timeout' => 30, /* Timeout (in seconds) when online operator becomes offline */
@ -114,6 +115,10 @@ class Settings
// underscore sign(_). // underscore sign(_).
// Unix timestamp when cron job ran last time. // Unix timestamp when cron job ran last time.
'_last_cron_run' => 0, '_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 // Load values from database

View File

@ -22,11 +22,6 @@
<br/> <br/>
<h3>{{l10n "Latest version:"}}</h3>
<div id="latest-version"></div>
<br/>
<h3>{{l10n "Installed localizations:"}}</h3> <h3>{{l10n "Installed localizations:"}}</h3>
{{#each localizations}} {{#each localizations}}
{{this}} {{this}}
@ -36,6 +31,26 @@
<h3>{{l10n "Environment:"}}</h3> <h3>{{l10n "Environment:"}}</h3>
PHP {{phpVersion}} {{#each extensions}}{{@key}}{{#if loaded}}{{#if version}}/{{version}}{{/if}}{{else}}/absent{{/if}} {{/each}} PHP {{phpVersion}} {{#each extensions}}{{@key}}{{#if loaded}}{{#if version}}/{{version}}{{/if}}{{else}}/absent{{/if}} {{/each}}
<br/><br/>
<h2>{{l10n "Available updates"}}</h2>
{{#if availableUpdates}}
{{#each availableUpdates}}
<h3>{{title}} ({{version}})</h3>
{{#if description}}
<div>{{description}}</div>
{{/if}}
<div>
<a href="{{url}}">Download</a>
</div>
<br/>
{{/each}}
{{else}}
There is no available updates.<br/><br/>
{{/if}}
<a href="{{route "update_check"}}">{{l10n "Check for available updates"}}</a>
</div> </div>
<div class="form-footer"> <div class="form-footer">

View File

@ -172,6 +172,15 @@
<br clear="all"/> <br clear="all"/>
</div> </div>
<div class="field">
<label for="autocheck-updates" class="field-label">{{l10n "Check updates automatically"}}</label>
<div class="field-value">
<input id="autocheck-updates" type="checkbox" name="autocheckupdates" value="on"{{#if formautocheckupdates}} checked="checked"{{/if}}{{#unless canmodify}} disabled="disabled"{{/unless}}/>
</div>
<label for="autocheck-updates" class="field-description"> &mdash; {{l10n "System will check updates for the core and plugins automatically using cron"}}</label>
<br clear="all"/>
</div>
{{#if canmodify}} {{#if canmodify}}
<div class="form-button"> <div class="form-button">
<input type="submit" name="save" class="submit-button-background save-button" value="{{l10n "Save"}}"/> <input type="submit" name="save" class="submit-button-background save-button" value="{{l10n "Save"}}"/>