Elgg  Version 1.12
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);
63 
64  $this->releaseUpgradeMutex();
65 
66  return $result;
67  }
68 
77  protected function upgradeCode($version, $quiet = false) {
78  $version = (int) $version;
79  $upgrade_path = _elgg_services()->config->get('path') . 'engine/lib/upgrades/';
80  $processed_upgrades = $this->getProcessedUpgrades();
81 
82  // upgrading from 1.7 to 1.8. Need to bootstrap.
83  if (!$processed_upgrades) {
84  $this->bootstrap17to18();
85 
86  // grab accurate processed upgrades
87  $processed_upgrades = $this->getProcessedUpgrades();
88  }
89 
90  $upgrade_files = $this->getUpgradeFiles($upgrade_path);
91 
92  if ($upgrade_files === false) {
93  return false;
94  }
95 
96  $upgrades = $this->getUnprocessedUpgrades($upgrade_files, $processed_upgrades);
97 
98  // Sort and execute
99  sort($upgrades);
100 
101  foreach ($upgrades as $upgrade) {
102  $upgrade_version = $this->getUpgradeFileVersion($upgrade);
103  $success = true;
104 
105  if ($upgrade_version <= $version) {
106  // skip upgrade files from before the installation version of Elgg
107  // because the upgrade files from before the installation version aren't
108  // added to the database.
109  continue;
110  }
111 
112  // hide all errors.
113  if ($quiet) {
114  // hide include errors as well as any exceptions that might happen
115  try {
116  if (!@self::includeCode("$upgrade_path/$upgrade")) {
117  $success = false;
118  error_log("Could not include $upgrade_path/$upgrade");
119  }
120  } catch (\Exception $e) {
121  $success = false;
122  error_log($e->getMessage());
123  }
124  } else {
125  if (!self::includeCode("$upgrade_path/$upgrade")) {
126  $success = false;
127  error_log("Could not include $upgrade_path/$upgrade");
128  }
129  }
130 
131  if ($success) {
132  // don't set the version to a lower number in instances where an upgrade
133  // has been merged from a lower version of Elgg
134  if ($upgrade_version > $version) {
135  _elgg_services()->datalist->set('version', $upgrade_version);
136  }
137 
138  // incrementally set upgrade so we know where to start if something fails.
139  $this->setProcessedUpgrade($upgrade);
140  } else {
141  return false;
142  }
143  }
144 
145  return true;
146  }
147 
154  protected static function includeCode($file) {
155  // do not remove - some upgrade scripts depend on this
156  global $CONFIG;
157 
158  return include $file;
159  }
160 
168  protected function setProcessedUpgrade($upgrade) {
169  $processed_upgrades = $this->getProcessedUpgrades();
170  $processed_upgrades[] = $upgrade;
171  $processed_upgrades = array_unique($processed_upgrades);
172  return _elgg_services()->datalist->set('processed_upgrades', serialize($processed_upgrades));
173  }
174 
180  protected function getProcessedUpgrades() {
181  $upgrades = _elgg_services()->datalist->get('processed_upgrades');
182  $unserialized = unserialize($upgrades);
183  return $unserialized;
184  }
185 
193  protected function getUpgradeFileVersion($filename) {
194  preg_match('/^([0-9]{10})([\.a-z0-9-_]+)?\.(php)$/i', $filename, $matches);
195 
196  if (isset($matches[1])) {
197  return (int) $matches[1];
198  }
199 
200  return false;
201  }
202 
209  protected function getUpgradeFiles($upgrade_path = null) {
210  if (!$upgrade_path) {
211  $upgrade_path = _elgg_services()->config->get('path') . 'engine/lib/upgrades/';
212  }
213  $upgrade_path = sanitise_filepath($upgrade_path);
214  $handle = opendir($upgrade_path);
215 
216  if (!$handle) {
217  return false;
218  }
219 
220  $upgrade_files = array();
221 
222  while ($upgrade_file = readdir($handle)) {
223  // make sure this is a wellformed upgrade.
224  if (is_dir($upgrade_path . '$upgrade_file')) {
225  continue;
226  }
227  $upgrade_version = $this->getUpgradeFileVersion($upgrade_file);
228  if (!$upgrade_version) {
229  continue;
230  }
231  $upgrade_files[] = $upgrade_file;
232  }
233 
234  sort($upgrade_files);
235 
236  return $upgrade_files;
237  }
238 
247  protected function getUnprocessedUpgrades($upgrade_files = null, $processed_upgrades = null) {
248  if ($upgrade_files === null) {
249  $upgrade_files = $this->getUpgradeFiles();
250  }
251 
252  if ($processed_upgrades === null) {
253  $processed_upgrades = unserialize(_elgg_services()->datalist->get('processed_upgrades'));
254  if (!is_array($processed_upgrades)) {
255  $processed_upgrades = array();
256  }
257  }
258 
259  $unprocessed = array_diff($upgrade_files, $processed_upgrades);
260  return $unprocessed;
261  }
262 
268  protected function processUpgrades() {
269 
270  $dbversion = (int) _elgg_services()->datalist->get('version');
271 
272  // No version number? Oh snap...this is an upgrade from a clean installation < 1.7.
273  // Run all upgrades without error reporting and hope for the best.
274  // See https://github.com/elgg/elgg/issues/1432 for more.
275  $quiet = !$dbversion;
276 
277  // Note: Database upgrades are deprecated as of 1.8. Use code upgrades. See #1433
278  if ($this->dbUpgrade($dbversion, '', $quiet)) {
279  system_message(_elgg_services()->translator->translate('upgrade:db'));
280  }
281 
282  if ($this->upgradeCode($dbversion, $quiet)) {
283  system_message(_elgg_services()->translator->translate('upgrade:core'));
284 
285  // Now we trigger an event to give the option for plugins to do something
286  $upgrade_details = new \stdClass;
287  $upgrade_details->from = $dbversion;
288  $upgrade_details->to = elgg_get_version();
289 
290  _elgg_services()->events->trigger('upgrade', 'upgrade', $upgrade_details);
291 
292  return true;
293  }
294 
295  return false;
296  }
297 
307  protected function bootstrap17to18() {
308  $db_version = (int) _elgg_services()->datalist->get('version');
309 
310  // the 1.8 upgrades before the upgrade system change that are interspersed with 1.7 upgrades.
311  $upgrades_18 = array(
312  '2010111501.php',
313  '2010121601.php',
314  '2010121602.php',
315  '2010121701.php',
316  '2010123101.php',
317  '2011010101.php',
318  );
319 
320  $upgrade_files = $this->getUpgradeFiles();
321  $processed_upgrades = array();
322 
323  foreach ($upgrade_files as $upgrade_file) {
324  // ignore if not in 1.7 format or if it's a 1.8 upgrade
325  if (in_array($upgrade_file, $upgrades_18) || !preg_match("/[0-9]{10}\.php/", $upgrade_file)) {
326  continue;
327  }
328 
329  $upgrade_version = $this->getUpgradeFileVersion($upgrade_file);
330 
331  // this has already been run in a previous 1.7.X -> 1.7.X upgrade
332  if ($upgrade_version < $db_version) {
333  $this->setProcessedUpgrade($upgrade_file);
334  }
335  }
336  }
337 
343  protected function getUpgradeMutex() {
344 
345 
346  if (!$this->isUpgradeLocked()) {
347  // lock it
348  _elgg_services()->db->insertData("create table {$this->CONFIG->dbprefix}upgrade_lock (id INT)");
349  _elgg_services()->logger->notice('Locked for upgrade.');
350  return true;
351  }
352 
353  _elgg_services()->logger->warn('Cannot lock for upgrade: already locked');
354  return false;
355  }
356 
362  public function releaseUpgradeMutex() {
363 
364  _elgg_services()->db->deleteData("drop table {$this->CONFIG->dbprefix}upgrade_lock");
365  _elgg_services()->logger->notice('Upgrade unlocked.');
366  }
367 
373  public function isUpgradeLocked() {
374 
375 
376  $is_locked = count(_elgg_services()->db->getData("SHOW TABLES LIKE '{$this->CONFIG->dbprefix}upgrade_lock'"));
377 
378  return (bool)$is_locked;
379  }
380 
403  protected function dbUpgrade($version, $fromdir = "", $quiet = false) {
404 
405 
406  $version = (int) $version;
407 
408  if (!$fromdir) {
409  $fromdir = $this->CONFIG->path . 'engine/schema/upgrades/';
410  }
411 
412  $i = 0;
413 
414  if ($handle = opendir($fromdir)) {
415  $sqlupgrades = array();
416 
417  while ($sqlfile = readdir($handle)) {
418  if (!is_dir($fromdir . $sqlfile)) {
419  if (preg_match('/^([0-9]{10})\.(sql)$/', $sqlfile, $matches)) {
420  $sql_version = (int) $matches[1];
421  if ($sql_version > $version) {
422  $sqlupgrades[] = $sqlfile;
423  }
424  }
425  }
426  }
427 
428  asort($sqlupgrades);
429 
430  if (sizeof($sqlupgrades) > 0) {
431  foreach ($sqlupgrades as $sqlfile) {
432 
433  // hide all errors.
434  if ($quiet) {
435  try {
436  _elgg_services()->db->runSqlScript($fromdir . $sqlfile);
437  } catch (\DatabaseException $e) {
438  error_log($e->getmessage());
439  }
440  } else {
441  _elgg_services()->db->runSqlScript($fromdir . $sqlfile);
442  }
443  $i++;
444  }
445  }
446  }
447 
448  return $i;
449  }
450 
451 }
452 
$filename
Definition: crop.php:23
_elgg_services()
Definition: autoloader.php:14
$success
Definition: view.php:29
elgg_flush_caches()
Flush all the registered caches.
Definition: cache.php:230
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:382
system_message($message)
Display a system message on next page load.
Definition: elgglib.php:456
elgg_get_version($human_readable=false)
Get the current Elgg version information.
Definition: elgglib.php:1001
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