Elgg  Version 1.9
UpgradeService.php
Go to the documentation of this file.
1 <?php
2 
15 
21  public function run() {
22  $result = array(
23  'failure' => false,
24  'reason' => '',
25  );
26 
27  // prevent someone from running the upgrade script in parallel (see #4643)
28  if (!$this->getUpgradeMutex()) {
29  $result['failure'] = true;
30  $result['reason'] = elgg_echo('upgrade:locked');
31  return $result;
32  }
33 
34  // disable the system log for upgrades to avoid exceptions when the schema changes.
35  elgg_unregister_event_handler('log', 'systemlog', 'system_log_default_logger');
36  elgg_unregister_event_handler('all', 'all', 'system_log_listener');
37 
38  // turn off time limit
39  set_time_limit(0);
40 
41  if ($this->getUnprocessedUpgrades()) {
42  $this->processUpgrades();
43  }
44 
45  elgg_trigger_event('upgrade', 'system', null);
48 
49  $this->releaseUpgradeMutex();
50 
51  return $result;
52  }
53 
62  protected function upgradeCode($version, $quiet = false) {
63  $version = (int) $version;
64  $upgrade_path = elgg_get_config('path') . 'engine/lib/upgrades/';
65  $processed_upgrades = $this->getProcessedUpgrades();
66 
67  // upgrading from 1.7 to 1.8. Need to bootstrap.
68  if (!$processed_upgrades) {
69  $this->bootstrap17to18();
70 
71  // grab accurate processed upgrades
72  $processed_upgrades = $this->getProcessedUpgrades();
73  }
74 
75  $upgrade_files = $this->getUpgradeFiles($upgrade_path);
76 
77  if ($upgrade_files === false) {
78  return false;
79  }
80 
81  $upgrades = $this->getUnprocessedUpgrades($upgrade_files, $processed_upgrades);
82 
83  // Sort and execute
84  sort($upgrades);
85 
86  foreach ($upgrades as $upgrade) {
87  $upgrade_version = $this->getUpgradeFileVersion($upgrade);
88  $success = true;
89 
90  // hide all errors.
91  if ($quiet) {
92  // hide include errors as well as any exceptions that might happen
93  try {
94  if (!@self::includeCode("$upgrade_path/$upgrade")) {
95  $success = false;
96  error_log("Could not include $upgrade_path/$upgrade");
97  }
98  } catch (Exception $e) {
99  $success = false;
100  error_log($e->getMessage());
101  }
102  } else {
103  if (!self::includeCode("$upgrade_path/$upgrade")) {
104  $success = false;
105  error_log("Could not include $upgrade_path/$upgrade");
106  }
107  }
108 
109  if ($success) {
110  // don't set the version to a lower number in instances where an upgrade
111  // has been merged from a lower version of Elgg
112  if ($upgrade_version > $version) {
113  datalist_set('version', $upgrade_version);
114  }
115 
116  // incrementally set upgrade so we know where to start if something fails.
117  $this->setProcessedUpgrade($upgrade);
118  } else {
119  return false;
120  }
121  }
122 
123  return true;
124  }
125 
132  protected static function includeCode($file) {
133  // do not remove - some upgrade scripts depend on this
134  global $CONFIG;
135 
136  return include $file;
137  }
138 
146  protected function setProcessedUpgrade($upgrade) {
147  $processed_upgrades = $this->getProcessedUpgrades();
148  $processed_upgrades[] = $upgrade;
149  $processed_upgrades = array_unique($processed_upgrades);
150  return datalist_set('processed_upgrades', serialize($processed_upgrades));
151  }
152 
158  protected function getProcessedUpgrades() {
159  $upgrades = datalist_get('processed_upgrades');
160  $unserialized = unserialize($upgrades);
161  return $unserialized;
162  }
163 
171  protected function getUpgradeFileVersion($filename) {
172  preg_match('/^([0-9]{10})([\.a-z0-9-_]+)?\.(php)$/i', $filename, $matches);
173 
174  if (isset($matches[1])) {
175  return (int) $matches[1];
176  }
177 
178  return false;
179  }
180 
187  protected function getUpgradeFiles($upgrade_path = null) {
188  if (!$upgrade_path) {
189  $upgrade_path = elgg_get_config('path') . 'engine/lib/upgrades/';
190  }
191  $upgrade_path = sanitise_filepath($upgrade_path);
192  $handle = opendir($upgrade_path);
193 
194  if (!$handle) {
195  return false;
196  }
197 
198  $upgrade_files = array();
199 
200  while ($upgrade_file = readdir($handle)) {
201  // make sure this is a wellformed upgrade.
202  if (is_dir($upgrade_path . '$upgrade_file')) {
203  continue;
204  }
205  $upgrade_version = $this->getUpgradeFileVersion($upgrade_file);
206  if (!$upgrade_version) {
207  continue;
208  }
209  $upgrade_files[] = $upgrade_file;
210  }
211 
212  sort($upgrade_files);
213 
214  return $upgrade_files;
215  }
216 
225  protected function getUnprocessedUpgrades($upgrade_files = null, $processed_upgrades = null) {
226  if ($upgrade_files === null) {
227  $upgrade_files = $this->getUpgradeFiles();
228  }
229 
230  if ($processed_upgrades === null) {
231  $processed_upgrades = unserialize(datalist_get('processed_upgrades'));
232  if (!is_array($processed_upgrades)) {
233  $processed_upgrades = array();
234  }
235  }
236 
237  $unprocessed = array_diff($upgrade_files, $processed_upgrades);
238  return $unprocessed;
239  }
240 
246  protected function processUpgrades() {
247 
248  $dbversion = (int) datalist_get('version');
249 
250  // No version number? Oh snap...this is an upgrade from a clean installation < 1.7.
251  // Run all upgrades without error reporting and hope for the best.
252  // See https://github.com/elgg/elgg/issues/1432 for more.
253  $quiet = !$dbversion;
254 
255  // Note: Database upgrades are deprecated as of 1.8. Use code upgrades. See #1433
256  if ($this->dbUpgrade($dbversion, '', $quiet)) {
257  system_message(elgg_echo('upgrade:db'));
258  }
259 
260  if ($this->upgradeCode($dbversion, $quiet)) {
261  system_message(elgg_echo('upgrade:core'));
262 
263  // Now we trigger an event to give the option for plugins to do something
264  $upgrade_details = new stdClass;
265  $upgrade_details->from = $dbversion;
266  $upgrade_details->to = elgg_get_version();
267 
268  elgg_trigger_event('upgrade', 'upgrade', $upgrade_details);
269 
270  return true;
271  }
272 
273  return false;
274  }
275 
285  protected function bootstrap17to18() {
286  $db_version = (int) datalist_get('version');
287 
288  // the 1.8 upgrades before the upgrade system change that are interspersed with 1.7 upgrades.
289  $upgrades_18 = array(
290  '2010111501.php',
291  '2010121601.php',
292  '2010121602.php',
293  '2010121701.php',
294  '2010123101.php',
295  '2011010101.php',
296  );
297 
298  $upgrade_files = $this->getUpgradeFiles();
299  $processed_upgrades = array();
300 
301  foreach ($upgrade_files as $upgrade_file) {
302  // ignore if not in 1.7 format or if it's a 1.8 upgrade
303  if (in_array($upgrade_file, $upgrades_18) || !preg_match("/[0-9]{10}\.php/", $upgrade_file)) {
304  continue;
305  }
306 
307  $upgrade_version = $this->getUpgradeFileVersion($upgrade_file);
308 
309  // this has already been run in a previous 1.7.X -> 1.7.X upgrade
310  if ($upgrade_version < $db_version) {
311  $this->setProcessedUpgrade($upgrade_file);
312  }
313  }
314  }
315 
321  protected function getUpgradeMutex() {
322  global $CONFIG;
323 
324  if (!$this->isUpgradeLocked()) {
325  // lock it
326  insert_data("create table {$CONFIG->dbprefix}upgrade_lock (id INT)");
327  elgg_log('Locked for upgrade.', 'NOTICE');
328  return true;
329  }
330 
331  elgg_log('Cannot lock for upgrade: already locked.', 'WARNING');
332  return false;
333  }
334 
340  public function releaseUpgradeMutex() {
341  global $CONFIG;
342  delete_data("drop table {$CONFIG->dbprefix}upgrade_lock");
343  elgg_log('Upgrade unlocked.', 'NOTICE');
344  }
345 
351  public function isUpgradeLocked() {
352  global $CONFIG;
353 
354  $is_locked = count(get_data("SHOW TABLES LIKE '{$CONFIG->dbprefix}upgrade_lock'"));
355 
356  return (bool)$is_locked;
357  }
358 
381  protected function dbUpgrade($version, $fromdir = "", $quiet = false) {
382  global $CONFIG;
383 
384  $version = (int) $version;
385 
386  if (!$fromdir) {
387  $fromdir = $CONFIG->path . 'engine/schema/upgrades/';
388  }
389 
390  $i = 0;
391 
392  if ($handle = opendir($fromdir)) {
393  $sqlupgrades = array();
394 
395  while ($sqlfile = readdir($handle)) {
396  if (!is_dir($fromdir . $sqlfile)) {
397  if (preg_match('/^([0-9]{10})\.(sql)$/', $sqlfile, $matches)) {
398  $sql_version = (int) $matches[1];
399  if ($sql_version > $version) {
400  $sqlupgrades[] = $sqlfile;
401  }
402  }
403  }
404  }
405 
406  asort($sqlupgrades);
407 
408  if (sizeof($sqlupgrades) > 0) {
409  foreach ($sqlupgrades as $sqlfile) {
410 
411  // hide all errors.
412  if ($quiet) {
413  try {
414  run_sql_script($fromdir . $sqlfile);
415  } catch (DatabaseException $e) {
416  error_log($e->getmessage());
417  }
418  } else {
419  run_sql_script($fromdir . $sqlfile);
420  }
421  $i++;
422  }
423  }
424  }
425 
426  return $i;
427  }
428 
429 }
$success
Definition: view.php:29
elgg_get_config($name, $site_guid=0)
Get an Elgg configuration value.
bootstrap17to18()
Boot straps into 1.8 upgrade system from 1.7.
elgg_reset_system_cache()
Reset the system cache by deleting the caches.
Definition: cache.php:40
elgg_invalidate_simplecache()
Deletes all cached views in the simplecache and sets the lastcache and lastupdate time to 0 for every...
Definition: cache.php:278
$e
Definition: metadata.php:12
upgradeCode($version, $quiet=false)
Run any php upgrade scripts which are required.
sanitise_filepath($path, $append_slash=true)
Sanitise file paths ensuring that they begin and end with slashes etc.
Definition: elgglib.php:484
setProcessedUpgrade($upgrade)
Saves a processed upgrade to a dataset.
$upgrades
Lists pending upgrades.
Definition: upgrades.php:6
delete_data($query)
Remove a row from the database.
Definition: database.php:106
getUnprocessedUpgrades($upgrade_files=null, $processed_upgrades=null)
Checks if any upgrades need to be run.
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.
getUpgradeFiles($upgrade_path=null)
Returns a list of upgrade files relative to the $upgrade_path dir.
releaseUpgradeMutex()
Unlocks upgrade.
insert_data($query)
Insert a row into the database.
Definition: database.php:80
processUpgrades()
Upgrades Elgg Database and code.
elgg_echo($message_key, $args=array(), $language="")
Given a message key, returns an appropriately translated full-text string.
Definition: languages.php:21
getUpgradeFileVersion($filename)
Returns the version of the upgrade filename.
datalist_get($name)
Get the value of a datalist element.
global $CONFIG
datalist_set($name, $value)
Set the value for a datalist element.
getProcessedUpgrades()
Gets a list of processes upgrades.
isUpgradeLocked()
Checks if upgrade is locked.
elgg global
Pointer to the global context.
Definition: elgglib.js:12
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.
run()
Run the upgrade process.
$is_locked
Definition: content.php:15
elgg system_message
Wrapper function for system_messages.
Definition: elgglib.js:374
elgg_log($message, $level= 'NOTICE')
Display or log a message.
Definition: elgglib.php:1083
run_sql_script($scriptlocation)
Runs a full database script from disk.
Definition: database.php:130
get_data($query, $callback="")
Retrieve rows from the database.
Definition: database.php:50
elgg_get_version($human_readable=false)
Get the current Elgg version information.
Definition: elgglib.php:1126
$filename
Definition: crop.php:23
elgg_trigger_event($event, $object_type, $object=null)
Trigger an Elgg Event and attempt to run all handler callbacks registered to that event...
Definition: elgglib.php:720
$version
Definition: version.php:14
elgg_unregister_event_handler($event, $object_type, $callback)
Unregisters a callback for an event.
Definition: elgglib.php:683