123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- <?php
- namespace Elgg;
- /**
- * Upgrade service for Elgg
- *
- * This is a straight port of the procedural code used for upgrading before
- * Elgg 1.9.
- *
- * @access private
- *
- * @package Elgg.Core
- * @subpackage Upgrade
- */
- class UpgradeService {
- /**
- * Global Elgg configuration
- *
- * @var \stdClass
- */
- private $CONFIG;
- /**
- * Constructor
- */
- public function __construct() {
- global $CONFIG;
- $this->CONFIG = $CONFIG;
- }
- /**
- * Run the upgrade process
- *
- * @return array
- */
- public function run() {
- $result = array(
- 'failure' => false,
- 'reason' => '',
- );
- // prevent someone from running the upgrade script in parallel (see #4643)
- if (!$this->getUpgradeMutex()) {
- $result['failure'] = true;
- $result['reason'] = _elgg_services()->translator->translate('upgrade:locked');
- return $result;
- }
- // disable the system log for upgrades to avoid exceptions when the schema changes.
- _elgg_services()->events->unregisterHandler('log', 'systemlog', 'system_log_default_logger');
- _elgg_services()->events->unregisterHandler('all', 'all', 'system_log_listener');
- // turn off time limit
- set_time_limit(0);
- if ($this->getUnprocessedUpgrades()) {
- $this->processUpgrades();
- }
- _elgg_services()->events->trigger('upgrade', 'system', null);
- elgg_flush_caches();
- $this->releaseUpgradeMutex();
- return $result;
- }
- /**
- * Run any php upgrade scripts which are required
- *
- * @param int $version Version upgrading from.
- * @param bool $quiet Suppress errors. Don't use this.
- *
- * @return bool
- */
- protected function upgradeCode($version, $quiet = false) {
- $version = (int) $version;
- $upgrade_path = _elgg_services()->config->get('path') . 'engine/lib/upgrades/';
- $processed_upgrades = $this->getProcessedUpgrades();
- // upgrading from 1.7 to 1.8. Need to bootstrap.
- if (!$processed_upgrades) {
- $this->bootstrap17to18();
- // grab accurate processed upgrades
- $processed_upgrades = $this->getProcessedUpgrades();
- }
- $upgrade_files = $this->getUpgradeFiles($upgrade_path);
- if ($upgrade_files === false) {
- return false;
- }
- $upgrades = $this->getUnprocessedUpgrades($upgrade_files, $processed_upgrades);
- // Sort and execute
- sort($upgrades);
- foreach ($upgrades as $upgrade) {
- $upgrade_version = $this->getUpgradeFileVersion($upgrade);
- $success = true;
- if ($upgrade_version <= $version) {
- // skip upgrade files from before the installation version of Elgg
- // because the upgrade files from before the installation version aren't
- // added to the database.
- continue;
- }
-
- // hide all errors.
- if ($quiet) {
- // hide include errors as well as any exceptions that might happen
- try {
- if (!@self::includeCode("$upgrade_path/$upgrade")) {
- $success = false;
- error_log("Could not include $upgrade_path/$upgrade");
- }
- } catch (\Exception $e) {
- $success = false;
- error_log($e->getMessage());
- }
- } else {
- if (!self::includeCode("$upgrade_path/$upgrade")) {
- $success = false;
- error_log("Could not include $upgrade_path/$upgrade");
- }
- }
- if ($success) {
- // don't set the version to a lower number in instances where an upgrade
- // has been merged from a lower version of Elgg
- if ($upgrade_version > $version) {
- _elgg_services()->datalist->set('version', $upgrade_version);
- }
- // incrementally set upgrade so we know where to start if something fails.
- $this->setProcessedUpgrade($upgrade);
- } else {
- return false;
- }
- }
- return true;
- }
- /**
- * PHP include a file with a very limited scope
- *
- * @param string $file File path to include
- * @return mixed
- */
- protected static function includeCode($file) {
- // do not remove - some upgrade scripts depend on this
- global $CONFIG;
- return include $file;
- }
- /**
- * Saves a processed upgrade to a dataset.
- *
- * @param string $upgrade Filename of the processed upgrade
- * (not the path, just the file)
- * @return bool
- */
- protected function setProcessedUpgrade($upgrade) {
- $processed_upgrades = $this->getProcessedUpgrades();
- $processed_upgrades[] = $upgrade;
- $processed_upgrades = array_unique($processed_upgrades);
- return _elgg_services()->datalist->set('processed_upgrades', serialize($processed_upgrades));
- }
- /**
- * Gets a list of processes upgrades
- *
- * @return mixed Array of processed upgrade filenames or false
- */
- protected function getProcessedUpgrades() {
- $upgrades = _elgg_services()->datalist->get('processed_upgrades');
- $unserialized = unserialize($upgrades);
- return $unserialized;
- }
- /**
- * Returns the version of the upgrade filename.
- *
- * @param string $filename The upgrade filename. No full path.
- * @return int|false
- * @since 1.8.0
- */
- protected function getUpgradeFileVersion($filename) {
- preg_match('/^([0-9]{10})([\.a-z0-9-_]+)?\.(php)$/i', $filename, $matches);
- if (isset($matches[1])) {
- return (int) $matches[1];
- }
- return false;
- }
- /**
- * Returns a list of upgrade files relative to the $upgrade_path dir.
- *
- * @param string $upgrade_path The up
- * @return array|false
- */
- protected function getUpgradeFiles($upgrade_path = null) {
- if (!$upgrade_path) {
- $upgrade_path = _elgg_services()->config->get('path') . 'engine/lib/upgrades/';
- }
- $upgrade_path = sanitise_filepath($upgrade_path);
- $handle = opendir($upgrade_path);
- if (!$handle) {
- return false;
- }
- $upgrade_files = array();
- while ($upgrade_file = readdir($handle)) {
- // make sure this is a wellformed upgrade.
- if (is_dir($upgrade_path . '$upgrade_file')) {
- continue;
- }
- $upgrade_version = $this->getUpgradeFileVersion($upgrade_file);
- if (!$upgrade_version) {
- continue;
- }
- $upgrade_files[] = $upgrade_file;
- }
- sort($upgrade_files);
- return $upgrade_files;
- }
- /**
- * Checks if any upgrades need to be run.
- *
- * @param null|array $upgrade_files Optional upgrade files
- * @param null|array $processed_upgrades Optional processed upgrades
- *
- * @return array
- */
- protected function getUnprocessedUpgrades($upgrade_files = null, $processed_upgrades = null) {
- if ($upgrade_files === null) {
- $upgrade_files = $this->getUpgradeFiles();
- }
- if ($processed_upgrades === null) {
- $processed_upgrades = unserialize(_elgg_services()->datalist->get('processed_upgrades'));
- if (!is_array($processed_upgrades)) {
- $processed_upgrades = array();
- }
- }
- $unprocessed = array_diff($upgrade_files, $processed_upgrades);
- return $unprocessed;
- }
- /**
- * Upgrades Elgg Database and code
- *
- * @return bool
- */
- protected function processUpgrades() {
- $dbversion = (int) _elgg_services()->datalist->get('version');
- // No version number? Oh snap...this is an upgrade from a clean installation < 1.7.
- // Run all upgrades without error reporting and hope for the best.
- // See https://github.com/elgg/elgg/issues/1432 for more.
- $quiet = !$dbversion;
- // Note: Database upgrades are deprecated as of 1.8. Use code upgrades. See #1433
- if ($this->dbUpgrade($dbversion, '', $quiet)) {
- system_message(_elgg_services()->translator->translate('upgrade:db'));
- }
- if ($this->upgradeCode($dbversion, $quiet)) {
- system_message(_elgg_services()->translator->translate('upgrade:core'));
- // Now we trigger an event to give the option for plugins to do something
- $upgrade_details = new \stdClass;
- $upgrade_details->from = $dbversion;
- $upgrade_details->to = elgg_get_version();
- _elgg_services()->events->trigger('upgrade', 'upgrade', $upgrade_details);
- return true;
- }
- return false;
- }
- /**
- * Boot straps into 1.8 upgrade system from 1.7
- *
- * This runs all the 1.7 upgrades, then sets the processed_upgrades to all existing 1.7 upgrades.
- * Control is then passed back to the main upgrade function which detects and runs the
- * 1.8 upgrades, regardless of filename convention.
- *
- * @return bool
- */
- protected function bootstrap17to18() {
- $db_version = (int) _elgg_services()->datalist->get('version');
- // the 1.8 upgrades before the upgrade system change that are interspersed with 1.7 upgrades.
- $upgrades_18 = array(
- '2010111501.php',
- '2010121601.php',
- '2010121602.php',
- '2010121701.php',
- '2010123101.php',
- '2011010101.php',
- );
- $upgrade_files = $this->getUpgradeFiles();
- $processed_upgrades = array();
- foreach ($upgrade_files as $upgrade_file) {
- // ignore if not in 1.7 format or if it's a 1.8 upgrade
- if (in_array($upgrade_file, $upgrades_18) || !preg_match("/[0-9]{10}\.php/", $upgrade_file)) {
- continue;
- }
- $upgrade_version = $this->getUpgradeFileVersion($upgrade_file);
- // this has already been run in a previous 1.7.X -> 1.7.X upgrade
- if ($upgrade_version < $db_version) {
- $this->setProcessedUpgrade($upgrade_file);
- }
- }
- }
- /**
- * Creates a table {prefix}upgrade_lock that is used as a mutex for upgrades.
- *
- * @return bool
- */
- protected function getUpgradeMutex() {
-
- if (!$this->isUpgradeLocked()) {
- // lock it
- _elgg_services()->db->insertData("create table {$this->CONFIG->dbprefix}upgrade_lock (id INT)");
- _elgg_services()->logger->notice('Locked for upgrade.');
- return true;
- }
- _elgg_services()->logger->warn('Cannot lock for upgrade: already locked');
- return false;
- }
- /**
- * Unlocks upgrade.
- *
- * @return void
- */
- public function releaseUpgradeMutex() {
-
- _elgg_services()->db->deleteData("drop table {$this->CONFIG->dbprefix}upgrade_lock");
- _elgg_services()->logger->notice('Upgrade unlocked.');
- }
- /**
- * Checks if upgrade is locked
- *
- * @return bool
- */
- public function isUpgradeLocked() {
-
- $is_locked = count(_elgg_services()->db->getData("SHOW TABLES LIKE '{$this->CONFIG->dbprefix}upgrade_lock'"));
- return (bool)$is_locked;
- }
- /**
- * ***************************************************************************
- * NOTE: If this is ever removed from Elgg, sites lose the ability to upgrade
- * from 1.7.x and earlier to the latest version of Elgg without upgrading to
- * 1.8 first.
- * ***************************************************************************
- *
- * Upgrade the database schema in an ordered sequence.
- *
- * Executes all upgrade files in elgg/engine/schema/upgrades/ in sequential order.
- * Upgrade files must be in the standard Elgg release format of YYYYMMDDII.sql
- * where II is an incrementor starting from 01.
- *
- * Files that are < $version will be ignored.
- *
- * @param int $version The version you are upgrading from in the format YYYYMMDDII.
- * @param string $fromdir Optional directory to load upgrades from. default: engine/schema/upgrades/
- * @param bool $quiet If true, suppress all error messages. Only use for the upgrade from <=1.6.
- *
- * @return int The number of upgrades run.
- * @deprecated 1.8 Use PHP upgrades for sql changes.
- */
- protected function dbUpgrade($version, $fromdir = "", $quiet = false) {
-
- $version = (int) $version;
- if (!$fromdir) {
- $fromdir = $this->CONFIG->path . 'engine/schema/upgrades/';
- }
- $i = 0;
- if ($handle = opendir($fromdir)) {
- $sqlupgrades = array();
- while ($sqlfile = readdir($handle)) {
- if (!is_dir($fromdir . $sqlfile)) {
- if (preg_match('/^([0-9]{10})\.(sql)$/', $sqlfile, $matches)) {
- $sql_version = (int) $matches[1];
- if ($sql_version > $version) {
- $sqlupgrades[] = $sqlfile;
- }
- }
- }
- }
- asort($sqlupgrades);
- if (sizeof($sqlupgrades) > 0) {
- foreach ($sqlupgrades as $sqlfile) {
- // hide all errors.
- if ($quiet) {
- try {
- _elgg_services()->db->runSqlScript($fromdir . $sqlfile);
- } catch (\DatabaseException $e) {
- error_log($e->getmessage());
- }
- } else {
- _elgg_services()->db->runSqlScript($fromdir . $sqlfile);
- }
- $i++;
- }
- }
- }
- return $i;
- }
- }
|