Elgg  Version 1.11
UpgradeService.php
Go to the documentation of this file.
1 <?php
2 namespace Elgg;
3 
16 
22  private $CONFIG;
23 
27  public function __construct() {
28  global $CONFIG;
29  $this->CONFIG = $CONFIG;
30  }
31 
37  public function run() {
38  $result = array(
39  'failure' => false,
40  'reason' => '',
41  );
42 
43  // prevent someone from running the upgrade script in parallel (see #4643)
44  if (!$this->getUpgradeMutex()) {
45  $result['failure'] = true;
46  $result['reason'] = _elgg_services()->translator->translate('upgrade:locked');
47  return $result;
48  }
49 
50  // disable the system log for upgrades to avoid exceptions when the schema changes.
51  _elgg_services()->events->unregisterHandler('log', 'systemlog', 'system_log_default_logger');
52  _elgg_services()->events->unregisterHandler('all', 'all', 'system_log_listener');
53 
54  // turn off time limit
55  set_time_limit(0);
56 
57  if ($this->getUnprocessedUpgrades()) {
58  $this->processUpgrades();
59  }
60 
61  _elgg_services()->events->trigger('upgrade', 'system', null);
64 
65  $this->releaseUpgradeMutex();
66 
67  return $result;
68  }
69 
78  protected function upgradeCode($version, $quiet = false) {
79  $version = (int) $version;
80  $upgrade_path = _elgg_services()->config->get('path') . 'engine/lib/upgrades/';
81  $processed_upgrades = $this->getProcessedUpgrades();
82 
83  // upgrading from 1.7 to 1.8. Need to bootstrap.
84  if (!$processed_upgrades) {
85  $this->bootstrap17to18();
86 
87  // grab accurate processed upgrades
88  $processed_upgrades = $this->getProcessedUpgrades();
89  }
90 
91  $upgrade_files = $this->getUpgradeFiles($upgrade_path);
92 
93  if ($upgrade_files === false) {
94  return false;
95  }
96 
97  $upgrades = $this->getUnprocessedUpgrades($upgrade_files, $processed_upgrades);
98 
99  // Sort and execute
100  sort($upgrades);
101 
102  foreach ($upgrades as $upgrade) {
103  $upgrade_version = $this->getUpgradeFileVersion($upgrade);
104  $success = true;
105 
106  if ($upgrade_version <= $version) {
107  // skip upgrade files from before the installation version of Elgg
108  // because the upgrade files from before the installation version aren't
109  // added to the database.
110  continue;
111  }
112 
113  // hide all errors.
114  if ($quiet) {
115  // hide include errors as well as any exceptions that might happen
116  try {
117  if (!@self::includeCode("$upgrade_path/$upgrade")) {
118  $success = false;
119  error_log("Could not include $upgrade_path/$upgrade");
120  }
121  } catch (\Exception $e) {
122  $success = false;
123  error_log($e->getMessage());
124  }
125  } else {
126  if (!self::includeCode("$upgrade_path/$upgrade")) {
127  $success = false;
128  error_log("Could not include $upgrade_path/$upgrade");
129  }
130  }
131 
132  if ($success) {
133  // don't set the version to a lower number in instances where an upgrade
134  // has been merged from a lower version of Elgg
135  if ($upgrade_version > $version) {
136  _elgg_services()->datalist->set('version', $upgrade_version);
137  }
138 
139  // incrementally set upgrade so we know where to start if something fails.
140  $this->setProcessedUpgrade($upgrade);
141  } else {
142  return false;
143  }
144  }
145 
146  return true;
147  }
148 
155  protected static function includeCode($file) {
156  // do not remove - some upgrade scripts depend on this
157  global $CONFIG;
158 
159  return include $file;
160  }
161 
169  protected function setProcessedUpgrade($upgrade) {
170  $processed_upgrades = $this->getProcessedUpgrades();
171  $processed_upgrades[] = $upgrade;
172  $processed_upgrades = array_unique($processed_upgrades);
173  return _elgg_services()->datalist->set('processed_upgrades', serialize($processed_upgrades));
174  }
175 
181  protected function getProcessedUpgrades() {
182  $upgrades = _elgg_services()->datalist->get('processed_upgrades');
183  $unserialized = unserialize($upgrades);
184  return $unserialized;
185  }
186 
194  protected function getUpgradeFileVersion($filename) {
195  preg_match('/^([0-9]{10})([\.a-z0-9-_]+)?\.(php)$/i', $filename, $matches);
196 
197  if (isset($matches[1])) {
198  return (int) $matches[1];
199  }
200 
201  return false;
202  }
203 
210  protected function getUpgradeFiles($upgrade_path = null) {
211  if (!$upgrade_path) {
212  $upgrade_path = _elgg_services()->config->get('path') . 'engine/lib/upgrades/';
213  }
214  $upgrade_path = sanitise_filepath($upgrade_path);
215  $handle = opendir($upgrade_path);
216 
217  if (!$handle) {
218  return false;
219  }
220 
221  $upgrade_files = array();
222 
223  while ($upgrade_file = readdir($handle)) {
224  // make sure this is a wellformed upgrade.
225  if (is_dir($upgrade_path . '$upgrade_file')) {
226  continue;
227  }
228  $upgrade_version = $this->getUpgradeFileVersion($upgrade_file);
229  if (!$upgrade_version) {
230  continue;
231  }
232  $upgrade_files[] = $upgrade_file;
233  }
234 
235  sort($upgrade_files);
236 
237  return $upgrade_files;
238  }
239 
248  protected function getUnprocessedUpgrades($upgrade_files = null, $processed_upgrades = null) {
249  if ($upgrade_files === null) {
250  $upgrade_files = $this->getUpgradeFiles();
251  }
252 
253  if ($processed_upgrades === null) {
254  $processed_upgrades = unserialize(_elgg_services()->datalist->get('processed_upgrades'));
255  if (!is_array($processed_upgrades)) {
256  $processed_upgrades = array();
257  }
258  }
259 
260  $unprocessed = array_diff($upgrade_files, $processed_upgrades);
261  return $unprocessed;
262  }
263 
269  protected function processUpgrades() {
270 
271  $dbversion = (int) _elgg_services()->datalist->get('version');
272 
273  // No version number? Oh snap...this is an upgrade from a clean installation < 1.7.
274  // Run all upgrades without error reporting and hope for the best.
275  // See https://github.com/elgg/elgg/issues/1432 for more.
276  $quiet = !$dbversion;
277 
278  // Note: Database upgrades are deprecated as of 1.8. Use code upgrades. See #1433
279  if ($this->dbUpgrade($dbversion, '', $quiet)) {
280  system_message(_elgg_services()->translator->translate('upgrade:db'));
281  }
282 
283  if ($this->upgradeCode($dbversion, $quiet)) {
284  system_message(_elgg_services()->translator->translate('upgrade:core'));
285 
286  // Now we trigger an event to give the option for plugins to do something
287  $upgrade_details = new \stdClass;
288  $upgrade_details->from = $dbversion;
289  $upgrade_details->to = elgg_get_version();
290 
291  _elgg_services()->events->trigger('upgrade', 'upgrade', $upgrade_details);
292 
293  return true;
294  }
295 
296  return false;
297  }
298 
308  protected function bootstrap17to18() {
309  $db_version = (int) _elgg_services()->datalist->get('version');
310 
311  // the 1.8 upgrades before the upgrade system change that are interspersed with 1.7 upgrades.
312  $upgrades_18 = array(
313  '2010111501.php',
314  '2010121601.php',
315  '2010121602.php',
316  '2010121701.php',
317  '2010123101.php',
318  '2011010101.php',
319  );
320 
321  $upgrade_files = $this->getUpgradeFiles();
322  $processed_upgrades = array();
323 
324  foreach ($upgrade_files as $upgrade_file) {
325  // ignore if not in 1.7 format or if it's a 1.8 upgrade
326  if (in_array($upgrade_file, $upgrades_18) || !preg_match("/[0-9]{10}\.php/", $upgrade_file)) {
327  continue;
328  }
329 
330  $upgrade_version = $this->getUpgradeFileVersion($upgrade_file);
331 
332  // this has already been run in a previous 1.7.X -> 1.7.X upgrade
333  if ($upgrade_version < $db_version) {
334  $this->setProcessedUpgrade($upgrade_file);
335  }
336  }
337  }
338 
344  protected function getUpgradeMutex() {
345 
346 
347  if (!$this->isUpgradeLocked()) {
348  // lock it
349  _elgg_services()->db->insertData("create table {$this->CONFIG->dbprefix}upgrade_lock (id INT)");
350  _elgg_services()->logger->notice('Locked for upgrade.');
351  return true;
352  }
353 
354  _elgg_services()->logger->warn('Cannot lock for upgrade: already locked');
355  return false;
356  }
357 
363  public function releaseUpgradeMutex() {
364 
365  _elgg_services()->db->deleteData("drop table {$this->CONFIG->dbprefix}upgrade_lock");
366  _elgg_services()->logger->notice('Upgrade unlocked.');
367  }
368 
374  public function isUpgradeLocked() {
375 
376 
377  $is_locked = count(_elgg_services()->db->getData("SHOW TABLES LIKE '{$this->CONFIG->dbprefix}upgrade_lock'"));
378 
379  return (bool)$is_locked;
380  }
381 
404  protected function dbUpgrade($version, $fromdir = "", $quiet = false) {
405 
406 
407  $version = (int) $version;
408 
409  if (!$fromdir) {
410  $fromdir = $this->CONFIG->path . 'engine/schema/upgrades/';
411  }
412 
413  $i = 0;
414 
415  if ($handle = opendir($fromdir)) {
416  $sqlupgrades = array();
417 
418  while ($sqlfile = readdir($handle)) {
419  if (!is_dir($fromdir . $sqlfile)) {
420  if (preg_match('/^([0-9]{10})\.(sql)$/', $sqlfile, $matches)) {
421  $sql_version = (int) $matches[1];
422  if ($sql_version > $version) {
423  $sqlupgrades[] = $sqlfile;
424  }
425  }
426  }
427  }
428 
429  asort($sqlupgrades);
430 
431  if (sizeof($sqlupgrades) > 0) {
432  foreach ($sqlupgrades as $sqlfile) {
433 
434  // hide all errors.
435  if ($quiet) {
436  try {
437  _elgg_services()->db->runSqlScript($fromdir . $sqlfile);
438  } catch (\DatabaseException $e) {
439  error_log($e->getmessage());
440  }
441  } else {
442  _elgg_services()->db->runSqlScript($fromdir . $sqlfile);
443  }
444  $i++;
445  }
446  }
447  }
448 
449  return $i;
450  }
451 
452 }
453 
$filename
Definition: crop.php:23
_elgg_services()
Definition: autoloader.php:14
$success
Definition: view.php:29
elgg_reset_system_cache()
Reset the system cache by deleting the caches.
Definition: cache.php:29
elgg_invalidate_simplecache()
Deletes all cached views in the simplecache and sets the lastcache and lastupdate time to 0 for every...
Definition: cache.php:220
run()
Run the upgrade process.
__construct()
Constructor.
getUpgradeMutex()
Creates a table {prefix}upgrade_lock that is used as a mutex for upgrades.
processUpgrades()
Upgrades Elgg Database and code.
static includeCode($file)
PHP include a file with a very limited scope.
getProcessedUpgrades()
Gets a list of processes upgrades.
isUpgradeLocked()
Checks if upgrade is locked.
getUpgradeFiles($upgrade_path=null)
Returns a list of upgrade files relative to the $upgrade_path dir.
dbUpgrade($version, $fromdir="", $quiet=false)
upgradeCode($version, $quiet=false)
Run any php upgrade scripts which are required.
releaseUpgradeMutex()
Unlocks upgrade.
bootstrap17to18()
Boot straps into 1.8 upgrade system from 1.7.
getUnprocessedUpgrades($upgrade_files=null, $processed_upgrades=null)
Checks if any upgrades need to be run.
getUpgradeFileVersion($filename)
Returns the version of the upgrade filename.
setProcessedUpgrade($upgrade)
Saves a processed upgrade to a dataset.
sanitise_filepath($path, $append_slash=true)
Sanitise file paths ensuring that they begin and end with slashes etc.
Definition: elgglib.php:368
system_message($message)
Display a system message on next page load.
Definition: elgglib.php:442
elgg_get_version($human_readable=false)
Get the current Elgg version information.
Definition: elgglib.php:976
Save menu items.
global $CONFIG
$upgrades
Lists pending upgrades.
Definition: upgrades.php:6
$version
Definition: version.php:14
$e
Definition: metadata.php:12
$is_locked
Definition: content.php:15