Projekt

Obecné

Profil

Stáhnout (11.1 KB) Statistiky
| Větev: | Tag: | Revize:
8baed187 Michal Kliment
<?php defined('SYSPATH') or die('No direct script access.');
/*
* This file is part of open source system FreenetIS
* and it is released under GPLv3 licence.
*
* More info about licence can be found:
* http://www.gnu.org/licenses/gpl-3.0.html
*
* More info about project can be found:
* http://www.freenetis.org/
*
*/


/**
* Class for versioning of FreenetIS. Manages source code and database versions
* and it performs database upgrade.
*
* @author Ondrej Fibich
* @see Controller
*/
class Version
{
/**
* Regex for valitadation of versions
*/
c2e44ab0 Michal Kliment
const VERSION_REGEX = '^(([0-9]+\.[0-9]+\.[0-9]+)(~(alpha|beta|dev|rc)[0-9]*)?)$';
8baed187 Michal Kliment
/**
* Gets version of Freenetis code
*
* @return string Version
*/
public static function get_version()
{
if (!defined('FREENETIS_VERSION'))
{
require 'version.php';
if (!self::is_valid_version(FREENETIS_VERSION))
{
throw new ErrorException('Wrong version format in version.php');
}
}
return FREENETIS_VERSION;
}
/**
* Gets version of Freenetis database
*
* @param $cache May be value returned from cache [optional]
* @return string Version
*/
public static function get_db_version($cache = TRUE)
{
return Settings::get('db_schema_version', $cache);
}
/**
* Check if the version of database is equal
*
* @return boolean true if versions are equal
*/
public static function is_db_up_to_date()
{
// old type of DB upgrades? => always false
if (is_numeric(self::get_db_version(FALSE)))
{
return false;
}
// new type
return (self::fn_version_compare() == 0);
}
/**
* Compares version of FreenetIS and its database
*
* @return integer -1 if the FreenetIS version is lower than the database
* version, 0 if they are equal, and 1 if the second is lower
*/
private static function fn_version_compare()
{
return self::compare(self::get_version(), self::get_db_version());
}
/**
* Checks if given version is in correct format
*
* @param string $version Version to check
* @return boolean true if it is, false otherwise
*/
private static function is_valid_version($version)
{
return mb_eregi(self::VERSION_REGEX, $version);
}
/**
* Compares two valid versions of FreenetIS
*
* @param string $version1
* @param string $version2
* @return integer -1 if the first version is lower than the second
* version, 0 if they are equal, and 1 if the second is lower
* @throws InvalidArgumentException On invalid version
*/
public static function compare($version1, $version2)
{
if (!self::is_valid_version($version1))
{
throw new InvalidArgumentException('Wrong version1 format: ' . $version1);
}
if (!self::is_valid_version($version2))
{
throw new InvalidArgumentException('Wrong version2 format: ' . $version2);
}
$version1_parts = explode('~', $version1);
$version2_parts = explode('~', $version2);
$cmp = 0;
if (($cmp = version_compare($version1_parts[0], $version2_parts[0])) == 0)
{
if (!isset($version1_parts[1]) && !isset($version2_parts[1]))
{
return 0;
}
else if (!isset($version1_parts[1]))
{
return 1;
}
else if (!isset($version2_parts[1]))
{
return -1;
}
$order = array('dev', 'alpha', 'beta', 'rc');
$order1 = NULL;
$order2 = NULL;
$i = 0;
foreach ($order as $type)
{
if (text::starts_with($version1_parts[1], $type))
{
$order1 = $i;
}
if (text::starts_with($version2_parts[1], $type))
{
$order2 = $i;
}
$i++;
}
if (($cmp = $order1 - $order2) == 0)
{
$number1 = substr($version1_parts[1], mb_strlen($order[$order1]));
$number2 = substr($version2_parts[1], mb_strlen($order[$order2]));
return intval($number1) - intval($number2);
}
}
return $cmp;
}
/**
* Makes database updates if there are any.
*
* Updates are located at /db_upgrades. Each upgrade has a name that consist
* of 'upgrade' and version (similar to version in /version.php).
*
* If any error occure during an upgrade. A location of the error is stored
* in the config DB table in a form of a midpoint. The upgrade starts from
* the stored midpoind in the next attempt to do the upgrade.
* Midpoint for before function is a number lower than zero and for after
* function is it a number greater than a count of SQL commands in the upgrade.
*
* @throws Database_Downgrate_Exception
* On not allowed database downgrade. (e.g. FN: 1.0.0 - DB: 1.1.0)
*
* @throws Old_Mechanism_Exception
* On old upgrade mechanism
*
c1bdc1c4 Michal Kliment
* @throws Not_Enabled_Upgrade_Exception
* On not enabled upgrade between DB versions
*
8baed187 Michal Kliment
* @throws Exception On any other error
*/
public static function make_db_up_to_date()
{
// detect downgrade (not on invalid DB verion - possibility of upgrade
// from old system)
if (self::is_valid_version(self::get_db_version()) &&
self::fn_version_compare() < 0)
{
throw new Database_Downgrate_Exception();
}
// database connection
$db = Database::instance();
// gets all files in /db_upgrades dir
$files = scandir('db_upgrades');
// array of available verisons
$versions = array();
// regex for file
$regex = '^upgrade_' . rtrim(ltrim(self::VERSION_REGEX, '^'), '$') . '\.php$';
// filter files
foreach ($files as $file)
{
// remove invalid files (wrong name) and value replace by version
if (!mb_eregi($regex, $file, $r))
{
continue;
}
// get version
$version = $r[1];
// remove old already installed upgrades and future upgrades
if (self::compare($version, self::get_version()) > 0 || (
!is_numeric(self::get_db_version()) &&
self::compare($version, self::get_db_version(FALSE)) <= 0
))
{
continue;
}
// add to available versions
$versions[] = $version;
}
// sort files according to version
usort($versions, 'Version::compare');
// midpoint
$midpoint = Settings::get('upgrade_midpoint_error', FALSE);
// make upgrades
foreach ($versions as $version)
{
Log::add('debug', 'Starting upgrade ' . $version);
// include include file
require 'db_upgrades/upgrade_' . $version . '.php';
c1bdc1c4 Michal Kliment
// check if the upgrade is allowed from the current DB version
if (isset($upgrade_enabled_only_from[$version]))
{
if (!is_array($upgrade_enabled_only_from[$version]))
{
$upgrade_enabled_only_from[$version] =
array($upgrade_enabled_only_from[$version]);
}
// not listed => not enabled
if (!in_array(self::get_db_version(), $upgrade_enabled_only_from[$version]))
{
$m = __('Database upgrade %s not allowed from version %s.',
array($version, self::get_db_version()));
// throw error with comment
throw new Not_Enabled_Upgrade_Exception($m);
}
else
{
Log::add('debug', 'Upgrade ' . $version . ' enabled from (' . self::get_db_version() . ')');
}
}
8baed187 Michal Kliment
// check if the upgrade is equvivalent to the current DB version
if (isset($upgrade_equal_to[$version]))
{
if (!is_array($upgrade_equal_to[$version]))
{
$upgrade_equal_to[$version] = array($upgrade_equal_to[$version]);
}
if (in_array(self::get_db_version(), $upgrade_equal_to[$version]))
{
Log::add('debug', 'Upgrade ' . $version . ' skipping (' . self::get_db_version() . ')');
// it is => so skip it
Settings::set('db_schema_version', $version);
Settings::set('upgrade_midpoint_error', '');
continue;
}
}
// check if old style is in use
if (is_numeric(self::get_db_version()) && self::get_db_version() > 0)
{
// not possible, inform user
throw new Old_Mechanism_Exception();
}
// transform version to version which may be used at PHP functions
$f_version = str_replace(array('~', '.'), array('_', '_'), $version);
// function names
$f_before = 'upgrade_' . $f_version . '_before';
$f_after = 'upgrade_' . $f_version . '_after';
c1bdc1c4 Michal Kliment
// last sql query for error message
$last_sql_query = NULL;
8baed187 Michal Kliment
// make update
try
{
// upgrade function before
if (function_exists($f_before) && (!is_numeric($midpoint) || $midpoint < 0))
{
try
{
Log::add('debug', 'Upgrade ' . $version . ' trigger before');
if (!call_user_func($f_before))
{
throw new Exception($f_before);
}
$midpoint = NULL;
}
catch (Exception $ex)
{
Settings::set('upgrade_midpoint_error', -1);
throw $ex;
}
}
else if ($midpoint < 0)
{
$midpoint = NULL;
}

// upgrade SQL
if (isset($upgrade_sql[$version]))
{
$qindex = is_numeric($midpoint) ? intval($midpoint) : 0;

// each item of array (SQL)
for (; $qindex < count($upgrade_sql[$version]); $qindex++)
{
try
{
Log::add('debug', 'Upgrade ' . $version . ' performing SQL command: ' . $qindex);
$db->query($upgrade_sql[$version][$qindex]);
}
catch (Exception $ex)
{
c1bdc1c4 Michal Kliment
$last_sql_query = $upgrade_sql[$version][$qindex];
8baed187 Michal Kliment
Settings::set('upgrade_midpoint_error', $qindex);
throw $ex;
}
}
}

// upgrade function after
if (function_exists($f_after))
{
try
{
Log::add('debug', 'Upgrade ' . $version . ' trigger after');
if (!call_user_func($f_after))
{
throw new Exception($f_after);
}
$midpoint = NULL;
}
catch (Exception $ex)
{
$top_e = count($upgrade_sql[$version]) + 1000;
Settings::set('upgrade_midpoint_error', $top_e);
throw $ex;
}
}
// clean memory
if (isset($upgrade_sql[$version]))
{
unset($upgrade_sql[$version]);
}
if (isset($upgrade_equal_to[$version]))
{
unset($upgrade_equal_to[$version]);
}

// set up db schema
Settings::set('db_schema_version', $version);

// clean midpoint if not cleaned
if (Settings::get('upgrade_midpoint_error') !== '')
{
Settings::set('upgrade_midpoint_error', '');
}
Log::add('debug', 'Upgrade ' . $version . ' complete');
}
catch (Exception $e)
{
$message = 'Upgrade DB: ' . $version . '<br /><br />'
c1bdc1c4 Michal Kliment
. 'Function: ' . $e->getMessage();
if (!empty($last_sql_query))
{
$message .= '<br />Last SQL command: ' . $last_sql_query;
}
8baed187 Michal Kliment
throw new Exception($message);
}
}
// set current version (optimalization if version has no DB update)
Settings::set('db_schema_version', self::get_version());
}
}

/**
* Exception that reflects state of not allowed downgration of database.
*/
class Database_Downgrate_Exception extends Exception
{
}

/**
* Exception that reflects state of old mechanism for updating of the database
* structure that cannot be automatically turned to new mechanism.
*/
class Old_Mechanism_Exception extends Exception
{
}
c1bdc1c4 Michal Kliment
/**
* Exception that reflects state of not enabled upgrade by using field
* upgrade_enabled_only_from in the current database upgrade.
*
* It occures when a new upgrade should be performed, but current version
* is not listed in upgrade_enabled_only_from.
*/
class Not_Enabled_Upgrade_Exception extends Exception
{
}