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 }
$filename
Definition: crop.php:23
$success
Definition: view.php:29
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
setProcessedUpgrade($upgrade)
Saves a processed upgrade to a dataset.
dbUpgrade($version, $fromdir="", $quiet=false)
upgradeCode($version, $quiet=false)
Run any php upgrade scripts which are required.
static includeCode($file)
PHP include a file with a very limited scope.
releaseUpgradeMutex()
Unlocks upgrade.
getUnprocessedUpgrades($upgrade_files=null, $processed_upgrades=null)
Checks if any upgrades need to be run.
bootstrap17to18()
Boot straps into 1.8 upgrade system from 1.7.
getUpgradeFiles($upgrade_path=null)
Returns a list of upgrade files relative to the $upgrade_path dir.
run()
Run the upgrade process.
getUpgradeFileVersion($filename)
Returns the version of the upgrade filename.
getUpgradeMutex()
Creates a table {prefix}upgrade_lock that is used as a mutex for upgrades.
processUpgrades()
Upgrades Elgg Database and code.
getProcessedUpgrades()
Gets a list of processes upgrades.
isUpgradeLocked()
Checks if upgrade is locked.
datalist_get($name)
Get the value of a datalist element.
datalist_set($name, $value)
Set the value for a datalist element.
elgg_get_config($name, $site_guid=0)
Get an Elgg configuration value.
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
elgg_log($message, $level='NOTICE')
Display or log a message.
Definition: elgglib.php:1083
sanitise_filepath($path, $append_slash=true)
Sanitise file paths ensuring that they begin and end with slashes etc.
Definition: elgglib.php:484
system_message($message)
Display a system message on next page load.
Definition: elgglib.php:592
elgg_get_version($human_readable=false)
Get the current Elgg version information.
Definition: elgglib.php:1126
elgg_unregister_event_handler($event, $object_type, $callback)
Unregisters a callback for an event.
Definition: elgglib.php:683
delete_data($query)
Remove a row from the database.
Definition: database.php:106
insert_data($query)
Insert a row into the database.
Definition: database.php:80
get_data($query, $callback="")
Retrieve rows from the database.
Definition: database.php:50
run_sql_script($scriptlocation)
Runs a full database script from disk.
Definition: database.php:130
elgg_echo($message_key, $args=array(), $language="")
Given a message key, returns an appropriately translated full-text string.
Definition: languages.php:21
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