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() {
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 
$success
Definition: view.php:29
elgg_reset_system_cache()
Reset the system cache by deleting the caches.
Definition: cache.php:29
getUpgradeFiles($upgrade_path=null)
Returns a list of upgrade files relative to the $upgrade_path dir.
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
__construct()
Constructor.
isUpgradeLocked()
Checks if upgrade is locked.
$e
Definition: metadata.php:12
sanitise_filepath($path, $append_slash=true)
Sanitise file paths ensuring that they begin and end with slashes etc.
Definition: elgglib.php:368
setProcessedUpgrade($upgrade)
Saves a processed upgrade to a dataset.
run()
Run the upgrade process.
getUpgradeFileVersion($filename)
Returns the version of the upgrade filename.
$upgrades
Lists pending upgrades.
Definition: upgrades.php:6
dbUpgrade($version, $fromdir="", $quiet=false)
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.
releaseUpgradeMutex()
Unlocks upgrade.
Save menu items.
getUnprocessedUpgrades($upgrade_files=null, $processed_upgrades=null)
Checks if any upgrades need to be run.
_elgg_services()
Definition: autoloader.php:14
global $CONFIG
elgg global
Pointer to the global context.
Definition: elgglib.js:12
static includeCode($file)
PHP include a file with a very limited scope.
getUpgradeMutex()
Creates a table {prefix}upgrade_lock that is used as a mutex for upgrades.
$is_locked
Definition: content.php:15
elgg system_message
Wrapper function for system_messages.
Definition: elgglib.js:374
elgg_get_version($human_readable=false)
Get the current Elgg version information.
Definition: elgglib.php:976
bootstrap17to18()
Boot straps into 1.8 upgrade system from 1.7.
processUpgrades()
Upgrades Elgg Database and code.
$filename
Definition: crop.php:23
$version
Definition: version.php:14
getProcessedUpgrades()
Gets a list of processes upgrades.
upgradeCode($version, $quiet=false)
Run any php upgrade scripts which are required.