MDL-32349 Installing can time out with large number of plugins
[moodle.git] / lib / upgradelib.php
1 <?php
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/>.
18 /**
19  * Various upgrade/install related functions and classes.
20  *
21  * @package    core
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
25  */
27 defined('MOODLE_INTERNAL') || die();
29 /** UPGRADE_LOG_NORMAL = 0 */
30 define('UPGRADE_LOG_NORMAL', 0);
31 /** UPGRADE_LOG_NOTICE = 1 */
32 define('UPGRADE_LOG_NOTICE', 1);
33 /** UPGRADE_LOG_ERROR = 2 */
34 define('UPGRADE_LOG_ERROR',  2);
36 /**
37  * Exception indicating unknown error during upgrade.
38  *
39  * @package    core
40  * @subpackage upgrade
41  * @copyright  2009 Petr Skoda {@link http://skodak.org}
42  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43  */
44 class upgrade_exception extends moodle_exception {
45     function __construct($plugin, $version, $debuginfo=NULL) {
46         global $CFG;
47         $a = (object)array('plugin'=>$plugin, 'version'=>$version);
48         parent::__construct('upgradeerror', 'admin', "$CFG->wwwroot/$CFG->admin/index.php", $a, $debuginfo);
49     }
50 }
52 /**
53  * Exception indicating downgrade error during upgrade.
54  *
55  * @package    core
56  * @subpackage upgrade
57  * @copyright  2009 Petr Skoda {@link http://skodak.org}
58  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
59  */
60 class 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 }
69 /**
70  * @package    core
71  * @subpackage upgrade
72  * @copyright  2009 Petr Skoda {@link http://skodak.org}
73  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
74  */
75 class upgrade_requires_exception extends moodle_exception {
76     function __construct($plugin, $pluginversion, $currentmoodle, $requiremoodle) {
77         global $CFG;
78         $a = new stdClass();
79         $a->pluginname     = $plugin;
80         $a->pluginversion  = $pluginversion;
81         $a->currentmoodle  = $currentmoodle;
82         $a->requiremoodle  = $requiremoodle;
83         parent::__construct('pluginrequirementsnotmet', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $a);
84     }
85 }
87 /**
88  * @package    core
89  * @subpackage upgrade
90  * @copyright  2009 Petr Skoda {@link http://skodak.org}
91  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
92  */
93 class 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 }
100 /**
101  * Upgrade savepoint, marks end of each upgrade block.
102  * It stores new main version, resets upgrade timeout
103  * and abort upgrade if user cancels page loading.
104  *
105  * Please do not make large upgrade blocks with lots of operations,
106  * for example when adding tables keep only one table operation per block.
107  *
108  * @category upgrade
109  * @param bool $result false if upgrade step failed, true if completed
110  * @param string or float $version main version
111  * @param bool $allowabort allow user to abort script execution here
112  * @return void
113  */
114 function upgrade_main_savepoint($result, $version, $allowabort=true) {
115     global $CFG;
117     //sanity check to avoid confusion with upgrade_mod_savepoint usage.
118     if (!is_bool($allowabort)) {
119         $errormessage = 'Parameter type mismatch. Are you mixing up upgrade_main_savepoint() and upgrade_mod_savepoint()?';
120         throw new coding_exception($errormessage);
121     }
123     if (!$result) {
124         throw new upgrade_exception(null, $version);
125     }
127     if ($CFG->version >= $version) {
128         // something really wrong is going on in main upgrade script
129         throw new downgrade_exception(null, $CFG->version, $version);
130     }
132     set_config('version', $version);
133     upgrade_log(UPGRADE_LOG_NORMAL, null, 'Upgrade savepoint reached');
135     // reset upgrade timeout to default
136     upgrade_set_timeout();
138     // this is a safe place to stop upgrades if user aborts page loading
139     if ($allowabort and connection_aborted()) {
140         die;
141     }
144 /**
145  * Module upgrade savepoint, marks end of module upgrade blocks
146  * It stores module version, resets upgrade timeout
147  * and abort upgrade if user cancels page loading.
148  *
149  * @category upgrade
150  * @param bool $result false if upgrade step failed, true if completed
151  * @param string or float $version main version
152  * @param string $modname name of module
153  * @param bool $allowabort allow user to abort script execution here
154  * @return void
155  */
156 function upgrade_mod_savepoint($result, $version, $modname, $allowabort=true) {
157     global $DB;
159     if (!$result) {
160         throw new upgrade_exception("mod_$modname", $version);
161     }
163     if (!$module = $DB->get_record('modules', array('name'=>$modname))) {
164         print_error('modulenotexist', 'debug', '', $modname);
165     }
167     if ($module->version >= $version) {
168         // something really wrong is going on in upgrade script
169         throw new downgrade_exception("mod_$modname", $module->version, $version);
170     }
171     $module->version = $version;
172     $DB->update_record('modules', $module);
173     upgrade_log(UPGRADE_LOG_NORMAL, "mod_$modname", 'Upgrade savepoint reached');
175     // reset upgrade timeout to default
176     upgrade_set_timeout();
178     // this is a safe place to stop upgrades if user aborts page loading
179     if ($allowabort and connection_aborted()) {
180         die;
181     }
184 /**
185  * Blocks upgrade savepoint, marks end of blocks upgrade blocks
186  * It stores block version, resets upgrade timeout
187  * and abort upgrade if user cancels page loading.
188  *
189  * @category upgrade
190  * @param bool $result false if upgrade step failed, true if completed
191  * @param string or float $version main version
192  * @param string $blockname name of block
193  * @param bool $allowabort allow user to abort script execution here
194  * @return void
195  */
196 function upgrade_block_savepoint($result, $version, $blockname, $allowabort=true) {
197     global $DB;
199     if (!$result) {
200         throw new upgrade_exception("block_$blockname", $version);
201     }
203     if (!$block = $DB->get_record('block', array('name'=>$blockname))) {
204         print_error('blocknotexist', 'debug', '', $blockname);
205     }
207     if ($block->version >= $version) {
208         // something really wrong is going on in upgrade script
209         throw new downgrade_exception("block_$blockname", $block->version, $version);
210     }
211     $block->version = $version;
212     $DB->update_record('block', $block);
213     upgrade_log(UPGRADE_LOG_NORMAL, "block_$blockname", 'Upgrade savepoint reached');
215     // reset upgrade timeout to default
216     upgrade_set_timeout();
218     // this is a safe place to stop upgrades if user aborts page loading
219     if ($allowabort and connection_aborted()) {
220         die;
221     }
224 /**
225  * Plugins upgrade savepoint, marks end of blocks upgrade blocks
226  * It stores plugin version, resets upgrade timeout
227  * and abort upgrade if user cancels page loading.
228  *
229  * @category upgrade
230  * @param bool $result false if upgrade step failed, true if completed
231  * @param string or float $version main version
232  * @param string $type name of plugin
233  * @param string $dir location of plugin
234  * @param bool $allowabort allow user to abort script execution here
235  * @return void
236  */
237 function upgrade_plugin_savepoint($result, $version, $type, $plugin, $allowabort=true) {
238     $component = $type.'_'.$plugin;
240     if (!$result) {
241         throw new upgrade_exception($component, $version);
242     }
244     $installedversion = get_config($component, 'version');
245     if ($installedversion >= $version) {
246         // Something really wrong is going on in the upgrade script
247         throw new downgrade_exception($component, $installedversion, $version);
248     }
249     set_config('version', $version, $component);
250     upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
252     // Reset upgrade timeout to default
253     upgrade_set_timeout();
255     // This is a safe place to stop upgrades if user aborts page loading
256     if ($allowabort and connection_aborted()) {
257         die;
258     }
261 /**
262  * Detect if there are leftovers in PHP source files.
263  *
264  * During main version upgrades administrators MUST move away
265  * old PHP source files and start from scratch (or better
266  * use git).
267  *
268  * @return bool true means borked upgrade, false means previous PHP files were properly removed
269  */
270 function upgrade_stale_php_files_present() {
271     global $CFG;
273     $someexamplesofremovedfiles = array(
274         // removed in 2.3dev
275         '/lib/minify/builder/',
276         // removed in 2.2dev
277         '/lib/yui/3.4.1pr1/',
278         // removed in 2.2
279         '/search/cron_php5.php',
280         '/course/report/log/indexlive.php',
281         '/admin/report/backups/index.php',
282         '/admin/generator.php',
283         // removed in 2.1
284         '/lib/yui/2.8.0r4/',
285         // removed in 2.0
286         '/blocks/admin/block_admin.php',
287         '/blocks/admin_tree/block_admin_tree.php',
288     );
290     foreach ($someexamplesofremovedfiles as $file) {
291         if (file_exists($CFG->dirroot.$file)) {
292             return true;
293         }
294     }
296     return false;
299 /**
300  * Upgrade plugins
301  * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
302  * return void
303  */
304 function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
305     global $CFG, $DB;
307 /// special cases
308     if ($type === 'mod') {
309         return upgrade_plugins_modules($startcallback, $endcallback, $verbose);
310     } else if ($type === 'block') {
311         return upgrade_plugins_blocks($startcallback, $endcallback, $verbose);
312     }
314     $plugs = get_plugin_list($type);
316     foreach ($plugs as $plug=>$fullplug) {
317         // Reset time so that it works when installing a large number of plugins
318         set_time_limit(600);
319         $component = clean_param($type.'_'.$plug, PARAM_COMPONENT); // standardised plugin name
321         // check plugin dir is valid name
322         if (empty($component)) {
323             throw new plugin_defective_exception($type.'_'.$plug, 'Invalid plugin directory name.');
324         }
326         if (!is_readable($fullplug.'/version.php')) {
327             continue;
328         }
330         $plugin = new stdClass();
331         require($fullplug.'/version.php');  // defines $plugin with version etc
333         // if plugin tells us it's full name we may check the location
334         if (isset($plugin->component)) {
335             if ($plugin->component !== $component) {
336                 throw new plugin_defective_exception($component, 'Plugin installed in wrong folder.');
337             }
338         }
340         if (empty($plugin->version)) {
341             throw new plugin_defective_exception($component, 'Missing version value in version.php');
342         }
344         $plugin->name     = $plug;
345         $plugin->fullname = $component;
348         if (!empty($plugin->requires)) {
349             if ($plugin->requires > $CFG->version) {
350                 throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
351             } else if ($plugin->requires < 2010000000) {
352                 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
353             }
354         }
356         // try to recover from interrupted install.php if needed
357         if (file_exists($fullplug.'/db/install.php')) {
358             if (get_config($plugin->fullname, 'installrunning')) {
359                 require_once($fullplug.'/db/install.php');
360                 $recover_install_function = 'xmldb_'.$plugin->fullname.'_install_recovery';
361                 if (function_exists($recover_install_function)) {
362                     $startcallback($component, true, $verbose);
363                     $recover_install_function();
364                     unset_config('installrunning', $plugin->fullname);
365                     update_capabilities($component);
366                     log_update_descriptions($component);
367                     external_update_descriptions($component);
368                     events_update_definition($component);
369                     message_update_providers($component);
370                     if ($type === 'message') {
371                         message_update_processors($plug);
372                     }
373                     upgrade_plugin_mnet_functions($component);
374                     $endcallback($component, true, $verbose);
375                 }
376             }
377         }
379         $installedversion = get_config($plugin->fullname, 'version');
380         if (empty($installedversion)) { // new installation
381             $startcallback($component, true, $verbose);
383         /// Install tables if defined
384             if (file_exists($fullplug.'/db/install.xml')) {
385                 $DB->get_manager()->install_from_xmldb_file($fullplug.'/db/install.xml');
386             }
388         /// store version
389             upgrade_plugin_savepoint(true, $plugin->version, $type, $plug, false);
391         /// execute post install file
392             if (file_exists($fullplug.'/db/install.php')) {
393                 require_once($fullplug.'/db/install.php');
394                 set_config('installrunning', 1, $plugin->fullname);
395                 $post_install_function = 'xmldb_'.$plugin->fullname.'_install';
396                 $post_install_function();
397                 unset_config('installrunning', $plugin->fullname);
398             }
400         /// Install various components
401             update_capabilities($component);
402             log_update_descriptions($component);
403             external_update_descriptions($component);
404             events_update_definition($component);
405             message_update_providers($component);
406             if ($type === 'message') {
407                 message_update_processors($plug);
408             }
409             upgrade_plugin_mnet_functions($component);
411             purge_all_caches();
412             $endcallback($component, true, $verbose);
414         } else if ($installedversion < $plugin->version) { // upgrade
415         /// Run the upgrade function for the plugin.
416             $startcallback($component, false, $verbose);
418             if (is_readable($fullplug.'/db/upgrade.php')) {
419                 require_once($fullplug.'/db/upgrade.php');  // defines upgrading function
421                 $newupgrade_function = 'xmldb_'.$plugin->fullname.'_upgrade';
422                 $result = $newupgrade_function($installedversion);
423             } else {
424                 $result = true;
425             }
427             $installedversion = get_config($plugin->fullname, 'version');
428             if ($installedversion < $plugin->version) {
429                 // store version if not already there
430                 upgrade_plugin_savepoint($result, $plugin->version, $type, $plug, false);
431             }
433         /// Upgrade various components
434             update_capabilities($component);
435             log_update_descriptions($component);
436             external_update_descriptions($component);
437             events_update_definition($component);
438             message_update_providers($component);
439             if ($type === 'message') {
440                 message_update_processors($plug);
441             }
442             upgrade_plugin_mnet_functions($component);
444             purge_all_caches();
445             $endcallback($component, false, $verbose);
447         } else if ($installedversion > $plugin->version) {
448             throw new downgrade_exception($component, $installedversion, $plugin->version);
449         }
450     }
453 /**
454  * Find and check all modules and load them up or upgrade them if necessary
455  *
456  * @global object
457  * @global object
458  */
459 function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
460     global $CFG, $DB;
462     $mods = get_plugin_list('mod');
464     foreach ($mods as $mod=>$fullmod) {
466         if ($mod === 'NEWMODULE') {   // Someone has unzipped the template, ignore it
467             continue;
468         }
470         $component = clean_param('mod_'.$mod, PARAM_COMPONENT);
472         // check module dir is valid name
473         if (empty($component)) {
474             throw new plugin_defective_exception('mod_'.$mod, 'Invalid plugin directory name.');
475         }
477         if (!is_readable($fullmod.'/version.php')) {
478             throw new plugin_defective_exception($component, 'Missing version.php');
479         }
481         $module = new stdClass();
482         require($fullmod .'/version.php');  // defines $module with version etc
484         // if plugin tells us it's full name we may check the location
485         if (isset($module->component)) {
486             if ($module->component !== $component) {
487                 throw new plugin_defective_exception($component, 'Plugin installed in wrong folder.');
488             }
489         }
491         if (empty($module->version)) {
492             if (isset($module->version)) {
493                 // Version is empty but is set - it means its value is 0 or ''. Let us skip such module.
494                 // This is intended for developers so they can work on the early stages of the module.
495                 continue;
496             }
497             throw new plugin_defective_exception($component, 'Missing version value in version.php');
498         }
500         if (!empty($module->requires)) {
501             if ($module->requires > $CFG->version) {
502                 throw new upgrade_requires_exception($component, $module->version, $CFG->version, $module->requires);
503             } else if ($module->requires < 2010000000) {
504                 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
505             }
506         }
508         if (empty($module->cron)) {
509             $module->cron = 0;
510         }
512         // all modules must have en lang pack
513         if (!is_readable("$fullmod/lang/en/$mod.php")) {
514             throw new plugin_defective_exception($component, 'Missing mandatory en language pack.');
515         }
517         $module->name = $mod;   // The name MUST match the directory
519         $currmodule = $DB->get_record('modules', array('name'=>$module->name));
521         if (file_exists($fullmod.'/db/install.php')) {
522             if (get_config($module->name, 'installrunning')) {
523                 require_once($fullmod.'/db/install.php');
524                 $recover_install_function = 'xmldb_'.$module->name.'_install_recovery';
525                 if (function_exists($recover_install_function)) {
526                     $startcallback($component, true, $verbose);
527                     $recover_install_function();
528                     unset_config('installrunning', $module->name);
529                     // Install various components too
530                     update_capabilities($component);
531                     log_update_descriptions($component);
532                     external_update_descriptions($component);
533                     events_update_definition($component);
534                     message_update_providers($component);
535                     upgrade_plugin_mnet_functions($component);
536                     $endcallback($component, true, $verbose);
537                 }
538             }
539         }
541         if (empty($currmodule->version)) {
542             $startcallback($component, true, $verbose);
544         /// Execute install.xml (XMLDB) - must be present in all modules
545             $DB->get_manager()->install_from_xmldb_file($fullmod.'/db/install.xml');
547         /// Add record into modules table - may be needed in install.php already
548             $module->id = $DB->insert_record('modules', $module);
550         /// Post installation hook - optional
551             if (file_exists("$fullmod/db/install.php")) {
552                 require_once("$fullmod/db/install.php");
553                 // Set installation running flag, we need to recover after exception or error
554                 set_config('installrunning', 1, $module->name);
555                 $post_install_function = 'xmldb_'.$module->name.'_install';
556                 $post_install_function();
557                 unset_config('installrunning', $module->name);
558             }
560         /// Install various components
561             update_capabilities($component);
562             log_update_descriptions($component);
563             external_update_descriptions($component);
564             events_update_definition($component);
565             message_update_providers($component);
566             upgrade_plugin_mnet_functions($component);
568             purge_all_caches();
569             $endcallback($component, true, $verbose);
571         } else if ($currmodule->version < $module->version) {
572         /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
573             $startcallback($component, false, $verbose);
575             if (is_readable($fullmod.'/db/upgrade.php')) {
576                 require_once($fullmod.'/db/upgrade.php');  // defines new upgrading function
577                 $newupgrade_function = 'xmldb_'.$module->name.'_upgrade';
578                 $result = $newupgrade_function($currmodule->version, $module);
579             } else {
580                 $result = true;
581             }
583             $currmodule = $DB->get_record('modules', array('name'=>$module->name));
584             if ($currmodule->version < $module->version) {
585                 // store version if not already there
586                 upgrade_mod_savepoint($result, $module->version, $mod, false);
587             }
589             // update cron flag if needed
590             if ($currmodule->cron != $module->cron) {
591                 $DB->set_field('modules', 'cron', $module->cron, array('name' => $module->name));
592             }
594             // Upgrade various components
595             update_capabilities($component);
596             log_update_descriptions($component);
597             external_update_descriptions($component);
598             events_update_definition($component);
599             message_update_providers($component);
600             upgrade_plugin_mnet_functions($component);
602             purge_all_caches();
604             $endcallback($component, false, $verbose);
606         } else if ($currmodule->version > $module->version) {
607             throw new downgrade_exception($component, $currmodule->version, $module->version);
608         }
609     }
613 /**
614  * This function finds all available blocks and install them
615  * into blocks table or do all the upgrade process if newer.
616  *
617  * @global object
618  * @global object
619  */
620 function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
621     global $CFG, $DB;
623     require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
625     $blocktitles   = array(); // we do not want duplicate titles
627     //Is this a first install
628     $first_install = null;
630     $blocks = get_plugin_list('block');
632     foreach ($blocks as $blockname=>$fullblock) {
634         if (is_null($first_install)) {
635             $first_install = ($DB->count_records('block_instances') == 0);
636         }
638         if ($blockname === 'NEWBLOCK') {   // Someone has unzipped the template, ignore it
639             continue;
640         }
642         $component = clean_param('block_'.$blockname, PARAM_COMPONENT);
644         // check block dir is valid name
645         if (empty($component)) {
646             throw new plugin_defective_exception('block_'.$blockname, 'Invalid plugin directory name.');
647         }
649         if (!is_readable($fullblock.'/version.php')) {
650             throw new plugin_defective_exception('block/'.$blockname, 'Missing version.php file.');
651         }
652         $plugin = new stdClass();
653         $plugin->version = NULL;
654         $plugin->cron    = 0;
655         include($fullblock.'/version.php');
656         $block = $plugin;
658         // if plugin tells us it's full name we may check the location
659         if (isset($block->component)) {
660             if ($block->component !== $component) {
661                 throw new plugin_defective_exception($component, 'Plugin installed in wrong folder.');
662             }
663         }
665         if (!empty($plugin->requires)) {
666             if ($plugin->requires > $CFG->version) {
667                 throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
668             } else if ($plugin->requires < 2010000000) {
669                 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
670             }
671         }
673         if (!is_readable($fullblock.'/block_'.$blockname.'.php')) {
674             throw new plugin_defective_exception('block/'.$blockname, 'Missing main block class file.');
675         }
676         include_once($fullblock.'/block_'.$blockname.'.php');
678         $classname = 'block_'.$blockname;
680         if (!class_exists($classname)) {
681             throw new plugin_defective_exception($component, 'Can not load main class.');
682         }
684         $blockobj    = new $classname;   // This is what we'll be testing
685         $blocktitle  = $blockobj->get_title();
687         // OK, it's as we all hoped. For further tests, the object will do them itself.
688         if (!$blockobj->_self_test()) {
689             throw new plugin_defective_exception($component, 'Self test failed.');
690         }
692         $block->name     = $blockname;   // The name MUST match the directory
694         if (empty($block->version)) {
695             throw new plugin_defective_exception($component, 'Missing block version.');
696         }
698         $currblock = $DB->get_record('block', array('name'=>$block->name));
700         if (file_exists($fullblock.'/db/install.php')) {
701             if (get_config('block_'.$blockname, 'installrunning')) {
702                 require_once($fullblock.'/db/install.php');
703                 $recover_install_function = 'xmldb_block_'.$blockname.'_install_recovery';
704                 if (function_exists($recover_install_function)) {
705                     $startcallback($component, true, $verbose);
706                     $recover_install_function();
707                     unset_config('installrunning', 'block_'.$blockname);
708                     // Install various components
709                     update_capabilities($component);
710                     log_update_descriptions($component);
711                     external_update_descriptions($component);
712                     events_update_definition($component);
713                     message_update_providers($component);
714                     upgrade_plugin_mnet_functions($component);
715                     $endcallback($component, true, $verbose);
716                 }
717             }
718         }
720         if (empty($currblock->version)) { // block not installed yet, so install it
721             $conflictblock = array_search($blocktitle, $blocktitles);
722             if ($conflictblock !== false) {
723                 // Duplicate block titles are not allowed, they confuse people
724                 // AND PHP's associative arrays ;)
725                 throw new plugin_defective_exception($component, get_string('blocknameconflict', 'error', (object)array('name'=>$block->name, 'conflict'=>$conflictblock)));
726             }
727             $startcallback($component, true, $verbose);
729             if (file_exists($fullblock.'/db/install.xml')) {
730                 $DB->get_manager()->install_from_xmldb_file($fullblock.'/db/install.xml');
731             }
732             $block->id = $DB->insert_record('block', $block);
734             if (file_exists($fullblock.'/db/install.php')) {
735                 require_once($fullblock.'/db/install.php');
736                 // Set installation running flag, we need to recover after exception or error
737                 set_config('installrunning', 1, 'block_'.$blockname);
738                 $post_install_function = 'xmldb_block_'.$blockname.'_install';;
739                 $post_install_function();
740                 unset_config('installrunning', 'block_'.$blockname);
741             }
743             $blocktitles[$block->name] = $blocktitle;
745             // Install various components
746             update_capabilities($component);
747             log_update_descriptions($component);
748             external_update_descriptions($component);
749             events_update_definition($component);
750             message_update_providers($component);
751             upgrade_plugin_mnet_functions($component);
753             purge_all_caches();
754             $endcallback($component, true, $verbose);
756         } else if ($currblock->version < $block->version) {
757             $startcallback($component, false, $verbose);
759             if (is_readable($fullblock.'/db/upgrade.php')) {
760                 require_once($fullblock.'/db/upgrade.php');  // defines new upgrading function
761                 $newupgrade_function = 'xmldb_block_'.$blockname.'_upgrade';
762                 $result = $newupgrade_function($currblock->version, $block);
763             } else {
764                 $result = true;
765             }
767             $currblock = $DB->get_record('block', array('name'=>$block->name));
768             if ($currblock->version < $block->version) {
769                 // store version if not already there
770                 upgrade_block_savepoint($result, $block->version, $block->name, false);
771             }
773             if ($currblock->cron != $block->cron) {
774                 // update cron flag if needed
775                 $currblock->cron = $block->cron;
776                 $DB->update_record('block', $currblock);
777             }
779             // Upgrade various components
780             update_capabilities($component);
781             log_update_descriptions($component);
782             external_update_descriptions($component);
783             events_update_definition($component);
784             message_update_providers($component);
785             upgrade_plugin_mnet_functions($component);
787             purge_all_caches();
788             $endcallback($component, false, $verbose);
790         } else if ($currblock->version > $block->version) {
791             throw new downgrade_exception($component, $currblock->version, $block->version);
792         }
793     }
796     // Finally, if we are in the first_install of BLOCKS setup frontpage and admin page blocks
797     if ($first_install) {
798         //Iterate over each course - there should be only site course here now
799         if ($courses = $DB->get_records('course')) {
800             foreach ($courses as $course) {
801                 blocks_add_default_course_blocks($course);
802             }
803         }
805         blocks_add_default_system_blocks();
806     }
810 /**
811  * Log_display description function used during install and upgrade.
812  *
813  * @param string $component name of component (moodle, mod_assignment, etc.)
814  * @return void
815  */
816 function log_update_descriptions($component) {
817     global $DB;
819     $defpath = get_component_directory($component).'/db/log.php';
821     if (!file_exists($defpath)) {
822         $DB->delete_records('log_display', array('component'=>$component));
823         return;
824     }
826     // load new info
827     $logs = array();
828     include($defpath);
829     $newlogs = array();
830     foreach ($logs as $log) {
831         $newlogs[$log['module'].'-'.$log['action']] = $log; // kind of unique name
832     }
833     unset($logs);
834     $logs = $newlogs;
836     $fields = array('module', 'action', 'mtable', 'field');
837     // update all log fist
838     $dblogs = $DB->get_records('log_display', array('component'=>$component));
839     foreach ($dblogs as $dblog) {
840         $name = $dblog->module.'-'.$dblog->action;
842         if (empty($logs[$name])) {
843             $DB->delete_records('log_display', array('id'=>$dblog->id));
844             continue;
845         }
847         $log = $logs[$name];
848         unset($logs[$name]);
850         $update = false;
851         foreach ($fields as $field) {
852             if ($dblog->$field != $log[$field]) {
853                 $dblog->$field = $log[$field];
854                 $update = true;
855             }
856         }
857         if ($update) {
858             $DB->update_record('log_display', $dblog);
859         }
860     }
861     foreach ($logs as $log) {
862         $dblog = (object)$log;
863         $dblog->component = $component;
864         $DB->insert_record('log_display', $dblog);
865     }
868 /**
869  * Web service discovery function used during install and upgrade.
870  * @param string $component name of component (moodle, mod_assignment, etc.)
871  * @return void
872  */
873 function external_update_descriptions($component) {
874     global $DB;
876     $defpath = get_component_directory($component).'/db/services.php';
878     if (!file_exists($defpath)) {
879         external_delete_descriptions($component);
880         return;
881     }
883     // load new info
884     $functions = array();
885     $services = array();
886     include($defpath);
888     // update all function fist
889     $dbfunctions = $DB->get_records('external_functions', array('component'=>$component));
890     foreach ($dbfunctions as $dbfunction) {
891         if (empty($functions[$dbfunction->name])) {
892             $DB->delete_records('external_functions', array('id'=>$dbfunction->id));
893             // do not delete functions from external_services_functions, beacuse
894             // we want to notify admins when functions used in custom services disappear
896             //TODO: this looks wrong, we have to delete it eventually (skodak)
897             continue;
898         }
900         $function = $functions[$dbfunction->name];
901         unset($functions[$dbfunction->name]);
902         $function['classpath'] = empty($function['classpath']) ? null : $function['classpath'];
904         $update = false;
905         if ($dbfunction->classname != $function['classname']) {
906             $dbfunction->classname = $function['classname'];
907             $update = true;
908         }
909         if ($dbfunction->methodname != $function['methodname']) {
910             $dbfunction->methodname = $function['methodname'];
911             $update = true;
912         }
913         if ($dbfunction->classpath != $function['classpath']) {
914             $dbfunction->classpath = $function['classpath'];
915             $update = true;
916         }
917         $functioncapabilities = key_exists('capabilities', $function)?$function['capabilities']:'';
918         if ($dbfunction->capabilities != $functioncapabilities) {
919             $dbfunction->capabilities = $functioncapabilities;
920             $update = true;
921         }
922         if ($update) {
923             $DB->update_record('external_functions', $dbfunction);
924         }
925     }
926     foreach ($functions as $fname => $function) {
927         $dbfunction = new stdClass();
928         $dbfunction->name       = $fname;
929         $dbfunction->classname  = $function['classname'];
930         $dbfunction->methodname = $function['methodname'];
931         $dbfunction->classpath  = empty($function['classpath']) ? null : $function['classpath'];
932         $dbfunction->component  = $component;
933         $dbfunction->capabilities = key_exists('capabilities', $function)?$function['capabilities']:'';
934         $dbfunction->id = $DB->insert_record('external_functions', $dbfunction);
935     }
936     unset($functions);
938     // now deal with services
939     $dbservices = $DB->get_records('external_services', array('component'=>$component));
940     foreach ($dbservices as $dbservice) {
941         if (empty($services[$dbservice->name])) {
942             $DB->delete_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
943             $DB->delete_records('external_services_users', array('externalserviceid'=>$dbservice->id));
944             $DB->delete_records('external_services', array('id'=>$dbservice->id));
945             continue;
946         }
947         $service = $services[$dbservice->name];
948         unset($services[$dbservice->name]);
949         $service['enabled'] = empty($service['enabled']) ? 0 : $service['enabled'];
950         $service['requiredcapability'] = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
951         $service['restrictedusers'] = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
952         $service['downloadfiles'] = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
953         $service['shortname'] = !isset($service['shortname']) ? null : $service['shortname'];
955         $update = false;
956         if ($dbservice->requiredcapability != $service['requiredcapability']) {
957             $dbservice->requiredcapability = $service['requiredcapability'];
958             $update = true;
959         }
960         if ($dbservice->restrictedusers != $service['restrictedusers']) {
961             $dbservice->restrictedusers = $service['restrictedusers'];
962             $update = true;
963         }
964         if ($dbservice->downloadfiles != $service['downloadfiles']) {
965             $dbservice->downloadfiles = $service['downloadfiles'];
966             $update = true;
967         }
968         //if shortname is not a PARAM_ALPHANUMEXT, fail (tested here for service update and creation)
969         if (isset($service['shortname']) and
970                 (clean_param($service['shortname'], PARAM_ALPHANUMEXT) != $service['shortname'])) {
971             throw new moodle_exception('installserviceshortnameerror', 'webservice', '', $service['shortname']);
972         }
973         if ($dbservice->shortname != $service['shortname']) {
974             //check that shortname is unique
975             if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
976                 $existingservice = $DB->get_record('external_services',
977                         array('shortname' => $service['shortname']));
978                 if (!empty($existingservice)) {
979                     throw new moodle_exception('installexistingserviceshortnameerror', 'webservice', '', $service['shortname']);
980                 }
981             }
982             $dbservice->shortname = $service['shortname'];
983             $update = true;
984         }
985         if ($update) {
986             $DB->update_record('external_services', $dbservice);
987         }
989         $functions = $DB->get_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
990         foreach ($functions as $function) {
991             $key = array_search($function->functionname, $service['functions']);
992             if ($key === false) {
993                 $DB->delete_records('external_services_functions', array('id'=>$function->id));
994             } else {
995                 unset($service['functions'][$key]);
996             }
997         }
998         foreach ($service['functions'] as $fname) {
999             $newf = new stdClass();
1000             $newf->externalserviceid = $dbservice->id;
1001             $newf->functionname      = $fname;
1002             $DB->insert_record('external_services_functions', $newf);
1003         }
1004         unset($functions);
1005     }
1006     foreach ($services as $name => $service) {
1007         //check that shortname is unique
1008         if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
1009             $existingservice = $DB->get_record('external_services',
1010                     array('shortname' => $service['shortname']));
1011             if (!empty($existingservice)) {
1012                 throw new moodle_exception('installserviceshortnameerror', 'webservice');
1013             }
1014         }
1016         $dbservice = new stdClass();
1017         $dbservice->name               = $name;
1018         $dbservice->enabled            = empty($service['enabled']) ? 0 : $service['enabled'];
1019         $dbservice->requiredcapability = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
1020         $dbservice->restrictedusers    = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
1021         $dbservice->downloadfiles      = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
1022         $dbservice->shortname          = !isset($service['shortname']) ? null : $service['shortname'];
1023         $dbservice->component          = $component;
1024         $dbservice->timecreated        = time();
1025         $dbservice->id = $DB->insert_record('external_services', $dbservice);
1026         foreach ($service['functions'] as $fname) {
1027             $newf = new stdClass();
1028             $newf->externalserviceid = $dbservice->id;
1029             $newf->functionname      = $fname;
1030             $DB->insert_record('external_services_functions', $newf);
1031         }
1032     }
1035 /**
1036  * Delete all service and external functions information defined in the specified component.
1037  * @param string $component name of component (moodle, mod_assignment, etc.)
1038  * @return void
1039  */
1040 function external_delete_descriptions($component) {
1041     global $DB;
1043     $params = array($component);
1045     $DB->delete_records_select('external_services_users', "externalserviceid IN (SELECT id FROM {external_services} WHERE component = ?)", $params);
1046     $DB->delete_records_select('external_services_functions', "externalserviceid IN (SELECT id FROM {external_services} WHERE component = ?)", $params);
1047     $DB->delete_records('external_services', array('component'=>$component));
1048     $DB->delete_records('external_functions', array('component'=>$component));
1051 /**
1052  * upgrade logging functions
1053  */
1054 function upgrade_handle_exception($ex, $plugin = null) {
1055     global $CFG;
1057     // rollback everything, we need to log all upgrade problems
1058     abort_all_db_transactions();
1060     $info = get_exception_info($ex);
1062     // First log upgrade error
1063     upgrade_log(UPGRADE_LOG_ERROR, $plugin, 'Exception: ' . get_class($ex), $info->message, $info->backtrace);
1065     // Always turn on debugging - admins need to know what is going on
1066     $CFG->debug = DEBUG_DEVELOPER;
1068     default_exception_handler($ex, true, $plugin);
1071 /**
1072  * Adds log entry into upgrade_log table
1073  *
1074  * @param int $type UPGRADE_LOG_NORMAL, UPGRADE_LOG_NOTICE or UPGRADE_LOG_ERROR
1075  * @param string $plugin frankenstyle component name
1076  * @param string $info short description text of log entry
1077  * @param string $details long problem description
1078  * @param string $backtrace string used for errors only
1079  * @return void
1080  */
1081 function upgrade_log($type, $plugin, $info, $details=null, $backtrace=null) {
1082     global $DB, $USER, $CFG;
1084     if (empty($plugin)) {
1085         $plugin = 'core';
1086     }
1088     list($plugintype, $pluginname) = normalize_component($plugin);
1089     $component = is_null($pluginname) ? $plugintype : $plugintype . '_' . $pluginname;
1091     $backtrace = format_backtrace($backtrace, true);
1093     $currentversion = null;
1094     $targetversion  = null;
1096     //first try to find out current version number
1097     if ($plugintype === 'core') {
1098         //main
1099         $currentversion = $CFG->version;
1101         $version = null;
1102         include("$CFG->dirroot/version.php");
1103         $targetversion = $version;
1105     } else if ($plugintype === 'mod') {
1106         try {
1107             $currentversion = $DB->get_field('modules', 'version', array('name'=>$pluginname));
1108             $currentversion = ($currentversion === false) ? null : $currentversion;
1109         } catch (Exception $ignored) {
1110         }
1111         $cd = get_component_directory($component);
1112         if (file_exists("$cd/version.php")) {
1113             $module = new stdClass();
1114             $module->version = null;
1115             include("$cd/version.php");
1116             $targetversion = $module->version;
1117         }
1119     } else if ($plugintype === 'block') {
1120         try {
1121             if ($block = $DB->get_record('block', array('name'=>$pluginname))) {
1122                 $currentversion = $block->version;
1123             }
1124         } catch (Exception $ignored) {
1125         }
1126         $cd = get_component_directory($component);
1127         if (file_exists("$cd/version.php")) {
1128             $plugin = new stdClass();
1129             $plugin->version = null;
1130             include("$cd/version.php");
1131             $targetversion = $plugin->version;
1132         }
1134     } else {
1135         $pluginversion = get_config($component, 'version');
1136         if (!empty($pluginversion)) {
1137             $currentversion = $pluginversion;
1138         }
1139         $cd = get_component_directory($component);
1140         if (file_exists("$cd/version.php")) {
1141             $plugin = new stdClass();
1142             $plugin->version = null;
1143             include("$cd/version.php");
1144             $targetversion = $plugin->version;
1145         }
1146     }
1148     $log = new stdClass();
1149     $log->type          = $type;
1150     $log->plugin        = $component;
1151     $log->version       = $currentversion;
1152     $log->targetversion = $targetversion;
1153     $log->info          = $info;
1154     $log->details       = $details;
1155     $log->backtrace     = $backtrace;
1156     $log->userid        = $USER->id;
1157     $log->timemodified  = time();
1158     try {
1159         $DB->insert_record('upgrade_log', $log);
1160     } catch (Exception $ignored) {
1161         // possible during install or 2.0 upgrade
1162     }
1165 /**
1166  * Marks start of upgrade, blocks any other access to site.
1167  * The upgrade is finished at the end of script or after timeout.
1168  *
1169  * @global object
1170  * @global object
1171  * @global object
1172  */
1173 function upgrade_started($preinstall=false) {
1174     global $CFG, $DB, $PAGE, $OUTPUT;
1176     static $started = false;
1178     if ($preinstall) {
1179         ignore_user_abort(true);
1180         upgrade_setup_debug(true);
1182     } else if ($started) {
1183         upgrade_set_timeout(120);
1185     } else {
1186         if (!CLI_SCRIPT and !$PAGE->headerprinted) {
1187             $strupgrade  = get_string('upgradingversion', 'admin');
1188             $PAGE->set_pagelayout('maintenance');
1189             upgrade_init_javascript();
1190             $PAGE->set_title($strupgrade.' - Moodle '.$CFG->target_release);
1191             $PAGE->set_heading($strupgrade);
1192             $PAGE->navbar->add($strupgrade);
1193             $PAGE->set_cacheable(false);
1194             echo $OUTPUT->header();
1195         }
1197         ignore_user_abort(true);
1198         register_shutdown_function('upgrade_finished_handler');
1199         upgrade_setup_debug(true);
1200         set_config('upgraderunning', time()+300);
1201         $started = true;
1202     }
1205 /**
1206  * Internal function - executed if upgrade interrupted.
1207  */
1208 function upgrade_finished_handler() {
1209     upgrade_finished();
1212 /**
1213  * Indicates upgrade is finished.
1214  *
1215  * This function may be called repeatedly.
1216  *
1217  * @global object
1218  * @global object
1219  */
1220 function upgrade_finished($continueurl=null) {
1221     global $CFG, $DB, $OUTPUT;
1223     if (!empty($CFG->upgraderunning)) {
1224         unset_config('upgraderunning');
1225         upgrade_setup_debug(false);
1226         ignore_user_abort(false);
1227         if ($continueurl) {
1228             echo $OUTPUT->continue_button($continueurl);
1229             echo $OUTPUT->footer();
1230             die;
1231         }
1232     }
1235 /**
1236  * @global object
1237  * @global object
1238  */
1239 function upgrade_setup_debug($starting) {
1240     global $CFG, $DB;
1242     static $originaldebug = null;
1244     if ($starting) {
1245         if ($originaldebug === null) {
1246             $originaldebug = $DB->get_debug();
1247         }
1248         if (!empty($CFG->upgradeshowsql)) {
1249             $DB->set_debug(true);
1250         }
1251     } else {
1252         $DB->set_debug($originaldebug);
1253     }
1256 function print_upgrade_separator() {
1257     if (!CLI_SCRIPT) {
1258         echo '<hr />';
1259     }
1262 /**
1263  * Default start upgrade callback
1264  * @param string $plugin
1265  * @param bool $installation true if installation, false means upgrade
1266  */
1267 function print_upgrade_part_start($plugin, $installation, $verbose) {
1268     global $OUTPUT;
1269     if (empty($plugin) or $plugin == 'moodle') {
1270         upgrade_started($installation); // does not store upgrade running flag yet
1271         if ($verbose) {
1272             echo $OUTPUT->heading(get_string('coresystem'));
1273         }
1274     } else {
1275         upgrade_started();
1276         if ($verbose) {
1277             echo $OUTPUT->heading($plugin);
1278         }
1279     }
1280     if ($installation) {
1281         if (empty($plugin) or $plugin == 'moodle') {
1282             // no need to log - log table not yet there ;-)
1283         } else {
1284             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin installation');
1285         }
1286     } else {
1287         if (empty($plugin) or $plugin == 'moodle') {
1288             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting core upgrade');
1289         } else {
1290             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin upgrade');
1291         }
1292     }
1295 /**
1296  * Default end upgrade callback
1297  * @param string $plugin
1298  * @param bool $installation true if installation, false means upgrade
1299  */
1300 function print_upgrade_part_end($plugin, $installation, $verbose) {
1301     global $OUTPUT;
1302     upgrade_started();
1303     if ($installation) {
1304         if (empty($plugin) or $plugin == 'moodle') {
1305             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core installed');
1306         } else {
1307             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin installed');
1308         }
1309     } else {
1310         if (empty($plugin) or $plugin == 'moodle') {
1311             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core upgraded');
1312         } else {
1313             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin upgraded');
1314         }
1315     }
1316     if ($verbose) {
1317         echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
1318         print_upgrade_separator();
1319     }
1322 /**
1323  * Sets up JS code required for all upgrade scripts.
1324  * @global object
1325  */
1326 function upgrade_init_javascript() {
1327     global $PAGE;
1328     // scroll to the end of each upgrade page so that ppl see either error or continue button,
1329     // no need to scroll continuously any more, it is enough to jump to end once the footer is printed ;-)
1330     $js = "window.scrollTo(0, 5000000);";
1331     $PAGE->requires->js_init_code($js);
1334 /**
1335  * Try to upgrade the given language pack (or current language)
1336  *
1337  * @param string $lang the code of the language to update, defaults to the current language
1338  */
1339 function upgrade_language_pack($lang = null) {
1340     global $CFG;
1342     if (!empty($CFG->skiplangupgrade)) {
1343         return;
1344     }
1346     if (!file_exists("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php")) {
1347         // weird, somebody uninstalled the import utility
1348         return;
1349     }
1351     if (!$lang) {
1352         $lang = current_language();
1353     }
1355     if (!get_string_manager()->translation_exists($lang)) {
1356         return;
1357     }
1359     get_string_manager()->reset_caches();
1361     if ($lang === 'en') {
1362         return;  // Nothing to do
1363     }
1365     upgrade_started(false);
1367     require_once("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php");
1368     tool_langimport_preupgrade_update($lang);
1370     get_string_manager()->reset_caches();
1372     print_upgrade_separator();
1375 /**
1376  * Install core moodle tables and initialize
1377  * @param float $version target version
1378  * @param bool $verbose
1379  * @return void, may throw exception
1380  */
1381 function install_core($version, $verbose) {
1382     global $CFG, $DB;
1384     try {
1385         set_time_limit(600);
1386         print_upgrade_part_start('moodle', true, $verbose); // does not store upgrade running flag
1388         $DB->get_manager()->install_from_xmldb_file("$CFG->libdir/db/install.xml");
1389         upgrade_started();     // we want the flag to be stored in config table ;-)
1391         // set all core default records and default settings
1392         require_once("$CFG->libdir/db/install.php");
1393         xmldb_main_install(); // installs the capabilities too
1395         // store version
1396         upgrade_main_savepoint(true, $version, false);
1398         // Continue with the installation
1399         log_update_descriptions('moodle');
1400         external_update_descriptions('moodle');
1401         events_update_definition('moodle');
1402         message_update_providers('moodle');
1404         // Write default settings unconditionally
1405         admin_apply_default_settings(NULL, true);
1407         print_upgrade_part_end(null, true, $verbose);
1408     } catch (exception $ex) {
1409         upgrade_handle_exception($ex);
1410     }
1413 /**
1414  * Upgrade moodle core
1415  * @param float $version target version
1416  * @param bool $verbose
1417  * @return void, may throw exception
1418  */
1419 function upgrade_core($version, $verbose) {
1420     global $CFG;
1422     raise_memory_limit(MEMORY_EXTRA);
1424     require_once($CFG->libdir.'/db/upgrade.php');    // Defines upgrades
1426     try {
1427         // Reset caches before any output
1428         purge_all_caches();
1430         // Upgrade current language pack if we can
1431         upgrade_language_pack();
1433         print_upgrade_part_start('moodle', false, $verbose);
1435         // one time special local migration pre 2.0 upgrade script
1436         if ($CFG->version < 2007101600) {
1437             $pre20upgradefile = "$CFG->dirroot/local/upgrade_pre20.php";
1438             if (file_exists($pre20upgradefile)) {
1439                 set_time_limit(0);
1440                 require($pre20upgradefile);
1441                 // reset upgrade timeout to default
1442                 upgrade_set_timeout();
1443             }
1444         }
1446         $result = xmldb_main_upgrade($CFG->version);
1447         if ($version > $CFG->version) {
1448             // store version if not already there
1449             upgrade_main_savepoint($result, $version, false);
1450         }
1452         // perform all other component upgrade routines
1453         update_capabilities('moodle');
1454         log_update_descriptions('moodle');
1455         external_update_descriptions('moodle');
1456         events_update_definition('moodle');
1457         message_update_providers('moodle');
1459         // Reset caches again, just to be sure
1460         purge_all_caches();
1462         // Clean up contexts - more and more stuff depends on existence of paths and contexts
1463         context_helper::cleanup_instances();
1464         context_helper::create_instances(null, false);
1465         context_helper::build_all_paths(false);
1466         $syscontext = context_system::instance();
1467         $syscontext->mark_dirty();
1469         print_upgrade_part_end('moodle', false, $verbose);
1470     } catch (Exception $ex) {
1471         upgrade_handle_exception($ex);
1472     }
1475 /**
1476  * Upgrade/install other parts of moodle
1477  * @param bool $verbose
1478  * @return void, may throw exception
1479  */
1480 function upgrade_noncore($verbose) {
1481     global $CFG;
1483     raise_memory_limit(MEMORY_EXTRA);
1485     // upgrade all plugins types
1486     try {
1487         $plugintypes = get_plugin_types();
1488         foreach ($plugintypes as $type=>$location) {
1489             upgrade_plugins($type, 'print_upgrade_part_start', 'print_upgrade_part_end', $verbose);
1490         }
1491     } catch (Exception $ex) {
1492         upgrade_handle_exception($ex);
1493     }
1496 /**
1497  * Checks if the main tables have been installed yet or not.
1498  * @return bool
1499  */
1500 function core_tables_exist() {
1501     global $DB;
1503     if (!$tables = $DB->get_tables() ) {    // No tables yet at all.
1504         return false;
1506     } else {                                 // Check for missing main tables
1507         $mtables = array('config', 'course', 'groupings'); // some tables used in 1.9 and 2.0, preferable something from the start and end of install.xml
1508         foreach ($mtables as $mtable) {
1509             if (!in_array($mtable, $tables)) {
1510                 return false;
1511             }
1512         }
1513         return true;
1514     }
1517 /**
1518  * upgrades the mnet rpc definitions for the given component.
1519  * this method doesn't return status, an exception will be thrown in the case of an error
1520  *
1521  * @param string $component the plugin to upgrade, eg auth_mnet
1522  */
1523 function upgrade_plugin_mnet_functions($component) {
1524     global $DB, $CFG;
1526     list($type, $plugin) = explode('_', $component);
1527     $path = get_plugin_directory($type, $plugin);
1529     $publishes = array();
1530     $subscribes = array();
1531     if (file_exists($path . '/db/mnet.php')) {
1532         require_once($path . '/db/mnet.php'); // $publishes comes from this file
1533     }
1534     if (empty($publishes)) {
1535         $publishes = array(); // still need this to be able to disable stuff later
1536     }
1537     if (empty($subscribes)) {
1538         $subscribes = array(); // still need this to be able to disable stuff later
1539     }
1541     static $servicecache = array();
1543     // rekey an array based on the rpc method for easy lookups later
1544     $publishmethodservices = array();
1545     $subscribemethodservices = array();
1546     foreach($publishes as $servicename => $service) {
1547         if (is_array($service['methods'])) {
1548             foreach($service['methods'] as $methodname) {
1549                 $service['servicename'] = $servicename;
1550                 $publishmethodservices[$methodname][] = $service;
1551             }
1552         }
1553     }
1555     // Disable functions that don't exist (any more) in the source
1556     // Should these be deleted? What about their permissions records?
1557     foreach ($DB->get_records('mnet_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
1558         if (!array_key_exists($rpc->functionname, $publishmethodservices) && $rpc->enabled) {
1559             $DB->set_field('mnet_rpc', 'enabled', 0, array('id' => $rpc->id));
1560         } else if (array_key_exists($rpc->functionname, $publishmethodservices) && !$rpc->enabled) {
1561             $DB->set_field('mnet_rpc', 'enabled', 1, array('id' => $rpc->id));
1562         }
1563     }
1565     // reflect all the services we're publishing and save them
1566     require_once($CFG->dirroot . '/lib/zend/Zend/Server/Reflection.php');
1567     static $cachedclasses = array(); // to store reflection information in
1568     foreach ($publishes as $service => $data) {
1569         $f = $data['filename'];
1570         $c = $data['classname'];
1571         foreach ($data['methods'] as $method) {
1572             $dataobject = new stdClass();
1573             $dataobject->plugintype  = $type;
1574             $dataobject->pluginname  = $plugin;
1575             $dataobject->enabled     = 1;
1576             $dataobject->classname   = $c;
1577             $dataobject->filename    = $f;
1579             if (is_string($method)) {
1580                 $dataobject->functionname = $method;
1582             } else if (is_array($method)) { // wants to override file or class
1583                 $dataobject->functionname = $method['method'];
1584                 $dataobject->classname     = $method['classname'];
1585                 $dataobject->filename      = $method['filename'];
1586             }
1587             $dataobject->xmlrpcpath = $type.'/'.$plugin.'/'.$dataobject->filename.'/'.$method;
1588             $dataobject->static = false;
1590             require_once($path . '/' . $dataobject->filename);
1591             $functionreflect = null; // slightly different ways to get this depending on whether it's a class method or a function
1592             if (!empty($dataobject->classname)) {
1593                 if (!class_exists($dataobject->classname)) {
1594                     throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
1595                 }
1596                 $key = $dataobject->filename . '|' . $dataobject->classname;
1597                 if (!array_key_exists($key, $cachedclasses)) { // look to see if we've already got a reflection object
1598                     try {
1599                         $cachedclasses[$key] = Zend_Server_Reflection::reflectClass($dataobject->classname);
1600                     } catch (Zend_Server_Reflection_Exception $e) { // catch these and rethrow them to something more helpful
1601                         throw new moodle_exception('installreflectionclasserror', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname, 'error' => $e->getMessage()));
1602                     }
1603                 }
1604                 $r =& $cachedclasses[$key];
1605                 if (!$r->hasMethod($dataobject->functionname)) {
1606                     throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
1607                 }
1608                 // stupid workaround for zend not having a getMethod($name) function
1609                 $ms = $r->getMethods();
1610                 foreach ($ms as $m) {
1611                     if ($m->getName() == $dataobject->functionname) {
1612                         $functionreflect = $m;
1613                         break;
1614                     }
1615                 }
1616                 $dataobject->static = (int)$functionreflect->isStatic();
1617             } else {
1618                 if (!function_exists($dataobject->functionname)) {
1619                     throw new moodle_exception('installnosuchfunction', 'mnet', '', (object)array('method' => $dataobject->functionname, 'file' => $dataobject->filename));
1620                 }
1621                 try {
1622                     $functionreflect = Zend_Server_Reflection::reflectFunction($dataobject->functionname);
1623                 } catch (Zend_Server_Reflection_Exception $e) { // catch these and rethrow them to something more helpful
1624                     throw new moodle_exception('installreflectionfunctionerror', 'mnet', '', (object)array('method' => $dataobject->functionname, '' => $dataobject->filename, 'error' => $e->getMessage()));
1625                 }
1626             }
1627             $dataobject->profile =  serialize(admin_mnet_method_profile($functionreflect));
1628             $dataobject->help = $functionreflect->getDescription();
1630             if ($record_exists = $DB->get_record('mnet_rpc', array('xmlrpcpath'=>$dataobject->xmlrpcpath))) {
1631                 $dataobject->id      = $record_exists->id;
1632                 $dataobject->enabled = $record_exists->enabled;
1633                 $DB->update_record('mnet_rpc', $dataobject);
1634             } else {
1635                 $dataobject->id = $DB->insert_record('mnet_rpc', $dataobject, true);
1636             }
1638             // TODO this API versioning must be reworked, here the recently processed method
1639             // sets the service API which may not be correct
1640             foreach ($publishmethodservices[$dataobject->functionname] as $service) {
1641                 if ($serviceobj = $DB->get_record('mnet_service', array('name'=>$service['servicename']))) {
1642                     $serviceobj->apiversion = $service['apiversion'];
1643                     $DB->update_record('mnet_service', $serviceobj);
1644                 } else {
1645                     $serviceobj = new stdClass();
1646                     $serviceobj->name        = $service['servicename'];
1647                     $serviceobj->description = empty($service['description']) ? '' : $service['description'];
1648                     $serviceobj->apiversion  = $service['apiversion'];
1649                     $serviceobj->offer       = 1;
1650                     $serviceobj->id          = $DB->insert_record('mnet_service', $serviceobj);
1651                 }
1652                 $servicecache[$service['servicename']] = $serviceobj;
1653                 if (!$DB->record_exists('mnet_service2rpc', array('rpcid'=>$dataobject->id, 'serviceid'=>$serviceobj->id))) {
1654                     $obj = new stdClass();
1655                     $obj->rpcid = $dataobject->id;
1656                     $obj->serviceid = $serviceobj->id;
1657                     $DB->insert_record('mnet_service2rpc', $obj, true);
1658                 }
1659             }
1660         }
1661     }
1662     // finished with methods we publish, now do subscribable methods
1663     foreach($subscribes as $service => $methods) {
1664         if (!array_key_exists($service, $servicecache)) {
1665             if (!$serviceobj = $DB->get_record('mnet_service', array('name' =>  $service))) {
1666                 debugging("TODO: skipping unknown service $service - somebody needs to fix MDL-21993");
1667                 continue;
1668             }
1669             $servicecache[$service] = $serviceobj;
1670         } else {
1671             $serviceobj = $servicecache[$service];
1672         }
1673         foreach ($methods as $method => $xmlrpcpath) {
1674             if (!$rpcid = $DB->get_field('mnet_remote_rpc', 'id', array('xmlrpcpath'=>$xmlrpcpath))) {
1675                 $remoterpc = (object)array(
1676                     'functionname' => $method,
1677                     'xmlrpcpath' => $xmlrpcpath,
1678                     'plugintype' => $type,
1679                     'pluginname' => $plugin,
1680                     'enabled'    => 1,
1681                 );
1682                 $rpcid = $remoterpc->id = $DB->insert_record('mnet_remote_rpc', $remoterpc, true);
1683             }
1684             if (!$DB->record_exists('mnet_remote_service2rpc', array('rpcid'=>$rpcid, 'serviceid'=>$serviceobj->id))) {
1685                 $obj = new stdClass();
1686                 $obj->rpcid = $rpcid;
1687                 $obj->serviceid = $serviceobj->id;
1688                 $DB->insert_record('mnet_remote_service2rpc', $obj, true);
1689             }
1690             $subscribemethodservices[$method][] = $service;
1691         }
1692     }
1694     foreach ($DB->get_records('mnet_remote_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
1695         if (!array_key_exists($rpc->functionname, $subscribemethodservices) && $rpc->enabled) {
1696             $DB->set_field('mnet_remote_rpc', 'enabled', 0, array('id' => $rpc->id));
1697         } else if (array_key_exists($rpc->functionname, $subscribemethodservices) && !$rpc->enabled) {
1698             $DB->set_field('mnet_remote_rpc', 'enabled', 1, array('id' => $rpc->id));
1699         }
1700     }
1702     return true;
1705 /**
1706  * Given some sort of Zend Reflection function/method object, return a profile array, ready to be serialized and stored
1707  *
1708  * @param Zend_Server_Reflection_Function_Abstract $function can be any subclass of this object type
1709  *
1710  * @return array
1711  */
1712 function admin_mnet_method_profile(Zend_Server_Reflection_Function_Abstract $function) {
1713     $protos = $function->getPrototypes();
1714     $proto = array_pop($protos);
1715     $ret = $proto->getReturnValue();
1716     $profile = array(
1717         'parameters' =>  array(),
1718         'return'     =>  array(
1719             'type'        => $ret->getType(),
1720             'description' => $ret->getDescription(),
1721         ),
1722     );
1723     foreach ($proto->getParameters() as $p) {
1724         $profile['parameters'][] = array(
1725             'name' => $p->getName(),
1726             'type' => $p->getType(),
1727             'description' => $p->getDescription(),
1728         );
1729     }
1730     return $profile;