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() {
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 
$success
Definition: view.php:29
getUpgradeFiles($upgrade_path=null)
Returns a list of upgrade files relative to the $upgrade_path dir.
__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:382
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:373
elgg_get_version($human_readable=false)
Get the current Elgg version information.
Definition: elgglib.php:1001
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
elgg_flush_caches()
Flush all the registered caches.
Definition: cache.php:230
getProcessedUpgrades()
Gets a list of processes upgrades.
upgradeCode($version, $quiet=false)
Run any php upgrade scripts which are required.