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