MDL-41787 make sure maturity constants are defined before they are used in core_compo...
[moodle.git] / lib / upgradelib.php
CommitLineData
96db14f9 1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
db9d4a3d 17
18/**
8580535b 19 * Various upgrade/install related functions and classes.
db9d4a3d 20 *
78bfb562 21 * @package core
96db14f9 22 * @subpackage upgrade
23 * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
db9d4a3d 25 */
26
78bfb562
PS
27defined('MOODLE_INTERNAL') || die();
28
72fb21b6 29/** UPGRADE_LOG_NORMAL = 0 */
db9d4a3d 30define('UPGRADE_LOG_NORMAL', 0);
72fb21b6 31/** UPGRADE_LOG_NOTICE = 1 */
db9d4a3d 32define('UPGRADE_LOG_NOTICE', 1);
72fb21b6 33/** UPGRADE_LOG_ERROR = 2 */
795a08ad 34define('UPGRADE_LOG_ERROR', 2);
35
36/**
37 * Exception indicating unknown error during upgrade.
72fb21b6 38 *
d32fb550 39 * @package core
72fb21b6 40 * @subpackage upgrade
d32fb550 41 * @copyright 2009 Petr Skoda {@link http://skodak.org}
72fb21b6 42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
795a08ad 43 */
44class upgrade_exception extends moodle_exception {
4f0c2d00 45 function __construct($plugin, $version, $debuginfo=NULL) {
795a08ad 46 global $CFG;
47 $a = (object)array('plugin'=>$plugin, 'version'=>$version);
4f0c2d00 48 parent::__construct('upgradeerror', 'admin', "$CFG->wwwroot/$CFG->admin/index.php", $a, $debuginfo);
795a08ad 49 }
50}
51
52/**
53 * Exception indicating downgrade error during upgrade.
72fb21b6 54 *
d32fb550 55 * @package core
72fb21b6 56 * @subpackage upgrade
d32fb550 57 * @copyright 2009 Petr Skoda {@link http://skodak.org}
72fb21b6 58 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
795a08ad 59 */
60class downgrade_exception extends moodle_exception {
61 function __construct($plugin, $oldversion, $newversion) {
62 global $CFG;
63 $plugin = is_null($plugin) ? 'moodle' : $plugin;
64 $a = (object)array('plugin'=>$plugin, 'oldversion'=>$oldversion, 'newversion'=>$newversion);
65 parent::__construct('cannotdowngrade', 'debug', "$CFG->wwwroot/$CFG->admin/index.php", $a);
66 }
67}
68
72fb21b6 69/**
d32fb550 70 * @package core
72fb21b6 71 * @subpackage upgrade
d32fb550 72 * @copyright 2009 Petr Skoda {@link http://skodak.org}
72fb21b6 73 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
74 */
795a08ad 75class upgrade_requires_exception extends moodle_exception {
76 function __construct($plugin, $pluginversion, $currentmoodle, $requiremoodle) {
77 global $CFG;
365a5941 78 $a = new stdClass();
795a08ad 79 $a->pluginname = $plugin;
80 $a->pluginversion = $pluginversion;
81 $a->currentmoodle = $currentmoodle;
17da2e6f 82 $a->requiremoodle = $requiremoodle;
795a08ad 83 parent::__construct('pluginrequirementsnotmet', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $a);
84 }
85}
86
72fb21b6 87/**
d32fb550 88 * @package core
72fb21b6 89 * @subpackage upgrade
d32fb550 90 * @copyright 2009 Petr Skoda {@link http://skodak.org}
72fb21b6 91 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
92 */
795a08ad 93class plugin_defective_exception extends moodle_exception {
94 function __construct($plugin, $details) {
95 global $CFG;
96 parent::__construct('detectedbrokenplugin', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $plugin, $details);
97 }
98}
db9d4a3d 99
bdbcb6d7
PS
100/**
101 * @package core
102 * @subpackage upgrade
103 * @copyright 2009 Petr Skoda {@link http://skodak.org}
104 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
105 */
106class plugin_misplaced_exception extends moodle_exception {
107 function __construct($component, $expected, $current) {
108 global $CFG;
109 $a = new stdClass();
110 $a->component = $component;
111 $a->expected = $expected;
112 $a->current = $current;
113 parent::__construct('detectedmisplacedplugin', 'core_plugin', "$CFG->wwwroot/$CFG->admin/index.php", $a);
114 }
115}
116
0c5406ca
PS
117/**
118 * Sets maximum expected time needed for upgrade task.
119 * Please always make sure that upgrade will not run longer!
120 *
121 * The script may be automatically aborted if upgrade times out.
122 *
123 * @category upgrade
124 * @param int $max_execution_time in seconds (can not be less than 60 s)
125 */
126function upgrade_set_timeout($max_execution_time=300) {
127 global $CFG;
128
129 if (!isset($CFG->upgraderunning) or $CFG->upgraderunning < time()) {
130 $upgraderunning = get_config(null, 'upgraderunning');
131 } else {
132 $upgraderunning = $CFG->upgraderunning;
133 }
134
135 if (!$upgraderunning) {
b814d1b4
PS
136 if (CLI_SCRIPT) {
137 // never stop CLI upgrades
138 $upgraderunning = 0;
139 } else {
140 // web upgrade not running or aborted
141 print_error('upgradetimedout', 'admin', "$CFG->wwwroot/$CFG->admin/");
142 }
0c5406ca
PS
143 }
144
145 if ($max_execution_time < 60) {
146 // protection against 0 here
147 $max_execution_time = 60;
148 }
149
150 $expected_end = time() + $max_execution_time;
151
152 if ($expected_end < $upgraderunning + 10 and $expected_end > $upgraderunning - 10) {
153 // no need to store new end, it is nearly the same ;-)
154 return;
155 }
156
5563f5fb
PS
157 if (CLI_SCRIPT) {
158 // there is no point in timing out of CLI scripts, admins can stop them if necessary
159 set_time_limit(0);
160 } else {
161 set_time_limit($max_execution_time);
162 }
0c5406ca
PS
163 set_config('upgraderunning', $expected_end); // keep upgrade locked until this time
164}
165
db9d4a3d 166/**
167 * Upgrade savepoint, marks end of each upgrade block.
168 * It stores new main version, resets upgrade timeout
169 * and abort upgrade if user cancels page loading.
170 *
171 * Please do not make large upgrade blocks with lots of operations,
172 * for example when adding tables keep only one table operation per block.
173 *
39b90b51 174 * @category upgrade
db9d4a3d 175 * @param bool $result false if upgrade step failed, true if completed
176 * @param string or float $version main version
177 * @param bool $allowabort allow user to abort script execution here
178 * @return void
179 */
180function upgrade_main_savepoint($result, $version, $allowabort=true) {
181 global $CFG;
182
bb44ef99
RT
183 //sanity check to avoid confusion with upgrade_mod_savepoint usage.
184 if (!is_bool($allowabort)) {
185 $errormessage = 'Parameter type mismatch. Are you mixing up upgrade_main_savepoint() and upgrade_mod_savepoint()?';
186 throw new coding_exception($errormessage);
187 }
188
795a08ad 189 if (!$result) {
520cea92 190 throw new upgrade_exception(null, $version);
795a08ad 191 }
192
193 if ($CFG->version >= $version) {
194 // something really wrong is going on in main upgrade script
195 throw new downgrade_exception(null, $CFG->version, $version);
db9d4a3d 196 }
197
795a08ad 198 set_config('version', $version);
199 upgrade_log(UPGRADE_LOG_NORMAL, null, 'Upgrade savepoint reached');
200
db9d4a3d 201 // reset upgrade timeout to default
202 upgrade_set_timeout();
203
204 // this is a safe place to stop upgrades if user aborts page loading
205 if ($allowabort and connection_aborted()) {
206 die;
207 }
208}
209
210/**
211 * Module upgrade savepoint, marks end of module upgrade blocks
212 * It stores module version, resets upgrade timeout
213 * and abort upgrade if user cancels page loading.
214 *
39b90b51 215 * @category upgrade
db9d4a3d 216 * @param bool $result false if upgrade step failed, true if completed
217 * @param string or float $version main version
218 * @param string $modname name of module
219 * @param bool $allowabort allow user to abort script execution here
220 * @return void
221 */
222function upgrade_mod_savepoint($result, $version, $modname, $allowabort=true) {
223 global $DB;
224
795a08ad 225 if (!$result) {
520cea92 226 throw new upgrade_exception("mod_$modname", $version);
795a08ad 227 }
228
db9d4a3d 229 if (!$module = $DB->get_record('modules', array('name'=>$modname))) {
230 print_error('modulenotexist', 'debug', '', $modname);
231 }
232
795a08ad 233 if ($module->version >= $version) {
234 // something really wrong is going on in upgrade script
520cea92 235 throw new downgrade_exception("mod_$modname", $module->version, $version);
db9d4a3d 236 }
795a08ad 237 $module->version = $version;
238 $DB->update_record('modules', $module);
520cea92 239 upgrade_log(UPGRADE_LOG_NORMAL, "mod_$modname", 'Upgrade savepoint reached');
db9d4a3d 240
241 // reset upgrade timeout to default
242 upgrade_set_timeout();
243
244 // this is a safe place to stop upgrades if user aborts page loading
245 if ($allowabort and connection_aborted()) {
246 die;
247 }
248}
249
250/**
251 * Blocks upgrade savepoint, marks end of blocks upgrade blocks
252 * It stores block version, resets upgrade timeout
253 * and abort upgrade if user cancels page loading.
254 *
39b90b51 255 * @category upgrade
db9d4a3d 256 * @param bool $result false if upgrade step failed, true if completed
257 * @param string or float $version main version
258 * @param string $blockname name of block
259 * @param bool $allowabort allow user to abort script execution here
260 * @return void
261 */
795a08ad 262function upgrade_block_savepoint($result, $version, $blockname, $allowabort=true) {
db9d4a3d 263 global $DB;
264
795a08ad 265 if (!$result) {
520cea92 266 throw new upgrade_exception("block_$blockname", $version);
795a08ad 267 }
268
db9d4a3d 269 if (!$block = $DB->get_record('block', array('name'=>$blockname))) {
270 print_error('blocknotexist', 'debug', '', $blockname);
271 }
272
795a08ad 273 if ($block->version >= $version) {
274 // something really wrong is going on in upgrade script
520cea92 275 throw new downgrade_exception("block_$blockname", $block->version, $version);
db9d4a3d 276 }
795a08ad 277 $block->version = $version;
278 $DB->update_record('block', $block);
520cea92 279 upgrade_log(UPGRADE_LOG_NORMAL, "block_$blockname", 'Upgrade savepoint reached');
db9d4a3d 280
281 // reset upgrade timeout to default
282 upgrade_set_timeout();
283
284 // this is a safe place to stop upgrades if user aborts page loading
285 if ($allowabort and connection_aborted()) {
286 die;
287 }
288}
289
290/**
291 * Plugins upgrade savepoint, marks end of blocks upgrade blocks
292 * It stores plugin version, resets upgrade timeout
293 * and abort upgrade if user cancels page loading.
294 *
39b90b51 295 * @category upgrade
db9d4a3d 296 * @param bool $result false if upgrade step failed, true if completed
297 * @param string or float $version main version
298 * @param string $type name of plugin
299 * @param string $dir location of plugin
300 * @param bool $allowabort allow user to abort script execution here
301 * @return void
302 */
17da2e6f 303function upgrade_plugin_savepoint($result, $version, $type, $plugin, $allowabort=true) {
304 $component = $type.'_'.$plugin;
305
795a08ad 306 if (!$result) {
17da2e6f 307 throw new upgrade_exception($component, $version);
db9d4a3d 308 }
309
17da2e6f 310 $installedversion = get_config($component, 'version');
795a08ad 311 if ($installedversion >= $version) {
312 // Something really wrong is going on in the upgrade script
313 throw new downgrade_exception($component, $installedversion, $version);
314 }
17da2e6f 315 set_config('version', $version, $component);
795a08ad 316 upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
317
db9d4a3d 318 // Reset upgrade timeout to default
319 upgrade_set_timeout();
320
321 // This is a safe place to stop upgrades if user aborts page loading
322 if ($allowabort and connection_aborted()) {
323 die;
324 }
325}
326
9008ec16
PS
327/**
328 * Detect if there are leftovers in PHP source files.
329 *
330 * During main version upgrades administrators MUST move away
331 * old PHP source files and start from scratch (or better
332 * use git).
333 *
334 * @return bool true means borked upgrade, false means previous PHP files were properly removed
335 */
336function upgrade_stale_php_files_present() {
337 global $CFG;
338
339 $someexamplesofremovedfiles = array(
72e3a3c9
PS
340 // removed in 2.6dev
341 '/admin/oacleanup.php',
301ebbe5 342 // removed in 2.5dev
c1782ec6
EL
343 '/backup/lib.php',
344 '/backup/bb/README.txt',
301ebbe5 345 '/lib/excel/test.php',
c8b3346c
PS
346 // removed in 2.4dev
347 '/admin/tool/unittest/simpletestlib.php',
37d72977
PS
348 // removed in 2.3dev
349 '/lib/minify/builder/',
9008ec16
PS
350 // removed in 2.2dev
351 '/lib/yui/3.4.1pr1/',
352 // removed in 2.2
353 '/search/cron_php5.php',
354 '/course/report/log/indexlive.php',
355 '/admin/report/backups/index.php',
356 '/admin/generator.php',
357 // removed in 2.1
358 '/lib/yui/2.8.0r4/',
359 // removed in 2.0
360 '/blocks/admin/block_admin.php',
361 '/blocks/admin_tree/block_admin_tree.php',
362 );
363
364 foreach ($someexamplesofremovedfiles as $file) {
365 if (file_exists($CFG->dirroot.$file)) {
366 return true;
367 }
368 }
369
370 return false;
371}
db9d4a3d 372
373/**
374 * Upgrade plugins
db9d4a3d 375 * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
17da2e6f 376 * return void
db9d4a3d 377 */
17da2e6f 378function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
db9d4a3d 379 global $CFG, $DB;
380
381/// special cases
382 if ($type === 'mod') {
194cdca8 383 return upgrade_plugins_modules($startcallback, $endcallback, $verbose);
795a08ad 384 } else if ($type === 'block') {
194cdca8 385 return upgrade_plugins_blocks($startcallback, $endcallback, $verbose);
db9d4a3d 386 }
387
bd3b3bba 388 $plugs = core_component::get_plugin_list($type);
db9d4a3d 389
17da2e6f 390 foreach ($plugs as $plug=>$fullplug) {
ebb98bf5 391 // Reset time so that it works when installing a large number of plugins
392 set_time_limit(600);
aff24313 393 $component = clean_param($type.'_'.$plug, PARAM_COMPONENT); // standardised plugin name
db9d4a3d 394
3e858ea7 395 // check plugin dir is valid name
aff24313
PS
396 if (empty($component)) {
397 throw new plugin_defective_exception($type.'_'.$plug, 'Invalid plugin directory name.');
3e858ea7
PS
398 }
399
795a08ad 400 if (!is_readable($fullplug.'/version.php')) {
401 continue;
db9d4a3d 402 }
403
365a5941 404 $plugin = new stdClass();
bdbcb6d7 405 $module = new stdClass(); // Prevent some notices when plugin placed in wrong directory.
795a08ad 406 require($fullplug.'/version.php'); // defines $plugin with version etc
db9d4a3d 407
bdbcb6d7
PS
408 if (!isset($plugin->version) and isset($module->version)) {
409 $plugin = $module;
410 }
411
3e858ea7
PS
412 // if plugin tells us it's full name we may check the location
413 if (isset($plugin->component)) {
414 if ($plugin->component !== $component) {
bdbcb6d7 415 $current = str_replace($CFG->dirroot, '$CFG->dirroot', $fullplug);
b0d1d941 416 $expected = str_replace($CFG->dirroot, '$CFG->dirroot', core_component::get_component_directory($plugin->component));
bdbcb6d7 417 throw new plugin_misplaced_exception($component, $expected, $current);
3e858ea7
PS
418 }
419 }
420
795a08ad 421 if (empty($plugin->version)) {
422 throw new plugin_defective_exception($component, 'Missing version value in version.php');
db9d4a3d 423 }
424
17da2e6f 425 $plugin->name = $plug;
426 $plugin->fullname = $component;
795a08ad 427
428
db9d4a3d 429 if (!empty($plugin->requires)) {
430 if ($plugin->requires > $CFG->version) {
795a08ad 431 throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
ef14c1e7
PS
432 } else if ($plugin->requires < 2010000000) {
433 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
db9d4a3d 434 }
435 }
436
9b683d13 437 // try to recover from interrupted install.php if needed
438 if (file_exists($fullplug.'/db/install.php')) {
439 if (get_config($plugin->fullname, 'installrunning')) {
440 require_once($fullplug.'/db/install.php');
441 $recover_install_function = 'xmldb_'.$plugin->fullname.'_install_recovery';
442 if (function_exists($recover_install_function)) {
443 $startcallback($component, true, $verbose);
444 $recover_install_function();
f6f06d69 445 unset_config('installrunning', $plugin->fullname);
9b683d13 446 update_capabilities($component);
c6d75bff 447 log_update_descriptions($component);
c976e271 448 external_update_descriptions($component);
9b683d13 449 events_update_definition($component);
450 message_update_providers($component);
afed8d0f
RK
451 if ($type === 'message') {
452 message_update_processors($plug);
453 }
de260e0f 454 upgrade_plugin_mnet_functions($component);
9b683d13 455 $endcallback($component, true, $verbose);
456 }
457 }
458 }
db9d4a3d 459
9b683d13 460 $installedversion = get_config($plugin->fullname, 'version');
795a08ad 461 if (empty($installedversion)) { // new installation
194cdca8 462 $startcallback($component, true, $verbose);
db9d4a3d 463
795a08ad 464 /// Install tables if defined
465 if (file_exists($fullplug.'/db/install.xml')) {
466 $DB->get_manager()->install_from_xmldb_file($fullplug.'/db/install.xml');
db9d4a3d 467 }
9b683d13 468
469 /// store version
470 upgrade_plugin_savepoint(true, $plugin->version, $type, $plug, false);
471
795a08ad 472 /// execute post install file
473 if (file_exists($fullplug.'/db/install.php')) {
474 require_once($fullplug.'/db/install.php');
f6f06d69
PS
475 set_config('installrunning', 1, $plugin->fullname);
476 $post_install_function = 'xmldb_'.$plugin->fullname.'_install';
795a08ad 477 $post_install_function();
f6f06d69 478 unset_config('installrunning', $plugin->fullname);
795a08ad 479 }
480
795a08ad 481 /// Install various components
482 update_capabilities($component);
c6d75bff 483 log_update_descriptions($component);
c976e271 484 external_update_descriptions($component);
795a08ad 485 events_update_definition($component);
486 message_update_providers($component);
afed8d0f
RK
487 if ($type === 'message') {
488 message_update_processors($plug);
489 }
de260e0f 490 upgrade_plugin_mnet_functions($component);
194cdca8 491 $endcallback($component, true, $verbose);
795a08ad 492
493 } else if ($installedversion < $plugin->version) { // upgrade
494 /// Run the upgrade function for the plugin.
194cdca8 495 $startcallback($component, false, $verbose);
795a08ad 496
497 if (is_readable($fullplug.'/db/upgrade.php')) {
498 require_once($fullplug.'/db/upgrade.php'); // defines upgrading function
499
500 $newupgrade_function = 'xmldb_'.$plugin->fullname.'_upgrade';
501 $result = $newupgrade_function($installedversion);
502 } else {
503 $result = true;
504 }
505
506 $installedversion = get_config($plugin->fullname, 'version');
507 if ($installedversion < $plugin->version) {
508 // store version if not already there
509 upgrade_plugin_savepoint($result, $plugin->version, $type, $plug, false);
510 }
511
512 /// Upgrade various components
513 update_capabilities($component);
c6d75bff 514 log_update_descriptions($component);
c976e271 515 external_update_descriptions($component);
795a08ad 516 events_update_definition($component);
517 message_update_providers($component);
afed8d0f
RK
518 if ($type === 'message') {
519 message_update_processors($plug);
520 }
de260e0f 521 upgrade_plugin_mnet_functions($component);
194cdca8 522 $endcallback($component, false, $verbose);
795a08ad 523
524 } else if ($installedversion > $plugin->version) {
525 throw new downgrade_exception($component, $installedversion, $plugin->version);
db9d4a3d 526 }
527 }
db9d4a3d 528}
529
530/**
531 * Find and check all modules and load them up or upgrade them if necessary
72fb21b6 532 *
533 * @global object
534 * @global object
db9d4a3d 535 */
194cdca8 536function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
db9d4a3d 537 global $CFG, $DB;
538
bd3b3bba 539 $mods = core_component::get_plugin_list('mod');
db9d4a3d 540
17da2e6f 541 foreach ($mods as $mod=>$fullmod) {
db9d4a3d 542
3e858ea7 543 if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
db9d4a3d 544 continue;
545 }
546
aff24313 547 $component = clean_param('mod_'.$mod, PARAM_COMPONENT);
db9d4a3d 548
3e858ea7 549 // check module dir is valid name
aff24313
PS
550 if (empty($component)) {
551 throw new plugin_defective_exception('mod_'.$mod, 'Invalid plugin directory name.');
3e858ea7
PS
552 }
553
795a08ad 554 if (!is_readable($fullmod.'/version.php')) {
555 throw new plugin_defective_exception($component, 'Missing version.php');
db9d4a3d 556 }
557
365a5941 558 $module = new stdClass();
bdbcb6d7 559 $plugin = new stdClass(); // Prevent some notices when plugin placed in wrong directory.
795a08ad 560 require($fullmod .'/version.php'); // defines $module with version etc
db9d4a3d 561
bdbcb6d7
PS
562 if (!isset($module->version) and isset($plugin->version)) {
563 $module = $plugin;
564 }
565
3e858ea7
PS
566 // if plugin tells us it's full name we may check the location
567 if (isset($module->component)) {
568 if ($module->component !== $component) {
bdbcb6d7 569 $current = str_replace($CFG->dirroot, '$CFG->dirroot', $fullmod);
b0d1d941 570 $expected = str_replace($CFG->dirroot, '$CFG->dirroot', core_component::get_component_directory($module->component));
bdbcb6d7 571 throw new plugin_misplaced_exception($component, $expected, $current);
3e858ea7
PS
572 }
573 }
574
795a08ad 575 if (empty($module->version)) {
c1fe2368 576 if (isset($module->version)) {
577 // Version is empty but is set - it means its value is 0 or ''. Let us skip such module.
b921b6ee 578 // This is intended for developers so they can work on the early stages of the module.
c1fe2368 579 continue;
580 }
795a08ad 581 throw new plugin_defective_exception($component, 'Missing version value in version.php');
db9d4a3d 582 }
583
584 if (!empty($module->requires)) {
585 if ($module->requires > $CFG->version) {
795a08ad 586 throw new upgrade_requires_exception($component, $module->version, $CFG->version, $module->requires);
ef14c1e7
PS
587 } else if ($module->requires < 2010000000) {
588 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
db9d4a3d 589 }
590 }
591
ff05bb31
TH
592 if (empty($module->cron)) {
593 $module->cron = 0;
594 }
595
3e858ea7
PS
596 // all modules must have en lang pack
597 if (!is_readable("$fullmod/lang/en/$mod.php")) {
598 throw new plugin_defective_exception($component, 'Missing mandatory en language pack.');
599 }
600
db9d4a3d 601 $module->name = $mod; // The name MUST match the directory
602
795a08ad 603 $currmodule = $DB->get_record('modules', array('name'=>$module->name));
db9d4a3d 604
9b683d13 605 if (file_exists($fullmod.'/db/install.php')) {
606 if (get_config($module->name, 'installrunning')) {
607 require_once($fullmod.'/db/install.php');
608 $recover_install_function = 'xmldb_'.$module->name.'_install_recovery';
609 if (function_exists($recover_install_function)) {
610 $startcallback($component, true, $verbose);
611 $recover_install_function();
612 unset_config('installrunning', $module->name);
613 // Install various components too
614 update_capabilities($component);
c6d75bff 615 log_update_descriptions($component);
c976e271 616 external_update_descriptions($component);
9b683d13 617 events_update_definition($component);
618 message_update_providers($component);
de260e0f 619 upgrade_plugin_mnet_functions($component);
9b683d13 620 $endcallback($component, true, $verbose);
621 }
622 }
623 }
624
795a08ad 625 if (empty($currmodule->version)) {
194cdca8 626 $startcallback($component, true, $verbose);
db9d4a3d 627
795a08ad 628 /// Execute install.xml (XMLDB) - must be present in all modules
629 $DB->get_manager()->install_from_xmldb_file($fullmod.'/db/install.xml');
db9d4a3d 630
2e3da297 631 /// Add record into modules table - may be needed in install.php already
632 $module->id = $DB->insert_record('modules', $module);
633
db9d4a3d 634 /// Post installation hook - optional
635 if (file_exists("$fullmod/db/install.php")) {
636 require_once("$fullmod/db/install.php");
9b683d13 637 // Set installation running flag, we need to recover after exception or error
638 set_config('installrunning', 1, $module->name);
3412cb43 639 $post_install_function = 'xmldb_'.$module->name.'_install';
db9d4a3d 640 $post_install_function();
9b683d13 641 unset_config('installrunning', $module->name);
db9d4a3d 642 }
643
795a08ad 644 /// Install various components
645 update_capabilities($component);
c6d75bff 646 log_update_descriptions($component);
c976e271 647 external_update_descriptions($component);
795a08ad 648 events_update_definition($component);
649 message_update_providers($component);
de260e0f 650 upgrade_plugin_mnet_functions($component);
795a08ad 651
194cdca8 652 $endcallback($component, true, $verbose);
db9d4a3d 653
795a08ad 654 } else if ($currmodule->version < $module->version) {
655 /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
194cdca8 656 $startcallback($component, false, $verbose);
795a08ad 657
658 if (is_readable($fullmod.'/db/upgrade.php')) {
659 require_once($fullmod.'/db/upgrade.php'); // defines new upgrading function
660 $newupgrade_function = 'xmldb_'.$module->name.'_upgrade';
661 $result = $newupgrade_function($currmodule->version, $module);
662 } else {
663 $result = true;
664 }
665
666 $currmodule = $DB->get_record('modules', array('name'=>$module->name));
667 if ($currmodule->version < $module->version) {
668 // store version if not already there
669 upgrade_mod_savepoint($result, $module->version, $mod, false);
670 }
671
3412cb43
TH
672 // update cron flag if needed
673 if ($currmodule->cron != $module->cron) {
674 $DB->set_field('modules', 'cron', $module->cron, array('name' => $module->name));
675 }
676
677 // Upgrade various components
795a08ad 678 update_capabilities($component);
c6d75bff 679 log_update_descriptions($component);
c976e271 680 external_update_descriptions($component);
795a08ad 681 events_update_definition($component);
682 message_update_providers($component);
de260e0f 683 upgrade_plugin_mnet_functions($component);
795a08ad 684
194cdca8 685 $endcallback($component, false, $verbose);
795a08ad 686
687 } else if ($currmodule->version > $module->version) {
688 throw new downgrade_exception($component, $currmodule->version, $module->version);
689 }
690 }
691}
db9d4a3d 692
db9d4a3d 693
795a08ad 694/**
695 * This function finds all available blocks and install them
696 * into blocks table or do all the upgrade process if newer.
72fb21b6 697 *
698 * @global object
699 * @global object
795a08ad 700 */
194cdca8 701function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
795a08ad 702 global $CFG, $DB;
703
795a08ad 704 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
705
706 $blocktitles = array(); // we do not want duplicate titles
707
708 //Is this a first install
709 $first_install = null;
710
bd3b3bba 711 $blocks = core_component::get_plugin_list('block');
795a08ad 712
17da2e6f 713 foreach ($blocks as $blockname=>$fullblock) {
795a08ad 714
715 if (is_null($first_install)) {
f46eba7e 716 $first_install = ($DB->count_records('block_instances') == 0);
795a08ad 717 }
718
aff24313 719 if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
795a08ad 720 continue;
db9d4a3d 721 }
722
aff24313 723 $component = clean_param('block_'.$blockname, PARAM_COMPONENT);
db9d4a3d 724
3e858ea7 725 // check block dir is valid name
aff24313
PS
726 if (empty($component)) {
727 throw new plugin_defective_exception('block_'.$blockname, 'Invalid plugin directory name.');
3e858ea7
PS
728 }
729
8571833f
PS
730 if (!is_readable($fullblock.'/version.php')) {
731 throw new plugin_defective_exception('block/'.$blockname, 'Missing version.php file.');
732 }
365a5941 733 $plugin = new stdClass();
bdbcb6d7 734 $module = new stdClass(); // Prevent some notices when module placed in wrong directory.
8571833f
PS
735 $plugin->version = NULL;
736 $plugin->cron = 0;
737 include($fullblock.'/version.php');
bdbcb6d7
PS
738 if (!isset($plugin->version) and isset($module->version)) {
739 $plugin = $module;
740 }
8571833f
PS
741 $block = $plugin;
742
3e858ea7
PS
743 // if plugin tells us it's full name we may check the location
744 if (isset($block->component)) {
745 if ($block->component !== $component) {
bdbcb6d7 746 $current = str_replace($CFG->dirroot, '$CFG->dirroot', $fullblock);
b0d1d941 747 $expected = str_replace($CFG->dirroot, '$CFG->dirroot', core_component::get_component_directory($block->component));
bdbcb6d7 748 throw new plugin_misplaced_exception($component, $expected, $current);
3e858ea7
PS
749 }
750 }
751
ef14c1e7
PS
752 if (!empty($plugin->requires)) {
753 if ($plugin->requires > $CFG->version) {
754 throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
755 } else if ($plugin->requires < 2010000000) {
756 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
757 }
758 }
759
795a08ad 760 if (!is_readable($fullblock.'/block_'.$blockname.'.php')) {
761 throw new plugin_defective_exception('block/'.$blockname, 'Missing main block class file.');
db9d4a3d 762 }
8571833f 763 include_once($fullblock.'/block_'.$blockname.'.php');
db9d4a3d 764
795a08ad 765 $classname = 'block_'.$blockname;
766
767 if (!class_exists($classname)) {
768 throw new plugin_defective_exception($component, 'Can not load main class.');
769 }
770
b921b6ee 771 $blockobj = new $classname; // This is what we'll be testing
795a08ad 772 $blocktitle = $blockobj->get_title();
773
774 // OK, it's as we all hoped. For further tests, the object will do them itself.
775 if (!$blockobj->_self_test()) {
776 throw new plugin_defective_exception($component, 'Self test failed.');
777 }
778
795a08ad 779 $block->name = $blockname; // The name MUST match the directory
795a08ad 780
781 if (empty($block->version)) {
782 throw new plugin_defective_exception($component, 'Missing block version.');
783 }
784
785 $currblock = $DB->get_record('block', array('name'=>$block->name));
786
9b683d13 787 if (file_exists($fullblock.'/db/install.php')) {
788 if (get_config('block_'.$blockname, 'installrunning')) {
789 require_once($fullblock.'/db/install.php');
790 $recover_install_function = 'xmldb_block_'.$blockname.'_install_recovery';
791 if (function_exists($recover_install_function)) {
792 $startcallback($component, true, $verbose);
793 $recover_install_function();
794 unset_config('installrunning', 'block_'.$blockname);
795 // Install various components
796 update_capabilities($component);
c6d75bff 797 log_update_descriptions($component);
c976e271 798 external_update_descriptions($component);
9b683d13 799 events_update_definition($component);
800 message_update_providers($component);
de260e0f 801 upgrade_plugin_mnet_functions($component);
9b683d13 802 $endcallback($component, true, $verbose);
803 }
804 }
805 }
806
795a08ad 807 if (empty($currblock->version)) { // block not installed yet, so install it
795a08ad 808 $conflictblock = array_search($blocktitle, $blocktitles);
809 if ($conflictblock !== false) {
810 // Duplicate block titles are not allowed, they confuse people
811 // AND PHP's associative arrays ;)
c1ddac83 812 throw new plugin_defective_exception($component, get_string('blocknameconflict', 'error', (object)array('name'=>$block->name, 'conflict'=>$conflictblock)));
795a08ad 813 }
194cdca8 814 $startcallback($component, true, $verbose);
795a08ad 815
816 if (file_exists($fullblock.'/db/install.xml')) {
817 $DB->get_manager()->install_from_xmldb_file($fullblock.'/db/install.xml');
818 }
819 $block->id = $DB->insert_record('block', $block);
820
821 if (file_exists($fullblock.'/db/install.php')) {
822 require_once($fullblock.'/db/install.php');
9b683d13 823 // Set installation running flag, we need to recover after exception or error
824 set_config('installrunning', 1, 'block_'.$blockname);
0e35ba6f 825 $post_install_function = 'xmldb_block_'.$blockname.'_install';
795a08ad 826 $post_install_function();
9b683d13 827 unset_config('installrunning', 'block_'.$blockname);
795a08ad 828 }
829
830 $blocktitles[$block->name] = $blocktitle;
831
832 // Install various components
833 update_capabilities($component);
c6d75bff 834 log_update_descriptions($component);
c976e271 835 external_update_descriptions($component);
795a08ad 836 events_update_definition($component);
837 message_update_providers($component);
de260e0f 838 upgrade_plugin_mnet_functions($component);
795a08ad 839
194cdca8 840 $endcallback($component, true, $verbose);
795a08ad 841
842 } else if ($currblock->version < $block->version) {
194cdca8 843 $startcallback($component, false, $verbose);
795a08ad 844
845 if (is_readable($fullblock.'/db/upgrade.php')) {
846 require_once($fullblock.'/db/upgrade.php'); // defines new upgrading function
847 $newupgrade_function = 'xmldb_block_'.$blockname.'_upgrade';
848 $result = $newupgrade_function($currblock->version, $block);
849 } else {
850 $result = true;
851 }
852
853 $currblock = $DB->get_record('block', array('name'=>$block->name));
854 if ($currblock->version < $block->version) {
855 // store version if not already there
856 upgrade_block_savepoint($result, $block->version, $block->name, false);
857 }
858
859 if ($currblock->cron != $block->cron) {
860 // update cron flag if needed
1fa84543 861 $DB->set_field('block', 'cron', $block->cron, array('id' => $currblock->id));
795a08ad 862 }
863
b921b6ee 864 // Upgrade various components
795a08ad 865 update_capabilities($component);
c6d75bff 866 log_update_descriptions($component);
c976e271 867 external_update_descriptions($component);
17da2e6f 868 events_update_definition($component);
795a08ad 869 message_update_providers($component);
de260e0f 870 upgrade_plugin_mnet_functions($component);
795a08ad 871
194cdca8 872 $endcallback($component, false, $verbose);
795a08ad 873
874 } else if ($currblock->version > $block->version) {
875 throw new downgrade_exception($component, $currblock->version, $block->version);
876 }
877 }
878
879
880 // Finally, if we are in the first_install of BLOCKS setup frontpage and admin page blocks
881 if ($first_install) {
795a08ad 882 //Iterate over each course - there should be only site course here now
883 if ($courses = $DB->get_records('course')) {
884 foreach ($courses as $course) {
9d1d606e 885 blocks_add_default_course_blocks($course);
db9d4a3d 886 }
887 }
795a08ad 888
9d1d606e 889 blocks_add_default_system_blocks();
795a08ad 890 }
891}
892
c6d75bff
PS
893
894/**
895 * Log_display description function used during install and upgrade.
896 *
897 * @param string $component name of component (moodle, mod_assignment, etc.)
898 * @return void
899 */
900function log_update_descriptions($component) {
901 global $DB;
902
b0d1d941 903 $defpath = core_component::get_component_directory($component).'/db/log.php';
c6d75bff
PS
904
905 if (!file_exists($defpath)) {
906 $DB->delete_records('log_display', array('component'=>$component));
907 return;
908 }
909
910 // load new info
911 $logs = array();
912 include($defpath);
913 $newlogs = array();
914 foreach ($logs as $log) {
915 $newlogs[$log['module'].'-'.$log['action']] = $log; // kind of unique name
916 }
917 unset($logs);
918 $logs = $newlogs;
919
920 $fields = array('module', 'action', 'mtable', 'field');
921 // update all log fist
922 $dblogs = $DB->get_records('log_display', array('component'=>$component));
923 foreach ($dblogs as $dblog) {
924 $name = $dblog->module.'-'.$dblog->action;
925
926 if (empty($logs[$name])) {
927 $DB->delete_records('log_display', array('id'=>$dblog->id));
928 continue;
929 }
930
931 $log = $logs[$name];
932 unset($logs[$name]);
933
934 $update = false;
935 foreach ($fields as $field) {
936 if ($dblog->$field != $log[$field]) {
937 $dblog->$field = $log[$field];
938 $update = true;
939 }
940 }
941 if ($update) {
942 $DB->update_record('log_display', $dblog);
943 }
944 }
945 foreach ($logs as $log) {
946 $dblog = (object)$log;
947 $dblog->component = $component;
948 $DB->insert_record('log_display', $dblog);
949 }
950}
951
c976e271 952/**
953 * Web service discovery function used during install and upgrade.
954 * @param string $component name of component (moodle, mod_assignment, etc.)
955 * @return void
956 */
957function external_update_descriptions($component) {
bc81eadb 958 global $DB, $CFG;
c976e271 959
b0d1d941 960 $defpath = core_component::get_component_directory($component).'/db/services.php';
c976e271 961
962 if (!file_exists($defpath)) {
bc81eadb 963 require_once($CFG->dirroot.'/lib/externallib.php');
c976e271 964 external_delete_descriptions($component);
965 return;
966 }
967
968 // load new info
969 $functions = array();
970 $services = array();
971 include($defpath);
972
973 // update all function fist
974 $dbfunctions = $DB->get_records('external_functions', array('component'=>$component));
975 foreach ($dbfunctions as $dbfunction) {
976 if (empty($functions[$dbfunction->name])) {
977 $DB->delete_records('external_functions', array('id'=>$dbfunction->id));
978 // do not delete functions from external_services_functions, beacuse
979 // we want to notify admins when functions used in custom services disappear
c6d75bff
PS
980
981 //TODO: this looks wrong, we have to delete it eventually (skodak)
c976e271 982 continue;
983 }
984
985 $function = $functions[$dbfunction->name];
986 unset($functions[$dbfunction->name]);
987 $function['classpath'] = empty($function['classpath']) ? null : $function['classpath'];
988
989 $update = false;
990 if ($dbfunction->classname != $function['classname']) {
991 $dbfunction->classname = $function['classname'];
992 $update = true;
993 }
994 if ($dbfunction->methodname != $function['methodname']) {
995 $dbfunction->methodname = $function['methodname'];
996 $update = true;
997 }
998 if ($dbfunction->classpath != $function['classpath']) {
999 $dbfunction->classpath = $function['classpath'];
1000 $update = true;
1001 }
12fc8acf 1002 $functioncapabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
72f68b51 1003 if ($dbfunction->capabilities != $functioncapabilities) {
1004 $dbfunction->capabilities = $functioncapabilities;
1005 $update = true;
1006 }
c976e271 1007 if ($update) {
1008 $DB->update_record('external_functions', $dbfunction);
1009 }
1010 }
1011 foreach ($functions as $fname => $function) {
365a5941 1012 $dbfunction = new stdClass();
c976e271 1013 $dbfunction->name = $fname;
1014 $dbfunction->classname = $function['classname'];
1015 $dbfunction->methodname = $function['methodname'];
1016 $dbfunction->classpath = empty($function['classpath']) ? null : $function['classpath'];
1017 $dbfunction->component = $component;
12fc8acf 1018 $dbfunction->capabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
c976e271 1019 $dbfunction->id = $DB->insert_record('external_functions', $dbfunction);
1020 }
1021 unset($functions);
1022
1023 // now deal with services
1024 $dbservices = $DB->get_records('external_services', array('component'=>$component));
1025 foreach ($dbservices as $dbservice) {
1026 if (empty($services[$dbservice->name])) {
bc81eadb 1027 $DB->delete_records('external_tokens', array('externalserviceid'=>$dbservice->id));
c976e271 1028 $DB->delete_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
1029 $DB->delete_records('external_services_users', array('externalserviceid'=>$dbservice->id));
1030 $DB->delete_records('external_services', array('id'=>$dbservice->id));
1031 continue;
1032 }
1033 $service = $services[$dbservice->name];
1034 unset($services[$dbservice->name]);
1035 $service['enabled'] = empty($service['enabled']) ? 0 : $service['enabled'];
1036 $service['requiredcapability'] = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
1037 $service['restrictedusers'] = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
af03513f 1038 $service['downloadfiles'] = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
106c55fb 1039 $service['uploadfiles'] = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles'];
c1b65883 1040 $service['shortname'] = !isset($service['shortname']) ? null : $service['shortname'];
c976e271 1041
1042 $update = false;
c976e271 1043 if ($dbservice->requiredcapability != $service['requiredcapability']) {
1044 $dbservice->requiredcapability = $service['requiredcapability'];
1045 $update = true;
1046 }
1047 if ($dbservice->restrictedusers != $service['restrictedusers']) {
1048 $dbservice->restrictedusers = $service['restrictedusers'];
1049 $update = true;
1050 }
af03513f
JM
1051 if ($dbservice->downloadfiles != $service['downloadfiles']) {
1052 $dbservice->downloadfiles = $service['downloadfiles'];
1053 $update = true;
1054 }
106c55fb
DW
1055 if ($dbservice->uploadfiles != $service['uploadfiles']) {
1056 $dbservice->uploadfiles = $service['uploadfiles'];
1057 $update = true;
1058 }
c1b65883
JM
1059 //if shortname is not a PARAM_ALPHANUMEXT, fail (tested here for service update and creation)
1060 if (isset($service['shortname']) and
1061 (clean_param($service['shortname'], PARAM_ALPHANUMEXT) != $service['shortname'])) {
1062 throw new moodle_exception('installserviceshortnameerror', 'webservice', '', $service['shortname']);
1063 }
1064 if ($dbservice->shortname != $service['shortname']) {
1065 //check that shortname is unique
1066 if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
1067 $existingservice = $DB->get_record('external_services',
1068 array('shortname' => $service['shortname']));
1069 if (!empty($existingservice)) {
1070 throw new moodle_exception('installexistingserviceshortnameerror', 'webservice', '', $service['shortname']);
1071 }
1072 }
1073 $dbservice->shortname = $service['shortname'];
1074 $update = true;
1075 }
c976e271 1076 if ($update) {
1077 $DB->update_record('external_services', $dbservice);
1078 }
1079
1080 $functions = $DB->get_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
1081 foreach ($functions as $function) {
1082 $key = array_search($function->functionname, $service['functions']);
1083 if ($key === false) {
1084 $DB->delete_records('external_services_functions', array('id'=>$function->id));
1085 } else {
1086 unset($service['functions'][$key]);
1087 }
1088 }
1089 foreach ($service['functions'] as $fname) {
365a5941 1090 $newf = new stdClass();
c976e271 1091 $newf->externalserviceid = $dbservice->id;
1092 $newf->functionname = $fname;
1093 $DB->insert_record('external_services_functions', $newf);
1094 }
1095 unset($functions);
1096 }
1097 foreach ($services as $name => $service) {
c1b65883
JM
1098 //check that shortname is unique
1099 if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
1100 $existingservice = $DB->get_record('external_services',
1101 array('shortname' => $service['shortname']));
1102 if (!empty($existingservice)) {
1103 throw new moodle_exception('installserviceshortnameerror', 'webservice');
1104 }
1105 }
1106
365a5941 1107 $dbservice = new stdClass();
c976e271 1108 $dbservice->name = $name;
1109 $dbservice->enabled = empty($service['enabled']) ? 0 : $service['enabled'];
1110 $dbservice->requiredcapability = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
1111 $dbservice->restrictedusers = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
af03513f 1112 $dbservice->downloadfiles = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
106c55fb 1113 $dbservice->uploadfiles = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles'];
c1b65883 1114 $dbservice->shortname = !isset($service['shortname']) ? null : $service['shortname'];
c976e271 1115 $dbservice->component = $component;
e5180580 1116 $dbservice->timecreated = time();
c976e271 1117 $dbservice->id = $DB->insert_record('external_services', $dbservice);
1118 foreach ($service['functions'] as $fname) {
365a5941 1119 $newf = new stdClass();
c976e271 1120 $newf->externalserviceid = $dbservice->id;
1121 $newf->functionname = $fname;
1122 $DB->insert_record('external_services_functions', $newf);
1123 }
1124 }
1125}
1126
72fb21b6 1127/**
1128 * upgrade logging functions
72fb21b6 1129 */
fd1a792e 1130function upgrade_handle_exception($ex, $plugin = null) {
96e1fdf4
PS
1131 global $CFG;
1132
a56c457e
PS
1133 // rollback everything, we need to log all upgrade problems
1134 abort_all_db_transactions();
1135
c19bc39c
PS
1136 $info = get_exception_info($ex);
1137
1138 // First log upgrade error
1139 upgrade_log(UPGRADE_LOG_ERROR, $plugin, 'Exception: ' . get_class($ex), $info->message, $info->backtrace);
1140
1141 // Always turn on debugging - admins need to know what is going on
96f81ea3 1142 set_debugging(DEBUG_DEVELOPER, true);
c19bc39c 1143
fd1a792e 1144 default_exception_handler($ex, true, $plugin);
db9d4a3d 1145}
1146
1147/**
1148 * Adds log entry into upgrade_log table
1149 *
1150 * @param int $type UPGRADE_LOG_NORMAL, UPGRADE_LOG_NOTICE or UPGRADE_LOG_ERROR
bb8b2971 1151 * @param string $plugin frankenstyle component name
db9d4a3d 1152 * @param string $info short description text of log entry
1153 * @param string $details long problem description
1154 * @param string $backtrace string used for errors only
1155 * @return void
1156 */
1157function upgrade_log($type, $plugin, $info, $details=null, $backtrace=null) {
1158 global $DB, $USER, $CFG;
1159
bb8b2971
PS
1160 if (empty($plugin)) {
1161 $plugin = 'core';
1162 }
1163
56da374e 1164 list($plugintype, $pluginname) = core_component::normalize_component($plugin);
bb8b2971 1165 $component = is_null($pluginname) ? $plugintype : $plugintype . '_' . $pluginname;
795a08ad 1166
34a2777c 1167 $backtrace = format_backtrace($backtrace, true);
db9d4a3d 1168
bb8b2971
PS
1169 $currentversion = null;
1170 $targetversion = null;
db9d4a3d 1171
1172 //first try to find out current version number
bb8b2971 1173 if ($plugintype === 'core') {
db9d4a3d 1174 //main
bb8b2971 1175 $currentversion = $CFG->version;
db9d4a3d 1176
bb8b2971
PS
1177 $version = null;
1178 include("$CFG->dirroot/version.php");
1179 $targetversion = $version;
795a08ad 1180
bb8b2971 1181 } else if ($plugintype === 'mod') {
db9d4a3d 1182 try {
bb8b2971
PS
1183 $currentversion = $DB->get_field('modules', 'version', array('name'=>$pluginname));
1184 $currentversion = ($currentversion === false) ? null : $currentversion;
db9d4a3d 1185 } catch (Exception $ignored) {
1186 }
b0d1d941 1187 $cd = core_component::get_component_directory($component);
bb8b2971
PS
1188 if (file_exists("$cd/version.php")) {
1189 $module = new stdClass();
1190 $module->version = null;
1191 include("$cd/version.php");
1192 $targetversion = $module->version;
1193 }
db9d4a3d 1194
bb8b2971 1195 } else if ($plugintype === 'block') {
db9d4a3d 1196 try {
bb8b2971
PS
1197 if ($block = $DB->get_record('block', array('name'=>$pluginname))) {
1198 $currentversion = $block->version;
db9d4a3d 1199 }
1200 } catch (Exception $ignored) {
1201 }
b0d1d941 1202 $cd = core_component::get_component_directory($component);
bb8b2971
PS
1203 if (file_exists("$cd/version.php")) {
1204 $plugin = new stdClass();
1205 $plugin->version = null;
1206 include("$cd/version.php");
1207 $targetversion = $plugin->version;
1208 }
795a08ad 1209
1210 } else {
bb8b2971 1211 $pluginversion = get_config($component, 'version');
795a08ad 1212 if (!empty($pluginversion)) {
bb8b2971
PS
1213 $currentversion = $pluginversion;
1214 }
b0d1d941 1215 $cd = core_component::get_component_directory($component);
bb8b2971
PS
1216 if (file_exists("$cd/version.php")) {
1217 $plugin = new stdClass();
1218 $plugin->version = null;
1219 include("$cd/version.php");
1220 $targetversion = $plugin->version;
795a08ad 1221 }
db9d4a3d 1222 }
1223
365a5941 1224 $log = new stdClass();
bb8b2971
PS
1225 $log->type = $type;
1226 $log->plugin = $component;
1227 $log->version = $currentversion;
1228 $log->targetversion = $targetversion;
1229 $log->info = $info;
1230 $log->details = $details;
1231 $log->backtrace = $backtrace;
1232 $log->userid = $USER->id;
1233 $log->timemodified = time();
db9d4a3d 1234 try {
1235 $DB->insert_record('upgrade_log', $log);
1236 } catch (Exception $ignored) {
795a08ad 1237 // possible during install or 2.0 upgrade
db9d4a3d 1238 }
1239}
1240
1241/**
1242 * Marks start of upgrade, blocks any other access to site.
1243 * The upgrade is finished at the end of script or after timeout.
72fb21b6 1244 *
1245 * @global object
1246 * @global object
1247 * @global object
db9d4a3d 1248 */
1249function upgrade_started($preinstall=false) {
de6d81e6 1250 global $CFG, $DB, $PAGE, $OUTPUT;
db9d4a3d 1251
1252 static $started = false;
1253
1254 if ($preinstall) {
1255 ignore_user_abort(true);
1256 upgrade_setup_debug(true);
1257
1258 } else if ($started) {
1259 upgrade_set_timeout(120);
1260
1261 } else {
c13a5e71 1262 if (!CLI_SCRIPT and !$PAGE->headerprinted) {
db9d4a3d 1263 $strupgrade = get_string('upgradingversion', 'admin');
78946b9b 1264 $PAGE->set_pagelayout('maintenance');
543f54d3 1265 upgrade_init_javascript();
de6d81e6 1266 $PAGE->set_title($strupgrade.' - Moodle '.$CFG->target_release);
1267 $PAGE->set_heading($strupgrade);
1268 $PAGE->navbar->add($strupgrade);
1269 $PAGE->set_cacheable(false);
1270 echo $OUTPUT->header();
db9d4a3d 1271 }
1272
1273 ignore_user_abort(true);
1274 register_shutdown_function('upgrade_finished_handler');
1275 upgrade_setup_debug(true);
1276 set_config('upgraderunning', time()+300);
1277 $started = true;
1278 }
1279}
1280
1281/**
b921b6ee 1282 * Internal function - executed if upgrade interrupted.
db9d4a3d 1283 */
1284function upgrade_finished_handler() {
1285 upgrade_finished();
1286}
1287
1288/**
1289 * Indicates upgrade is finished.
1290 *
1291 * This function may be called repeatedly.
72fb21b6 1292 *
1293 * @global object
1294 * @global object
db9d4a3d 1295 */
1296function upgrade_finished($continueurl=null) {
7e0d6675 1297 global $CFG, $DB, $OUTPUT;
db9d4a3d 1298
1299 if (!empty($CFG->upgraderunning)) {
1300 unset_config('upgraderunning');
24dedb96
SH
1301 // We have to forcefully purge the caches using the writer here.
1302 // This has to be done after we unset the config var. If someone hits the site while this is set they will
1303 // cause the config values to propogate to the caches.
1304 // Caches are purged after the last step in an upgrade but there is several code routines that exceute between
1305 // then and now that leaving a window for things to fall out of sync.
1306 cache_helper::purge_all(true);
db9d4a3d 1307 upgrade_setup_debug(false);
1308 ignore_user_abort(false);
1309 if ($continueurl) {
aa9a6867 1310 echo $OUTPUT->continue_button($continueurl);
7e0d6675 1311 echo $OUTPUT->footer();
db9d4a3d 1312 die;
1313 }
1314 }
1315}
1316
72fb21b6 1317/**
1318 * @global object
1319 * @global object
1320 */
db9d4a3d 1321function upgrade_setup_debug($starting) {
1322 global $CFG, $DB;
1323
1324 static $originaldebug = null;
1325
1326 if ($starting) {
1327 if ($originaldebug === null) {
1328 $originaldebug = $DB->get_debug();
1329 }
1330 if (!empty($CFG->upgradeshowsql)) {
1331 $DB->set_debug(true);
1332 }
1333 } else {
1334 $DB->set_debug($originaldebug);
1335 }
1336}
1337
1338function print_upgrade_separator() {
1339 if (!CLI_SCRIPT) {
1340 echo '<hr />';
1341 }
1342}
1343
795a08ad 1344/**
1345 * Default start upgrade callback
1346 * @param string $plugin
b921b6ee 1347 * @param bool $installation true if installation, false means upgrade
795a08ad 1348 */
194cdca8 1349function print_upgrade_part_start($plugin, $installation, $verbose) {
3c159385 1350 global $OUTPUT;
795a08ad 1351 if (empty($plugin) or $plugin == 'moodle') {
1352 upgrade_started($installation); // does not store upgrade running flag yet
194cdca8 1353 if ($verbose) {
3c159385 1354 echo $OUTPUT->heading(get_string('coresystem'));
194cdca8 1355 }
795a08ad 1356 } else {
1357 upgrade_started();
194cdca8 1358 if ($verbose) {
3c159385 1359 echo $OUTPUT->heading($plugin);
194cdca8 1360 }
795a08ad 1361 }
1362 if ($installation) {
1363 if (empty($plugin) or $plugin == 'moodle') {
1364 // no need to log - log table not yet there ;-)
1365 } else {
1366 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin installation');
1367 }
1368 } else {
1369 if (empty($plugin) or $plugin == 'moodle') {
1370 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting core upgrade');
1371 } else {
1372 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin upgrade');
1373 }
1374 }
1375}
1376
1377/**
1378 * Default end upgrade callback
1379 * @param string $plugin
b921b6ee 1380 * @param bool $installation true if installation, false means upgrade
795a08ad 1381 */
194cdca8 1382function print_upgrade_part_end($plugin, $installation, $verbose) {
aa9a6867 1383 global $OUTPUT;
795a08ad 1384 upgrade_started();
1385 if ($installation) {
1386 if (empty($plugin) or $plugin == 'moodle') {
1387 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core installed');
1388 } else {
1389 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin installed');
1390 }
1391 } else {
1392 if (empty($plugin) or $plugin == 'moodle') {
1393 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core upgraded');
1394 } else {
1395 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin upgraded');
1396 }
1397 }
194cdca8 1398 if ($verbose) {
aa9a6867 1399 echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
194cdca8 1400 print_upgrade_separator();
96db14f9 1401 }
1402}
1403
72fb21b6 1404/**
b921b6ee 1405 * Sets up JS code required for all upgrade scripts.
72fb21b6 1406 * @global object
1407 */
543f54d3 1408function upgrade_init_javascript() {
e29380f3 1409 global $PAGE;
543f54d3
PS
1410 // scroll to the end of each upgrade page so that ppl see either error or continue button,
1411 // no need to scroll continuously any more, it is enough to jump to end once the footer is printed ;-)
1412 $js = "window.scrollTo(0, 5000000);";
1413 $PAGE->requires->js_init_code($js);
db9d4a3d 1414}
1415
db9d4a3d 1416/**
1417 * Try to upgrade the given language pack (or current language)
af8d4d91 1418 *
74a4c9a9 1419 * @param string $lang the code of the language to update, defaults to the current language
db9d4a3d 1420 */
bd41bdd9
PS
1421function upgrade_language_pack($lang = null) {
1422 global $CFG;
db9d4a3d 1423
bd41bdd9
PS
1424 if (!empty($CFG->skiplangupgrade)) {
1425 return;
1426 }
af8d4d91 1427
bd41bdd9
PS
1428 if (!file_exists("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php")) {
1429 // weird, somebody uninstalled the import utility
1430 return;
1431 }
1432
1433 if (!$lang) {
db9d4a3d 1434 $lang = current_language();
1435 }
1436
bd41bdd9
PS
1437 if (!get_string_manager()->translation_exists($lang)) {
1438 return;
db9d4a3d 1439 }
1440
bd41bdd9
PS
1441 get_string_manager()->reset_caches();
1442
1443 if ($lang === 'en') {
1444 return; // Nothing to do
db9d4a3d 1445 }
af8d4d91 1446
bd41bdd9
PS
1447 upgrade_started(false);
1448
1449 require_once("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php");
1450 tool_langimport_preupgrade_update($lang);
1451
af8d4d91
DM
1452 get_string_manager()->reset_caches();
1453
551fe0e5 1454 print_upgrade_separator();
db9d4a3d 1455}
8580535b 1456
1457/**
1458 * Install core moodle tables and initialize
1459 * @param float $version target version
1460 * @param bool $verbose
1461 * @return void, may throw exception
1462 */
1463function install_core($version, $verbose) {
1464 global $CFG, $DB;
1465
e2e35e71 1466 // We can not call purge_all_caches() yet, make sure the temp and cache dirs exist and are empty.
e2e35e71 1467 remove_dir($CFG->cachedir.'', true);
85b38061
PS
1468 make_cache_directory('', true);
1469
1470 remove_dir($CFG->localcachedir.'', true);
1471 make_localcache_directory('', true);
1472
e2e35e71 1473 remove_dir($CFG->tempdir.'', true);
85b38061
PS
1474 make_temp_directory('', true);
1475
e2e35e71 1476 remove_dir($CFG->dataroot.'/muc', true);
85b38061 1477 make_writable_directory($CFG->dataroot.'/muc', true);
e2e35e71 1478
8580535b 1479 try {
c3d0e149 1480 set_time_limit(600);
194cdca8 1481 print_upgrade_part_start('moodle', true, $verbose); // does not store upgrade running flag
8580535b 1482
1483 $DB->get_manager()->install_from_xmldb_file("$CFG->libdir/db/install.xml");
1484 upgrade_started(); // we want the flag to be stored in config table ;-)
1485
1486 // set all core default records and default settings
1487 require_once("$CFG->libdir/db/install.php");
c6d75bff 1488 xmldb_main_install(); // installs the capabilities too
8580535b 1489
1490 // store version
1491 upgrade_main_savepoint(true, $version, false);
1492
df997f84 1493 // Continue with the installation
c6d75bff
PS
1494 log_update_descriptions('moodle');
1495 external_update_descriptions('moodle');
8580535b 1496 events_update_definition('moodle');
1497 message_update_providers('moodle');
8580535b 1498
df997f84 1499 // Write default settings unconditionally
8580535b 1500 admin_apply_default_settings(NULL, true);
1501
194cdca8 1502 print_upgrade_part_end(null, true, $verbose);
94ef67cf 1503
e0d9b7c0
SH
1504 // Purge all caches. They're disabled but this ensures that we don't have any persistent data just in case something
1505 // during installation didn't use APIs.
1506 cache_helper::purge_all();
8580535b 1507 } catch (exception $ex) {
1508 upgrade_handle_exception($ex);
1509 }
1510}
1511
1512/**
1513 * Upgrade moodle core
1514 * @param float $version target version
1515 * @param bool $verbose
1516 * @return void, may throw exception
1517 */
1518function upgrade_core($version, $verbose) {
1519 global $CFG;
1520
41209c1e
PS
1521 raise_memory_limit(MEMORY_EXTRA);
1522
3316fe24 1523 require_once($CFG->libdir.'/db/upgrade.php'); // Defines upgrades
3316fe24 1524
8580535b 1525 try {
7b4d5af7 1526 // Reset caches before any output.
b0dd08dd 1527 cache_helper::purge_all(true);
7b4d5af7 1528 purge_all_caches();
dcf9be7f 1529
8580535b 1530 // Upgrade current language pack if we can
bd41bdd9 1531 upgrade_language_pack();
8580535b 1532
194cdca8 1533 print_upgrade_part_start('moodle', false, $verbose);
8580535b 1534
6b4b7490
PS
1535 // Pre-upgrade scripts for local hack workarounds.
1536 $preupgradefile = "$CFG->dirroot/local/preupgrade.php";
1537 if (file_exists($preupgradefile)) {
1538 set_time_limit(0);
1539 require($preupgradefile);
1540 // Reset upgrade timeout to default.
1541 upgrade_set_timeout();
17da2e6f 1542 }
1543
8580535b 1544 $result = xmldb_main_upgrade($CFG->version);
1545 if ($version > $CFG->version) {
1546 // store version if not already there
1547 upgrade_main_savepoint($result, $version, false);
1548 }
1549
1550 // perform all other component upgrade routines
1551 update_capabilities('moodle');
c6d75bff 1552 log_update_descriptions('moodle');
c976e271 1553 external_update_descriptions('moodle');
8580535b 1554 events_update_definition('moodle');
1555 message_update_providers('moodle');
75af47ee
SH
1556 // Update core definitions.
1557 cache_helper::update_definitions(true);
8580535b 1558
94ef67cf 1559 // Purge caches again, just to be sure we arn't holding onto old stuff now.
a5a6e93f 1560 cache_helper::purge_all(true);
7b4d5af7 1561 purge_all_caches();
2e5eba85
PS
1562
1563 // Clean up contexts - more and more stuff depends on existence of paths and contexts
e922fe23
PS
1564 context_helper::cleanup_instances();
1565 context_helper::create_instances(null, false);
1566 context_helper::build_all_paths(false);
1567 $syscontext = context_system::instance();
1568 $syscontext->mark_dirty();
8580535b 1569
194cdca8 1570 print_upgrade_part_end('moodle', false, $verbose);
8580535b 1571 } catch (Exception $ex) {
1572 upgrade_handle_exception($ex);
1573 }
1574}
1575
1576/**
1577 * Upgrade/install other parts of moodle
1578 * @param bool $verbose
1579 * @return void, may throw exception
1580 */
1581function upgrade_noncore($verbose) {
1582 global $CFG;
1583
41209c1e
PS
1584 raise_memory_limit(MEMORY_EXTRA);
1585
8580535b 1586 // upgrade all plugins types
1587 try {
7b4d5af7
PS
1588 // Reset caches before any output.
1589 cache_helper::purge_all(true);
1590 purge_all_caches();
1591
46f6f7f2 1592 $plugintypes = core_component::get_plugin_types();
8580535b 1593 foreach ($plugintypes as $type=>$location) {
17da2e6f 1594 upgrade_plugins($type, 'print_upgrade_part_start', 'print_upgrade_part_end', $verbose);
8580535b 1595 }
94ef67cf 1596 // Update cache definitions. Involves scanning each plugin for any changes.
75af47ee 1597 cache_helper::update_definitions();
c5701ce7
PS
1598 // Mark the site as upgraded.
1599 set_config('allversionshash', core_component::get_all_versions_hash());
7b4d5af7
PS
1600
1601 // Purge caches again, just to be sure we arn't holding onto old stuff now.
1602 cache_helper::purge_all(true);
1603 purge_all_caches();
1604
8580535b 1605 } catch (Exception $ex) {
1606 upgrade_handle_exception($ex);
1607 }
8580535b 1608}
3316fe24 1609
1610/**
1611 * Checks if the main tables have been installed yet or not.
e2e35e71
PS
1612 *
1613 * Note: we can not use caches here because they might be stale,
1614 * use with care!
1615 *
3316fe24 1616 * @return bool
1617 */
1618function core_tables_exist() {
1619 global $DB;
1620
e2e35e71 1621 if (!$tables = $DB->get_tables(false) ) { // No tables yet at all.
3316fe24 1622 return false;
9b683d13 1623
3316fe24 1624 } else { // Check for missing main tables
1625 $mtables = array('config', 'course', 'groupings'); // some tables used in 1.9 and 2.0, preferable something from the start and end of install.xml
1626 foreach ($mtables as $mtable) {
1627 if (!in_array($mtable, $tables)) {
1628 return false;
1629 }
1630 }
1631 return true;
9b683d13 1632 }
ba04999c 1633}
c71ade2f
PL
1634
1635/**
1636 * upgrades the mnet rpc definitions for the given component.
1637 * this method doesn't return status, an exception will be thrown in the case of an error
1638 *
1639 * @param string $component the plugin to upgrade, eg auth_mnet
1640 */
1641function upgrade_plugin_mnet_functions($component) {
1642 global $DB, $CFG;
1643
1644 list($type, $plugin) = explode('_', $component);
1c74b260 1645 $path = core_component::get_plugin_directory($type, $plugin);
c71ade2f 1646
17b71716
PS
1647 $publishes = array();
1648 $subscribes = array();
c71ade2f
PL
1649 if (file_exists($path . '/db/mnet.php')) {
1650 require_once($path . '/db/mnet.php'); // $publishes comes from this file
1651 }
1652 if (empty($publishes)) {
1653 $publishes = array(); // still need this to be able to disable stuff later
1654 }
1655 if (empty($subscribes)) {
1656 $subscribes = array(); // still need this to be able to disable stuff later
1657 }
1658
1659 static $servicecache = array();
1660
1661 // rekey an array based on the rpc method for easy lookups later
1662 $publishmethodservices = array();
1663 $subscribemethodservices = array();
1664 foreach($publishes as $servicename => $service) {
1665 if (is_array($service['methods'])) {
1666 foreach($service['methods'] as $methodname) {
1667 $service['servicename'] = $servicename;
1668 $publishmethodservices[$methodname][] = $service;
1669 }
1670 }
1671 }
1672
1673 // Disable functions that don't exist (any more) in the source
1674 // Should these be deleted? What about their permissions records?
1675 foreach ($DB->get_records('mnet_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
1676 if (!array_key_exists($rpc->functionname, $publishmethodservices) && $rpc->enabled) {
1677 $DB->set_field('mnet_rpc', 'enabled', 0, array('id' => $rpc->id));
1678 } else if (array_key_exists($rpc->functionname, $publishmethodservices) && !$rpc->enabled) {
1679 $DB->set_field('mnet_rpc', 'enabled', 1, array('id' => $rpc->id));
1680 }
1681 }
1682
1683 // reflect all the services we're publishing and save them
1684 require_once($CFG->dirroot . '/lib/zend/Zend/Server/Reflection.php');
1685 static $cachedclasses = array(); // to store reflection information in
1686 foreach ($publishes as $service => $data) {
1687 $f = $data['filename'];
1688 $c = $data['classname'];
1689 foreach ($data['methods'] as $method) {
6bdfef5d 1690 $dataobject = new stdClass();
c71ade2f
PL
1691 $dataobject->plugintype = $type;
1692 $dataobject->pluginname = $plugin;
1693 $dataobject->enabled = 1;
1694 $dataobject->classname = $c;
1695 $dataobject->filename = $f;
1696
1697 if (is_string($method)) {
1698 $dataobject->functionname = $method;
1699
1700 } else if (is_array($method)) { // wants to override file or class
1701 $dataobject->functionname = $method['method'];
1702 $dataobject->classname = $method['classname'];
1703 $dataobject->filename = $method['filename'];
1704 }
1705 $dataobject->xmlrpcpath = $type.'/'.$plugin.'/'.$dataobject->filename.'/'.$method;
1706 $dataobject->static = false;
1707
1708 require_once($path . '/' . $dataobject->filename);
1709 $functionreflect = null; // slightly different ways to get this depending on whether it's a class method or a function
1710 if (!empty($dataobject->classname)) {
1711 if (!class_exists($dataobject->classname)) {
1712 throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
1713 }
1714 $key = $dataobject->filename . '|' . $dataobject->classname;
1715 if (!array_key_exists($key, $cachedclasses)) { // look to see if we've already got a reflection object
1716 try {
1717 $cachedclasses[$key] = Zend_Server_Reflection::reflectClass($dataobject->classname);
1718 } catch (Zend_Server_Reflection_Exception $e) { // catch these and rethrow them to something more helpful
1719 throw new moodle_exception('installreflectionclasserror', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname, 'error' => $e->getMessage()));
1720 }
1721 }
1722 $r =& $cachedclasses[$key];
1723 if (!$r->hasMethod($dataobject->functionname)) {
1724 throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
1725 }
1726 // stupid workaround for zend not having a getMethod($name) function
1727 $ms = $r->getMethods();
1728 foreach ($ms as $m) {
1729 if ($m->getName() == $dataobject->functionname) {
1730 $functionreflect = $m;
1731 break;
1732 }
1733 }
1734 $dataobject->static = (int)$functionreflect->isStatic();
1735 } else {
1736 if (!function_exists($dataobject->functionname)) {
1737 throw new moodle_exception('installnosuchfunction', 'mnet', '', (object)array('method' => $dataobject->functionname, 'file' => $dataobject->filename));
1738 }
1739 try {
1740 $functionreflect = Zend_Server_Reflection::reflectFunction($dataobject->functionname);
1741 } catch (Zend_Server_Reflection_Exception $e) { // catch these and rethrow them to something more helpful
1742 throw new moodle_exception('installreflectionfunctionerror', 'mnet', '', (object)array('method' => $dataobject->functionname, '' => $dataobject->filename, 'error' => $e->getMessage()));
1743 }
1744 }
1745 $dataobject->profile = serialize(admin_mnet_method_profile($functionreflect));
1746 $dataobject->help = $functionreflect->getDescription();
1747
1748 if ($record_exists = $DB->get_record('mnet_rpc', array('xmlrpcpath'=>$dataobject->xmlrpcpath))) {
1749 $dataobject->id = $record_exists->id;
1750 $dataobject->enabled = $record_exists->enabled;
1751 $DB->update_record('mnet_rpc', $dataobject);
1752 } else {
1753 $dataobject->id = $DB->insert_record('mnet_rpc', $dataobject, true);
1754 }
c71ade2f 1755
d5bd0b01
DM
1756 // TODO this API versioning must be reworked, here the recently processed method
1757 // sets the service API which may not be correct
677b6321
PL
1758 foreach ($publishmethodservices[$dataobject->functionname] as $service) {
1759 if ($serviceobj = $DB->get_record('mnet_service', array('name'=>$service['servicename']))) {
1760 $serviceobj->apiversion = $service['apiversion'];
1761 $DB->update_record('mnet_service', $serviceobj);
1762 } else {
1763 $serviceobj = new stdClass();
1764 $serviceobj->name = $service['servicename'];
f4a2817a 1765 $serviceobj->description = empty($service['description']) ? '' : $service['description'];
677b6321
PL
1766 $serviceobj->apiversion = $service['apiversion'];
1767 $serviceobj->offer = 1;
1768 $serviceobj->id = $DB->insert_record('mnet_service', $serviceobj);
1769 }
1770 $servicecache[$service['servicename']] = $serviceobj;
1771 if (!$DB->record_exists('mnet_service2rpc', array('rpcid'=>$dataobject->id, 'serviceid'=>$serviceobj->id))) {
1772 $obj = new stdClass();
1773 $obj->rpcid = $dataobject->id;
1774 $obj->serviceid = $serviceobj->id;
1775 $DB->insert_record('mnet_service2rpc', $obj, true);
1776 }
c71ade2f
PL
1777 }
1778 }
1779 }
c71ade2f
PL
1780 // finished with methods we publish, now do subscribable methods
1781 foreach($subscribes as $service => $methods) {
1782 if (!array_key_exists($service, $servicecache)) {
1783 if (!$serviceobj = $DB->get_record('mnet_service', array('name' => $service))) {
81634f03 1784 debugging("TODO: skipping unknown service $service - somebody needs to fix MDL-21993");
c71ade2f
PL
1785 continue;
1786 }
1787 $servicecache[$service] = $serviceobj;
1788 } else {
1789 $serviceobj = $servicecache[$service];
1790 }
1791 foreach ($methods as $method => $xmlrpcpath) {
1792 if (!$rpcid = $DB->get_field('mnet_remote_rpc', 'id', array('xmlrpcpath'=>$xmlrpcpath))) {
1793 $remoterpc = (object)array(
1794 'functionname' => $method,
1795 'xmlrpcpath' => $xmlrpcpath,
1796 'plugintype' => $type,
1797 'pluginname' => $plugin,
1798 'enabled' => 1,
1799 );
1800 $rpcid = $remoterpc->id = $DB->insert_record('mnet_remote_rpc', $remoterpc, true);
1801 }
1802 if (!$DB->record_exists('mnet_remote_service2rpc', array('rpcid'=>$rpcid, 'serviceid'=>$serviceobj->id))) {
1803 $obj = new stdClass();
1804 $obj->rpcid = $rpcid;
1805 $obj->serviceid = $serviceobj->id;
1806 $DB->insert_record('mnet_remote_service2rpc', $obj, true);
1807 }
677b6321 1808 $subscribemethodservices[$method][] = $service;
c71ade2f
PL
1809 }
1810 }
1811
1812 foreach ($DB->get_records('mnet_remote_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
1813 if (!array_key_exists($rpc->functionname, $subscribemethodservices) && $rpc->enabled) {
1814 $DB->set_field('mnet_remote_rpc', 'enabled', 0, array('id' => $rpc->id));
1815 } else if (array_key_exists($rpc->functionname, $subscribemethodservices) && !$rpc->enabled) {
1816 $DB->set_field('mnet_remote_rpc', 'enabled', 1, array('id' => $rpc->id));
1817 }
1818 }
1819
1820 return true;
1821}
1822
1823/**
1824 * Given some sort of Zend Reflection function/method object, return a profile array, ready to be serialized and stored
1825 *
1826 * @param Zend_Server_Reflection_Function_Abstract $function can be any subclass of this object type
1827 *
1828 * @return array
1829 */
1830function admin_mnet_method_profile(Zend_Server_Reflection_Function_Abstract $function) {
f20edd52
PS
1831 $protos = $function->getPrototypes();
1832 $proto = array_pop($protos);
c71ade2f
PL
1833 $ret = $proto->getReturnValue();
1834 $profile = array(
1835 'parameters' => array(),
1836 'return' => array(
1837 'type' => $ret->getType(),
1838 'description' => $ret->getDescription(),
1839 ),
1840 );
1841 foreach ($proto->getParameters() as $p) {
1842 $profile['parameters'][] = array(
1843 'name' => $p->getName(),
1844 'type' => $p->getType(),
1845 'description' => $p->getDescription(),
1846 );
1847 }
1848 return $profile;
1849}
424a19b1
AB
1850
1851
1852/**
1853 * This function finds duplicate records (based on combinations of fields that should be unique)
1854 * and then progamatically generated a "most correct" version of the data, update and removing
1855 * records as appropriate
1856 *
1857 * Thanks to Dan Marsden for help
1858 *
1859 * @param string $table Table name
1860 * @param array $uniques Array of field names that should be unique
e6f55285 1861 * @param array $fieldstocheck Array of fields to generate "correct" data from (optional)
424a19b1
AB
1862 * @return void
1863 */
1864function upgrade_course_completion_remove_duplicates($table, $uniques, $fieldstocheck = array()) {
1865 global $DB;
1866
1867 // Find duplicates
1868 $sql_cols = implode(', ', $uniques);
1869
1870 $sql = "SELECT {$sql_cols} FROM {{$table}} GROUP BY {$sql_cols} HAVING (count(id) > 1)";
1871 $duplicates = $DB->get_recordset_sql($sql, array());
1872
1873 // Loop through duplicates
1874 foreach ($duplicates as $duplicate) {
1875 $pointer = 0;
1876
1877 // Generate SQL for finding records with these duplicate uniques
e6f55285 1878 $sql_select = implode(' = ? AND ', $uniques).' = ?'; // builds "fieldname = ? AND fieldname = ?"
424a19b1
AB
1879 $uniq_values = array();
1880 foreach ($uniques as $u) {
1881 $uniq_values[] = $duplicate->$u;
1882 }
1883
1884 $sql_order = implode(' DESC, ', $uniques).' DESC'; // builds "fieldname DESC, fieldname DESC"
1885
1886 // Get records with these duplicate uniques
1887 $records = $DB->get_records_select(
1888 $table,
1889 $sql_select,
1890 $uniq_values,
1891 $sql_order
1892 );
1893
1894 // Loop through and build a "correct" record, deleting the others
1895 $needsupdate = false;
1896 $origrecord = null;
1897 foreach ($records as $record) {
1898 $pointer++;
1899 if ($pointer === 1) { // keep 1st record but delete all others.
1900 $origrecord = $record;
1901 } else {
1902 // If we have fields to check, update original record
1903 if ($fieldstocheck) {
1904 // we need to keep the "oldest" of all these fields as the valid completion record.
1905 // but we want to ignore null values
1906 foreach ($fieldstocheck as $f) {
1907 if ($record->$f && (($origrecord->$f > $record->$f) || !$origrecord->$f)) {
1908 $origrecord->$f = $record->$f;
1909 $needsupdate = true;
1910 }
1911 }
1912 }
1913 $DB->delete_records($table, array('id' => $record->id));
1914 }
1915 }
1916 if ($needsupdate || isset($origrecord->reaggregate)) {
e6f55285 1917 // If this table has a reaggregate field, update to force recheck on next cron run
424a19b1
AB
1918 if (isset($origrecord->reaggregate)) {
1919 $origrecord->reaggregate = time();
1920 }
1921 $DB->update_record($table, $origrecord);
1922 }
1923 }
1924}
67e3dd2c
FM
1925
1926/**
1927 * Find questions missing an existing category and associate them with
1928 * a category which purpose is to gather them.
1929 *
1930 * @return void
1931 */
1932function upgrade_save_orphaned_questions() {
1933 global $DB;
1934
1935 // Looking for orphaned questions
1936 $orphans = $DB->record_exists_select('question',
1937 'NOT EXISTS (SELECT 1 FROM {question_categories} WHERE {question_categories}.id = {question}.category)');
1938 if (!$orphans) {
1939 return;
1940 }
1941
1942 // Generate a unique stamp for the orphaned questions category, easier to identify it later on
1943 $uniquestamp = "unknownhost+120719170400+orphan";
1944 $systemcontext = context_system::instance();
1945
1946 // Create the orphaned category at system level
1947 $cat = $DB->get_record('question_categories', array('stamp' => $uniquestamp,
1948 'contextid' => $systemcontext->id));
1949 if (!$cat) {
1950 $cat = new stdClass();
1951 $cat->parent = 0;
1952 $cat->contextid = $systemcontext->id;
1953 $cat->name = get_string('orphanedquestionscategory', 'question');
1954 $cat->info = get_string('orphanedquestionscategoryinfo', 'question');
1955 $cat->sortorder = 999;
1956 $cat->stamp = $uniquestamp;
1957 $cat->id = $DB->insert_record("question_categories", $cat);
1958 }
1959
1960 // Set a category to those orphans
1961 $params = array('catid' => $cat->id);
1962 $DB->execute('UPDATE {question} SET category = :catid WHERE NOT EXISTS
1963 (SELECT 1 FROM {question_categories} WHERE {question_categories}.id = {question}.category)', $params);
1964}
1965
d8271e39
FM
1966/**
1967 * Rename old backup files to current backup files.
1968 *
1969 * When added the setting 'backup_shortname' (MDL-28657) the backup file names did not contain the id of the course.
1970 * Further we fixed that behaviour by forcing the id to be always present in the file name (MDL-33812).
1971 * This function will explore the backup directory and attempt to rename the previously created files to include
1972 * the id in the name. Doing this will put them back in the process of deleting the excess backups for each course.
1973 *
1974 * This function manually recreates the file name, instead of using
1975 * {@link backup_plan_dbops::get_default_backup_filename()}, use it carefully if you're using it outside of the
1976 * usual upgrade process.
1977 *
1978 * @see backup_cron_automated_helper::remove_excess_backups()
1979 * @link http://tracker.moodle.org/browse/MDL-35116
1980 * @return void
1981 * @since 2.4
1982 */
1983function upgrade_rename_old_backup_files_using_shortname() {
1984 global $CFG;
1985 $dir = get_config('backup', 'backup_auto_destination');
1986 $useshortname = get_config('backup', 'backup_shortname');
1987 if (empty($dir) || !is_dir($dir) || !is_writable($dir)) {
1988 return;
1989 }
1990
d8271e39 1991 require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php');
2f1e464a 1992 $backupword = str_replace(' ', '_', core_text::strtolower(get_string('backupfilename')));
d8271e39
FM
1993 $backupword = trim(clean_filename($backupword), '_');
1994 $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-';
1995 $regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#';
1996 $thirtyapril = strtotime('30 April 2012 00:00');
1997
1998 // Reading the directory.
1999 if (!$files = scandir($dir)) {
2000 return;
2001 }
2002 foreach ($files as $file) {
2003 // Skip directories and files which do not start with the common prefix.
2004 // This avoids working on files which are not related to this issue.
2005 if (!is_file($dir . '/' . $file) || !preg_match($regex, $file)) {
2006 continue;
2007 }
2008
2009 // Extract the information from the XML file.
2010 try {
2011 $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file);
2012 } catch (backup_helper_exception $e) {
2013 // Some error while retrieving the backup informations, skipping...
2014 continue;
2015 }
2016
2017 // Make sure this a course backup.
2018 if ($bcinfo->format !== backup::FORMAT_MOODLE || $bcinfo->type !== backup::TYPE_1COURSE) {
2019 continue;
2020 }
2021
2022 // Skip the backups created before the short name option was initially introduced (MDL-28657).
2023 // This was integrated on the 2nd of May 2012. Let's play safe with timezone and use the 30th of April.
2024 if ($bcinfo->backup_date < $thirtyapril) {
2025 continue;
2026 }
2027
2028 // Let's check if the file name contains the ID where it is supposed to be, if it is the case then
2029 // we will skip the file. Of course it could happen that the course ID is identical to the course short name
2030 // even though really unlikely, but then renaming this file is not necessary. If the ID is not found in the
2031 // file name then it was probably the short name which was used.
2032 $idfilename = $filename . $bcinfo->original_course_id . '-';
2033 $idregex = '#^'.preg_quote($idfilename, '#').'.*\.mbz$#';
2034 if (preg_match($idregex, $file)) {
2035 continue;
2036 }
2037
2038 // Generating the file name manually. We do not use backup_plan_dbops::get_default_backup_filename() because
2039 // it will query the database to get some course information, and the course could not exist any more.
2040 $newname = $filename . $bcinfo->original_course_id . '-';
2041 if ($useshortname) {
2042 $shortname = str_replace(' ', '_', $bcinfo->original_course_shortname);
2f1e464a 2043 $shortname = core_text::strtolower(trim(clean_filename($shortname), '_'));
d8271e39
FM
2044 $newname .= $shortname . '-';
2045 }
2046
2047 $backupdateformat = str_replace(' ', '_', get_string('backupnameformat', 'langconfig'));
2048 $date = userdate($bcinfo->backup_date, $backupdateformat, 99, false);
2f1e464a 2049 $date = core_text::strtolower(trim(clean_filename($date), '_'));
d8271e39
FM
2050 $newname .= $date;
2051
2052 if (isset($bcinfo->root_settings['users']) && !$bcinfo->root_settings['users']) {
2053 $newname .= '-nu';
2054 } else if (isset($bcinfo->root_settings['anonymize']) && $bcinfo->root_settings['anonymize']) {
2055 $newname .= '-an';
2056 }
2057 $newname .= '.mbz';
2058
2059 // Final check before attempting the renaming.
2060 if ($newname == $file || file_exists($dir . '/' . $newname)) {
2061 continue;
2062 }
2063 @rename($dir . '/' . $file, $dir . '/' . $newname);
2064 }
2065}