upgrade.php 59.1 KB
Newer Older
1
2
3
4
<?php
/**
 *
 * @package    mahara
5
 * @subpackage core
6
 * @author     Catalyst IT Ltd
7
8
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
 * @copyright  For copyright information on Mahara, please see the README file distributed with this software.
9
10
11
12
13
14
15
16
 *
 */

defined('INTERNAL') || die();

/**
 * Class to use for installation exceptions
 */
17
class InstallationException extends SystemException {}
18

19
require_once('ddl.php');
20
21

/**
22
23
 * This function checks core and plugins for which need to be upgraded/installed
 *
24
25
26
27
 * Note: This function is sometimes executed during upgrades from
 * ancient databases.  Avoid rash assumptions about what's installed
 * or these upgrades may fail.
 *
28
29
 * @param string $name The name of the plugin to check. If no name is specified,
 *                     all plugins are checked.
30
31
32
 * @return mixed If a name is specified, an object will be returned with upgrade data
 *                     about the requested component (which can be "core", "local", or a plugin).
 *                     If the component desn't need to be updated, an empty array will be returned.
33
34
35
 *               If no name is specified, an array of such objects will be returned.
 *                     It will also include an array key "settings", which will be an array
 *                     that may contain metadata about the upgrade/install process.
36
 */
37
function check_upgrades($name=null) {
38

39
40
41
    $pluginstocheck = plugin_types();

    $toupgrade = array();
42
    $settings = array();
43
44
    $toupgradecount = 0;
    $newinstallcount = 0;
45
    $installing = false;
46
    $disablelogin = false;
47
    $newinstalls = array();
48
49

    require('version.php');
50
51
52
    if (isset($config->disablelogin) && !empty($config->disablelogin)) {
        $disablelogin = true;
    }
53
54
55
56
    // check core first...
    if (empty($name) || $name == 'core') {
        try {
            $coreversion = get_config('version');
Elliot Pahl's avatar
Elliot Pahl committed
57
        }
58
59
60
        catch (Exception $e) {
            $coreversion = 0;
        }
61
62
63
64
        $core = new stdClass();
        $core->to = $config->version;
        $core->torelease = $config->release;
        $core->toseries = $config->series;
65
        $toupgrade['core'] = $core;
66
        if (empty($coreversion)) {
67
            if (is_mysql()) { // Show a more informative error message if using mysql with skip-innodb
68
                // In MySQL 5.6.x, we run the command 'SHOW ENGINES' to check if InnoDB is enabled or not
69
                global $db;
70
71
72
73
74
75
76
77
78
79
80
                $result = $db->Execute("SHOW ENGINES");
                $hasinnodb = false;
                while (!$result->EOF) {
                    if ($result->fields['Engine'] == 'InnoDB' && ($result->fields['Support'] == 'YES' || $result->fields['Support'] == 'DEFAULT')) {
                        $hasinnodb = true;
                        break;
                    }
                    $result->MoveNext();
                }

                if (!$hasinnodb) {
81
82
83
                    throw new ConfigSanityException("Mahara requires InnoDB tables.  Please ensure InnoDB tables are enabled in your MySQL server.");
                }
            }
84
85
            $core->install = true;
            $installing = true;
Elliot Pahl's avatar
Elliot Pahl committed
86
        }
87
        else if ($config->version > $coreversion) {
88
            $corerelease = get_config('release');
Elliot Pahl's avatar
Elliot Pahl committed
89
            if (isset($config->minupgradefrom) && isset($config->minupgraderelease)
90
                && $coreversion < $config->minupgradefrom) {
91
                throw new ConfigSanityException("Must upgrade to $config->minupgradefrom "
92
93
                                          . "($config->minupgraderelease) first "
                                          . " (you have $coreversion ($corerelease)");
94
            }
95
            $toupgradecount ++;
96
97
            $core->upgrade = true;
            $core->from = $coreversion;
98
            $core->fromrelease = $corerelease;
99
        }
100
101
102
103
        else {
            // Core doesn't need to be upgraded. Remove it from the list!
            unset($toupgrade['core']);
        }
104
105
    }

106
    // If we were just checking if the core needed to be upgraded, we can stop here
107
    if ($name == 'core') {
108
        $toupgrade['core']->disablelogin = $disablelogin;
109
110
111
        return $toupgrade['core'];
    }

112
113
114
115
116
117
118
119
120
121
122
123
    if (!$installing && (empty($name) || $name == 'local')) {
        $localversion = get_config('localversion');
        $localrelease = get_config('localrelease');
        if (is_null($localversion)) {
            $localversion = 0;
            $localrelease = 0;
        }

        $config = new StdClass;
        require(get_config('docroot') . 'local/version.php');

        if ($config->version > $localversion) {
124
            $toupgradecount ++;
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
            $toupgrade['local'] = (object) array(
                'upgrade'     => true,
                'from'        => $localversion,
                'fromrelease' => $localrelease,
                'to'          => $config->version,
                'torelease'   => $config->release,
            );
        }

        if ($name == 'local') {
            $toupgrade['local']->disablelogin = $disablelogin;
            return $toupgrade['local'];
        }
    }

140
141
    $plugins = array();
    if (!empty($name)) {
142
143
144
145
146
147
        try {
            $bits = explode('.', $name);
            $pt = $bits[0];
            $pn = $bits[1];
            $pp = null;
            if ($pt == 'blocktype' && strpos($pn, '/') !== false) {
148
                $bits = explode('/', $pn);
149
150
151
152
153
154
                $pp = get_config('docroot') . 'artefact/' . $bits[0]  . '/blocktype/' . $bits[1];
            }
            validate_plugin($pt, $pn, $pp);
            $plugins[] = explode('.', $name);
        }
        catch (InstallationException $_e) {
155
            log_warn("Plugin $pt $pn is not installable: " . $_e->GetMessage());
156
        }
157
158
159
160
161
    }
    else {
        foreach ($pluginstocheck as $plugin) {
            $dirhandle = opendir(get_config('docroot') . $plugin);
            while (false !== ($dir = readdir($dirhandle))) {
162
                if (strpos($dir, '.') === 0 or 'CVS' == $dir) {
163
164
165
166
167
                    continue;
                }
                if (!is_dir(get_config('docroot') . $plugin . '/' . $dir)) {
                    continue;
                }
168
169
170
171
172
                try {
                    validate_plugin($plugin, $dir);
                    $plugins[] = array($plugin, $dir);
                }
                catch (InstallationException $_e) {
173
                    log_warn("Plugin $plugin $dir is not installable: " . $_e->GetMessage());
174
175
176
                }

                if ($plugin == 'artefact') { // go check it for blocks as well
177
                    $btlocation = get_config('docroot') . $plugin . '/' . $dir . '/blocktype';
178
                    if (!is_dir($btlocation)) {
179
                        continue;
Elliot Pahl's avatar
Elliot Pahl committed
180
                    }
181
182
                    $btdirhandle = opendir($btlocation);
                    while (false !== ($btdir = readdir($btdirhandle))) {
183
                        if (strpos($btdir, '.') === 0 or 'CVS' == $btdir) {
184
185
                            continue;
                        }
186
                        if (!is_dir(get_config('docroot') . $plugin . '/' . $dir . '/blocktype/' . $btdir)) {
187
188
189
                            continue;
                        }
                        $plugins[] = array('blocktype', $dir . '/' . $btdir);
190
191
                    }
                }
192
193
194
195
196
197
198
199
200
201
            }
        }
    }

    foreach ($plugins as $plugin) {
        $plugintype = $plugin[0];
        $pluginname = $plugin[1];
        $pluginpath = "$plugin[0]/$plugin[1]";
        $pluginkey  = "$plugin[0].$plugin[1]";

202
203
204
205
206
207
        if ($plugintype == 'blocktype' && strpos($pluginname, '/') !== false) {
            // sigh.. we're a bit special...
            $bits = explode('/', $pluginname);
            $pluginpath = 'artefact/' . $bits[0] . '/blocktype/' . $bits[1];
        }

208
209
210
        // Don't try to get the plugin info if we are installing - it will
        // definitely fail
        $pluginversion = 0;
211
212
213
214
215
216
217
218
219
        if (!$installing && table_exists(new XMLDBTable($plugintype . '_installed'))) {
            if ($plugintype == 'blocktype' && strpos($pluginname, '/')) {
                $bits = explode('/', $pluginname);
                $installed = get_record('blocktype_installed', 'name', $bits[1], 'artefactplugin', $bits[0]);
            }
            else {
                $installed = get_record($plugintype . '_installed', 'name', $pluginname);
            }
            if ($installed) {
220
221
222
                $pluginversion = $installed->version;
                $pluginrelease =  $installed->release;
            }
223
        }
224
225

        $config = new StdClass;
226
227
228
        require(get_config('docroot') . $pluginpath . '/version.php');
        if (isset($config->disablelogin) && !empty($config->disablelogin)) {
            $disablelogin = true;
229
230
231
        }

        if (empty($pluginversion)) {
232
            $newinstall = false;
233
            if (empty($installing) && $pluginkey != $name) {
234
                $newinstall = true;
235
            }
236
237
238
239
            $plugininfo = new StdClass;
            $plugininfo->install = true;
            $plugininfo->to = $config->version;
            $plugininfo->torelease = $config->release;
240
241
242
243
244
245
            if (property_exists($config, 'requires_config')) {
                $plugininfo->requires_config = $config->requires_config;
            }
            if (property_exists($config, 'requires_parent')) {
                $plugininfo->requires_parent = $config->requires_parent;
            }
246
247
248
249
250
251
252
253
254
255
256
257

            $classname = generate_class_name($plugintype, $pluginname);
            safe_require($plugintype, $pluginname);
            try {
                $classname::sanity_check();
            }
            catch (InstallationException $exc) {
                $plugininfo->to = get_string('notinstalled', 'admin');
                $plugininfo->torelease = get_string('notinstalled', 'admin');
                $plugininfo->errormsg = $exc->getMessage();
            }

258
259
260
261
262
263
264
265
266
267
            if ($newinstall) {
                $plugininfo->from = get_string('notinstalled', 'admin');
                $plugininfo->fromrelease = get_string('notinstalled', 'admin');
                $plugininfo->newinstall = true;
                $newinstallcount ++;
                $newinstalls[$pluginkey] = $plugininfo;
            }
            else {
                $toupgrade[$pluginkey] = $plugininfo;
            }
268
269
        }
        else if ($config->version > $pluginversion) {
270
271
272
273
274
            if (isset($config->minupgradefrom) && isset($config->minupgraderelease)
                && $pluginversion < $config->minupgradefrom) {
                throw new ConfigSanityException("Must upgrade to $config->minupgradefrom "
                                          . " ($config->minupgraderelease) first "
                                          . " (you have $pluginversion ($pluginrelease))");
275
            }
276
            $toupgradecount++;
277
278
279
280
281
282
            $plugininfo = new StdClass;
            $plugininfo->upgrade = true;
            $plugininfo->from = $pluginversion;
            $plugininfo->fromrelease = $pluginrelease;
            $plugininfo->to = $config->version;
            $plugininfo->torelease = $config->release;
283
284
285
286
287
288
            if (property_exists($config, 'requires_config')) {
                $plugininfo->requires_config = $config->requires_config;
            }
            if (property_exists($config, 'requires_parent')) {
                $plugininfo->requires_parent = $config->requires_parent;
            }
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303

            $classname = generate_class_name($plugintype, $pluginname);
            safe_require($plugintype, $pluginname);
            try {
                $classname::sanity_check();
            }
            catch (InstallationException $exc) {
                $plugininfo->to = $config->version;
                $plugininfo->torelease = $pluginrelease;
                $plugininfo->errormsg = $exc->getMessage();
                $toupgrade[$pluginkey] = $plugininfo;

                continue;
            }

304
305
306
307
308
            $toupgrade[$pluginkey] = $plugininfo;
        }
    }

    // if we've just asked for one, don't return an array...
309
310
311
312
313
314
315
316
317
318
319
320
    if (!empty($name)){
        if (count($toupgrade) == 1) {
            $upgrade = new StdClass;
            $upgrade->name = $name;
            foreach ((array)$toupgrade[$name] as $key => $value) {
                $upgrade->{$key} = $value;
            }
            $upgrade->disablelogin = $disablelogin;
            return $upgrade;
        }
        else {
            return array();
321
322
        }
    }
323
324
325

    // Nothing needed to be upgraded or installed
    if (count($toupgrade) == 0) {
326
        if (!empty($name))
327
        $disablelogin = false;
328
    }
329
330

    // If we get here, it's because we have an array of objects to return
331
    uksort($toupgrade, 'sort_upgrades');
332
333
    $settings['disablelogin'] = $disablelogin;
    $settings['newinstallcount'] = $newinstallcount;
334
    $settings['newinstalls'] = $newinstalls;
335
336
    $settings['toupgradecount'] = $toupgradecount;
    $toupgrade['settings'] = $settings;
337
338
339
    return $toupgrade;
}

340
341
342
343
344
/**
 * Upgrades the core system to given upgrade version.
 *
 * @param object $upgrade   The version to upgrade to
 * @return bool             Whether the upgrade succeeded or not
345
 * @throws SQLException     If the upgrade failed due to a database error
346
 */
347
348
349
function upgrade_core($upgrade) {
    global $db;

350
    $location = get_config('libroot') . 'db/';
Penny Leach's avatar
Penny Leach committed
351
352

    db_begin();
353
354

    if (!empty($upgrade->install)) {
355
        install_from_xmldb_file($location . 'install.xml');
356
357
358
    }
    else {
        require_once($location . 'upgrade.php');
Penny Leach's avatar
Penny Leach committed
359
        xmldb_core_upgrade($upgrade->from);
360
361
    }

Penny Leach's avatar
Penny Leach committed
362
363
    set_config('version', $upgrade->to);
    set_config('release', $upgrade->torelease);
364
    set_config('series', $upgrade->toseries);
365
    bump_cache_version();
Elliot Pahl's avatar
Elliot Pahl committed
366

367
    if (!empty($upgrade->install)) {
Penny Leach's avatar
Penny Leach committed
368
        core_postinst();
369
    }
370

Penny Leach's avatar
Penny Leach committed
371
372
    db_commit();
    return true;
373
374
}

375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
/**
 * Upgrades local customisations.
 *
 * @param object $upgrade   The version to upgrade to
 * @return bool             Whether the upgrade succeeded or not
 * @throws SQLException     If the upgrade failed due to a database error
 */
function upgrade_local($upgrade) {
    db_begin();

    require_once(get_config('docroot') . 'local/upgrade.php');
    xmldb_local_upgrade($upgrade->from);

    set_config('localversion', $upgrade->to);
    set_config('localrelease', $upgrade->torelease);
390
    bump_cache_version();
391
392
393
394
395

    db_commit();
    return true;
}

396
397
398
/**
 * Upgrades the plugin to a new version
 *
399
400
401
402
 * Note: This function is sometimes executed during upgrades from
 * ancient databases.  Avoid rash assumptions about what's installed
 * or these upgrades may fail.
 *
403
404
 * @param object $upgrade   Information about the plugin to upgrade
 * @return bool             Whether the upgrade succeeded or not
405
 * @throws SQLException     If the upgrade failed due to a database error
406
 */
407
408
409
410
411
412
413
414
function upgrade_plugin($upgrade) {
    global $db;

    $plugintype = '';
    $pluginname = '';

    list($plugintype, $pluginname) = explode('.', $upgrade->name);

415
416
    if ($plugintype == 'blocktype' && strpos($pluginname, '/') !== false) {
        list($artefactplugin, $blocktypename) = explode('/', $pluginname);
417
418
419
420
421
422
        $location = get_config('docroot') . 'artefact/' . $artefactplugin . '/blocktype/' . $blocktypename . '/db/';
        $function = 'xmldb_' . $plugintype . '_' . $blocktypename . '_upgrade';
    }
    else {
        $location = get_config('docroot') . $plugintype . '/' . $pluginname . '/db/';
        $function = 'xmldb_' . $plugintype . '_' . $pluginname . '_upgrade';
Elliot Pahl's avatar
Elliot Pahl committed
423
    }
424

425
    db_begin();
426
427
428

    if (!empty($upgrade->install)) {
        if (is_readable($location . 'install.xml')) {
429
            install_from_xmldb_file($location . 'install.xml');
430
431
432
433
434
        }
    }
    else {
        if (is_readable($location .  'upgrade.php')) {
            require_once($location . 'upgrade.php');
435
436
437
            if (!$function($upgrade->from)) {
                throw new InstallationException("Failed to run " . $function . " (check logs for errors)");
            }
438
439
440
441
442
443
444
        }
    }

    $installed = new StdClass;
    $installed->name = $pluginname;
    $installed->version = $upgrade->to;
    $installed->release = $upgrade->torelease;
445
446
447
448
449
450
451
452
    if ($plugintype == 'blocktype') {
        if (!empty($blocktypename)) {
            $installed->name = $blocktypename;
        }
        if (!empty($artefactplugin)) { // blocks come from artefactplugins.
            $installed->artefactplugin = $artefactplugin;
        }
    }
453
454
455
456
457
458
    if (property_exists($upgrade, 'requires_config')) {
        $installed->requires_config = $upgrade->requires_config;
    }
    if (property_exists($upgrade, 'requires_parent')) {
        $installed->requires_parent = $upgrade->requires_parent;
    }
459
    $installtable = $plugintype . '_installed';
460
461
462

    if (!empty($upgrade->install)) {
        insert_record($installtable,$installed);
Elliot Pahl's avatar
Elliot Pahl committed
463
    }
464
465
466
    else {
        update_record($installtable, $installed, 'name');
    }
467
    bump_cache_version();
468
469

    // postinst stuff...
Nigel McNie's avatar
Nigel McNie committed
470
    safe_require($plugintype, $pluginname);
471
    $pcname = generate_class_name($plugintype, $installed->name);
472
473
474
475
476
477
478
479
480
481
482

    if ($crons = call_static_method($pcname, 'get_cron')) {
        foreach ($crons as $cron) {
            $cron = (object)$cron;
            if (empty($cron->callfunction)) {
                throw new InstallationException("cron for $pcname didn't supply function name");
            }
            if (!is_callable(array($pcname, $cron->callfunction))) {
                throw new InstallationException("cron $cron->callfunction for $pcname supplied but wasn't callable");
            }
            $new = false;
483
            $table = $plugintype . '_cron';
484
485
486
            if (!empty($upgrade->install)) {
                $new = true;
            }
487
            else if (!record_exists($table, 'plugin', $pluginname, 'callfunction', $cron->callfunction)) {
488
489
490
491
                $new = true;
            }
            $cron->plugin = $pluginname;
            if (!empty($new)) {
492
                insert_record($table, $cron);
493
494
            }
            else {
495
                update_record($table, $cron, array('plugin', 'callfunction'));
496
497
498
            }
        }
    }
Elliot Pahl's avatar
Elliot Pahl committed
499

500
501
502
503
    if ($events = call_static_method($pcname, 'get_event_subscriptions')) {
        foreach ($events as $event) {
            $event = (object)$event;

504
            if (!record_exists('event_type', 'name', $event->event)) {
505
506
507
508
509
510
511
512
513
                throw new InstallationException("event $event->event for $pcname doesn't exist!");
            }
            if (empty($event->callfunction)) {
                throw new InstallationException("event $event->event for $pcname didn't supply function name");
            }
            if (!is_callable(array($pcname, $event->callfunction))) {
                throw new InstallationException("event $event->event with function $event->callfunction for $pcname supplied but wasn't callable");
            }
            $exists = false;
514
            $table = $plugintype . '_event_subscription';
515
            $block = blocktype_namespaced_to_single($pluginname);
516
            if (empty($upgrade->install)) {
517
                $exists = get_record($table, 'plugin' , $block, 'event', $event->event);
518
            }
519
            $event->plugin = $block;
520
            if (empty($exists)) {
521
                insert_record($table, $event);
522
523
            }
            else {
524
                update_record($table, $event, array('id' => $exists->id));
525
526
527
528
            }
        }
    }

529
530
531
532
533
534
    if ($activities = call_static_method($pcname, 'get_activity_types')) {
        foreach ($activities as $activity) {
            $classname = 'ActivityType' . ucfirst($plugintype) . ucfirst($pluginname) . ucfirst($activity->name);
            if (!class_exists($classname)) {
                throw new InstallationException(get_string('classmissing', 'error',  $classname, $pluginname, $plugintype));
            }
535
536
537
538
539
540
541
542
543
544
545
546
            // Add activity_type if it doesn't exist
            if (!get_record('activity_type', 'name', $activity->name, 'plugintype', $plugintype, 'pluginname', $pluginname)) {
                $activity->plugintype = $plugintype;
                $activity->pluginname = $pluginname;
                $activity->defaultmethod = get_config('defaultnotificationmethod') ? get_config('defaultnotificationmethod') : $activity->defaultmethod;
                $where = (object) array(
                    'name'       => $activity->name,
                    'plugintype' => $plugintype,
                    'pluginname' => $pluginname,
                );
                ensure_record_exists('activity_type', $where, $activity);
            }
547
548
549
        }
    }

550
551
     // install artefact types
    if ($plugintype == 'artefact') {
552
553
554
        if (!is_callable(array($pcname, 'get_artefact_types'))) {
            throw new InstallationException("Artefact plugin $pcname must implement get_artefact_types and doesn't");
        }
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
        $types = call_static_method($pcname, 'get_artefact_types');
        $ph = array();
        if (is_array($types)) {
            foreach ($types as $type) {
                $ph[] = '?';
                if (!record_exists('artefact_installed_type', 'plugin', $pluginname, 'name', $type)) {
                    $t = new StdClass;
                    $t->name = $type;
                    $t->plugin = $pluginname;
                    insert_record('artefact_installed_type',$t);
                }
            }
            $select = '(plugin = ? AND name NOT IN (' . implode(',', $ph) . '))';
            delete_records_select('artefact_installed_type', $select,
                                  array_merge(array($pluginname),$types));
        }
    }
Elliot Pahl's avatar
Elliot Pahl committed
572

573
574
575
    // install blocktype categories.
    if ($plugintype == 'blocktype' && get_config('installed')) {
        install_blocktype_categories_for_plugin($pluginname);
576
        install_blocktype_viewtypes_for_plugin($pluginname);
577
578
    }

579
580
    $prevversion = (empty($upgrade->install)) ? $upgrade->from : 0;
    call_static_method($pcname, 'postinst', $prevversion);
Elliot Pahl's avatar
Elliot Pahl committed
581

582
583
    db_commit();
    return true;
584
585
586
587
}

function core_postinst() {
    $status = true;
588
589

    // Attempt to create session directories
590
    $sessionpath = get_config('sessionpath');
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
    if (check_dir_exists($sessionpath)) {
        // Create three levels of directories, named 0-9, a-f
        $characters = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f');
        foreach ($characters as $c1) {
            if (check_dir_exists("$sessionpath/$c1")) {
                foreach ($characters as $c2) {
                    if (check_dir_exists("$sessionpath/$c1/$c2")) {
                        foreach ($characters as $c3) {
                            if (!check_dir_exists("$sessionpath/$c1/$c2/$c3")) {
                                $status = false;
                                break(3);
                            }
                        }
                    }
                    else {
                        $status = false;
                        break(2);
                    }
                }
            }
            else {
                $status = false;
                break;
            }
        }
    }
    else {
        $status = false;
    }
620

621
    $now = db_format_timestamp(time());
622
623
624
    // Set default search plugin
    set_config('searchplugin', 'internal');

625
    set_config('lang', 'en.utf8');
626
    set_config('installation_key', get_random_key());
627
    set_config('installation_time', $now);
628
    set_config('stats_installation_time', $now);
629

630
631
632
633
634
635
636
    // Pre-define SMTP settings
    set_config('smtphosts', '');
    set_config('smtpport', '');
    set_config('smtpuser', '');
    set_config('smtppass', '');
    set_config('smtpsecure', '');

637
638
639
640
641
642
643
644
645
    // XMLDB adds a table's keys immediately after creating the table.  Some
    // foreign keys therefore cannot be created during the XMLDB installation,
    // because they refer to tables created later in the installation.  These
    // missing keys can be created now that all the core tables exist.
    $table = new XMLDBTable('usr');
    $key = new XMLDBKey('profileiconfk');
    $key->setAttributes(XMLDB_KEY_FOREIGN, array('profileicon'), 'artefact', array('id'));
    add_key($table, $key);

646
647
648
649
650
    $table = new XMLDBTable('institution');
    $key = new XMLDBKey('logofk');
    $key->setAttributes(XMLDB_KEY_FOREIGN, array('logo'), 'artefact', array('id'));
    add_key($table, $key);

651
    // PostgreSQL supports indexes over functions of columns, MySQL does not.
652
    // We make use if this if we can
653
    if (is_postgres()) {
654
        // Improve the username index
655
656
        execute_sql('DROP INDEX {usr_use_uix}');
        execute_sql('CREATE UNIQUE INDEX {usr_use_uix} ON {usr}(LOWER(username))');
657

658
659
660
661
662
663
664
665
666
        // Add user search indexes
        // Postgres only.  We could create non-lowercased indexes in MySQL, but
        // they would not be useful, and would require a change to varchar columns.
        execute_sql('CREATE INDEX {usr_fir_ix} ON {usr}(LOWER(firstname))');
        execute_sql('CREATE INDEX {usr_las_ix} ON {usr}(LOWER(lastname))');
        execute_sql('CREATE INDEX {usr_pre_ix} ON {usr}(LOWER(preferredname))');
        execute_sql('CREATE INDEX {usr_ema_ix} ON {usr}(LOWER(email))');
        execute_sql('CREATE INDEX {usr_stu_ix} ON {usr}(LOWER(studentid))');

667
668
        // Only one profile view per user
        execute_sql("CREATE UNIQUE INDEX {view_own_type_uix} ON {view}(owner) WHERE type = 'profile'");
669
670
    }

671
672
673
674
675
676
677
678
679
680
681
    // Some more advanced constraints. XMLDB can't handle this in its xml file format
    execute_sql('ALTER TABLE {artefact} ADD CHECK (
        (owner IS NOT NULL AND "group" IS NULL     AND institution IS NULL) OR
        (owner IS NULL     AND "group" IS NOT NULL AND institution IS NULL) OR
        (owner IS NULL     AND "group" IS NULL     AND institution IS NOT NULL)
    )');
    execute_sql('ALTER TABLE {view} ADD CHECK (
        (owner IS NOT NULL AND "group" IS NULL     AND institution IS NULL) OR
        (owner IS NULL     AND "group" IS NOT NULL AND institution IS NULL) OR
        (owner IS NULL     AND "group" IS NULL     AND institution IS NOT NULL)
    )');
682
683
684
685
    execute_sql('ALTER TABLE {artefact} ADD CHECK (
        (author IS NOT NULL AND authorname IS NULL    ) OR
        (author IS NULL     AND authorname IS NOT NULL)
    )');
686
    execute_sql('ALTER TABLE {view_access} ADD CHECK (
687
688
689
690
691
        (accesstype IS NOT NULL AND "group" IS NULL     AND usr IS NULL     AND token IS NULL   AND institution IS NULL) OR
        (accesstype IS NULL     AND "group" IS NOT NULL AND usr IS NULL     AND token IS NULL AND institution IS NULL) OR
        (accesstype IS NULL     AND "group" IS NULL     AND usr IS NOT NULL AND token IS NULL AND institution IS NULL) OR
        (accesstype IS NULL     AND "group" IS NULL     AND usr IS NULL     AND token IS NOT NULL AND institution IS NULL) OR
        (accesstype IS NULL     AND "group" IS NULL     AND usr IS NULL     AND token IS NULL AND institution IS NOT NULL)
692
    )');
693
694
695
696
697
    execute_sql('ALTER TABLE {collection} ADD CHECK (
        (owner IS NOT NULL AND "group" IS NULL     AND institution IS NULL) OR
        (owner IS NULL     AND "group" IS NOT NULL AND institution IS NULL) OR
        (owner IS NULL     AND "group" IS NULL     AND institution IS NOT NULL)
    )');
698

699
    set_antispam_defaults();
700
    reload_html_filters();
701
702
703
704
705
706
707
708
709
710
711

    // Default set of sites from which iframe content can be embedded
    $iframesources = array(
        'www.youtube.com/embed/'                   => 'YouTube',
        'player.vimeo.com/video/'                  => 'Vimeo',
        'www.slideshare.net/slideshow/embed_code/' => 'SlideShare',
        'www.glogster.com/glog/'                   => 'Glogster',
        'www.glogster.com/glog.php'                => 'Glogster',
        'edu.glogster.com/glog/'                   => 'Glogster',
        'edu.glogster.com/glog.php'                => 'Glogster',
        'wikieducator.org/index.php'               => 'WikiEducator',
712
        'voki.com/php/'                            => 'Voki',
713
714
715
716
717
718
719
    );
    $iframedomains = array(
        'YouTube'      => 'www.youtube.com',
        'Vimeo'        => 'vimeo.com',
        'SlideShare'   => 'www.slideshare.net',
        'Glogster'     => 'www.glogster.com',
        'WikiEducator' => 'wikieducator.org',
720
        'Voki'         => 'voki.com',
721
722
723
    );
    update_safe_iframes($iframesources, $iframedomains);

724
725
726
    require_once(get_config('docroot') . 'lib/file.php');
    update_magicdb_path();

727
    return $status;
728
729
}

730
function core_install_lastcoredata_defaults() {
731
    global $USER;
732
    db_begin();
733
    $institution = new StdClass;
734
735
736
    $institution->name = 'mahara';
    $institution->displayname = 'No Institution';
    $institution->authplugin  = 'internal';
737
    $institution->theme  = 'default';
738
    $institution->priority = 0;
739
    insert_record('institution', $institution);
740

741
742
743
744
745
746
747
    $pages = site_content_pages();
    $now = db_format_timestamp(time());
    foreach ($pages as $name) {
        $page = new stdClass();
        $page->name = $name;
        $page->ctime = $now;
        $page->mtime = $now;
748
        $page->content = get_string($page->name . 'defaultcontent', 'install', get_string('staticpageconfigdefault', 'install'));
749
750
751
752
        $page->institution = 'mahara';
        insert_record('site_content', $page);
    }

753
    $auth_instance = new StdClass;
754
    $auth_instance->instancename  = 'Internal';
755
    $auth_instance->priority='1';
756
757
    $auth_instance->institution   = 'mahara';
    $auth_instance->authname      = 'internal';
758
759
    $auth_instance->id = insert_record('auth_instance', $auth_instance, 'id', true);

760
761
762
763
764
765
766
767
768
    // Insert the root user
    $user = new StdClass;
    $user->id = 0;
    $user->username = 'root';
    $user->password = '*';
    $user->salt = '*';
    $user->firstname = 'System';
    $user->lastname = 'User';
    $user->email = 'root@example.org';
769
    $user->quota = get_config_plugin('artefact', 'file', 'defaultquota');
770
    $user->authinstance = $auth_instance->id;
771

772
773
    if (is_mysql()) { // gratuitous mysql workaround
        $newid = insert_record('usr', $user, 'id', true);
774
        set_field('usr', 'id', 0, 'id', $newid);
775
        execute_sql('ALTER TABLE {usr} AUTO_INCREMENT=1');
776
    }
777
778
779
    else {
        insert_record('usr', $user);
    }
780

781
782
783
    // install the default layout options
    install_view_layout_defaults();

784
    require_once('group.php');
785
    install_system_profile_view();
786
    install_system_dashboard_view();
787
    install_system_grouphomepage_view();
788

789
790
791
    require_once('license.php');
    install_licenses_default();

792
793
794
    require_once('skin.php');
    install_skins_default();

795
    // Insert the admin user
796
797
    $user = new StdClass;
    $user->username = 'admin';
798
    $user->salt = auth_get_random_salt();
799
    $user->password = crypt('mahara', '$2a$' . get_config('bcrypt_cost') . '$' . substr(md5(get_config('passwordsaltmain') . $user->salt), 0, 22));
800
    $user->password = substr($user->password, 0, 7) . substr($user->password, 7+22);
801
    $user->authinstance = $auth_instance->id;
802
803
804
805
806
    $user->passwordchange = 1;
    $user->admin = 1;
    $user->firstname = 'Admin';
    $user->lastname = 'User';
    $user->email = 'admin@example.org';
807
    $user->quota = get_config_plugin('artefact', 'file', 'defaultquota');
808
809
810
811
    $user->id = insert_record('usr', $user, 'id', true);
    set_profile_field($user->id, 'email', $user->email);
    set_profile_field($user->id, 'firstname', $user->firstname);
    set_profile_field($user->id, 'lastname', $user->lastname);
812
    handle_event('createuser', $user);
813
    activity_add_admin_defaults(array($user->id));
814
    db_commit();
815
816

    // if we're installing, set up the block categories here and then poll the plugins.
Elliot Pahl's avatar
Elliot Pahl committed
817
    // if we're upgrading this happens somewhere else.  This is because of dependency issues around
818
    // the order of installation stuff.
819
    install_blocktype_extras();
820
821
}

822
823
824
function core_install_firstcoredata_defaults() {
    // Install the default institution
    db_begin();
Elliot Pahl's avatar
Elliot Pahl committed
825

826
    set_config('session_timeout', 86400);
827
    set_config('sitename', 'Mahara');
828
    set_config('defaultregistrationexpirylifetime', 1209600);
829
    set_config('defaultaccountinactivewarn', 604800);
830
    set_config('creategroups', 'all');
831
    set_config('createpublicgroups', 'all');
832
    set_config('allowpublicviews', 1);
833
    set_config('allowpublicprofiles', 1);
834
    set_config('allowanonymouspages', 0);
835
    set_config('generatesitemap', 1);
836
837
838
    set_config('showselfsearchsideblock', 0);
    set_config('showtagssideblock', 1);
    set_config('tagssideblockmaxtags', 20);
839
    set_config('usersallowedmultipleinstitutions', 1);
840
841
    set_config('viewmicroheaders', 0);
    set_config('userscanchooseviewthemes', 0);
842
    set_config('anonymouscomments', 1);
843
    set_config('homepageinfo', 1);
844
    set_config('showonlineuserssideblock', 1);
845
    set_config('footerlinks', serialize(array('privacystatement', 'about', 'contactus')));
846
    set_config('nousernames', 0);
847
    set_config('onlineuserssideblockmaxusers', 10);
848
    set_config('loggedinprofileviewaccess', 1);
849
    set_config('dropdownmenu', 0);
850
851
    // Set this to a random starting number to make minor version slightly harder to detect
    set_config('cacheversion', rand(1000, 9999));
852
    set_config('watchlistnotification_delay', 20);
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879

    // install the applications
    $app = new StdClass;
    $app->name = 'mahara';
    $app->displayname = 'Mahara';
    $app->xmlrpcserverurl = '/api/xmlrpc/server.php';
    $app->ssolandurl = '/auth/xmlrpc/land.php';
    insert_record('application', $app);

    $app->name = 'moodle';
    $app->displayname = 'Moodle';
    $app->xmlrpcserverurl = '/mnet/xmlrpc/server.php';
    $app->ssolandurl = '/auth/mnet/land.php';
    insert_record('application', $app);

    // insert the event types
    $eventtypes = array(
        'createuser',
        'updateuser',
        'suspenduser',
        'unsuspenduser',
        'deleteuser',
        'undeleteuser',
        'expireuser',
        'unexpireuser',
        'deactivateuser',
        'activateuser',
880
        'userjoinsgroup',
881
882
        'saveartefact',
        'deleteartefact',
883
        'deleteartefacts',
884
885
        'saveview',
        'deleteview',
886
        'blockinstancecommit',
887
        'deleteblockinstance',
888
889
890
891
        'addfriend',
        'removefriend',
        'addfriendrequest',
        'removefriendrequest',
Richard Mansfield's avatar
Richard Mansfield committed
892
        'creategroup',
893
        'loginas',
894
895
896
897
898
899
900
901
    );

    foreach ($eventtypes as $et) {
        $e = new StdClass;
        $e->name = $et;
        insert_record('event_type', $e);
    }

902
903
904
905
906
    // install the core event subscriptions
    $subs = array(
        array(
            'event'        => 'createuser',
            'callfunction' => 'activity_set_defaults',
907
        ),
908
909
910
911
        array(
            'event'        => 'createuser',
            'callfunction' => 'add_user_to_autoadd_groups',
        ),
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
        array(
            'event'         => 'blockinstancecommit',
            'callfunction'  => 'watchlist_record_changes',
        ),
        array(
            'event'         => 'deleteblockinstance',
            'callfunction'  => 'watchlist_block_deleted',
        ),
        array(
            'event'         => 'saveartefact',
            'callfunction'  => 'watchlist_record_changes',
        ),
        array(
            'event'         => 'saveview',
            'callfunction'  => 'watchlist_record_changes',
        ),
928
929
930
931
932
    );

    foreach ($subs as $sub) {
        insert_record('event_subscription', (object)$sub);
    }
Elliot Pahl's avatar
Elliot Pahl committed
933

934
    // Install the activity types. Name, admin, delay, allownonemethod, defaultmethod.
935
    $activitytypes = array(
936
937
938
939
940
941
942
943
944
945
        array('maharamessage',      0, 0, 0, 'email'),
        array('usermessage',        0, 0, 0, 'email'),
        array('watchlist',          0, 1, 1, 'email'),
        array('viewaccess',         0, 1, 1, 'email'),
        array('contactus',          1, 1, 1, 'email'),
        array('objectionable',      1, 1, 1, 'email'),
        array('virusrepeat',        1, 1, 1, 'email'),
        array('virusrelease',       1, 1, 1, 'email'),
        array('institutionmessage', 0, 0, 1, 'email'),
        array('groupmessage',       0, 1, 1, 'email'),
946
947
948
949
950
951
952
    );

    foreach ($activitytypes as $at) {
        $a = new StdClass;
        $a->name = $at[0];
        $a->admin = $at[1];
        $a->delay = $at[2];
953
        $a->allownonemethod = $at[3];
954
        $a->defaultmethod = $at[4];
955
956
957
958
959
        insert_record('activity_type', $a);
    }

    // install the cronjobs...
    $cronjobs = array(
960
        'auth_clean_partial_registrations'          => array('5', '0', '*', '*', '*'),
961
        'auth_clean_expired_password_requests'      => array('5', '0', '*', '*', '*'),
962
963
964
965
966
967
        'auth_handle_account_expiries'              => array('5', '10', '*', '*', '*'),
        'auth_handle_institution_expiries'          => array('5', '9', '*', '*', '*'),
        'activity_process_queue'                    => array('*/5', '*', '*', '*', '*'),
        'auth_remove_old_session_files'             => array('30', '20', '*', '*', '*'),
        'recalculate_quota'                         => array('15', '2', '*', '*', '*'),
        'import_process_queue'                      => array('*/5', '*', '*', '*', '*'),
968
969
        'export_process_queue'                      => array('*/6', '*', '*', '*', '*'),
        'submissions_delete_removed_archive'        => array('15', '1', '1', '*', '*'),
970
971
972
973
974
975
976
        'cron_send_registration_data'               => array(rand(0, 59), rand(0, 23), '*', '*', rand(0, 6)),
        'export_cleanup_old_exports'                => array('0', '3,15', '*', '*', '*'),
        'import_cleanup_old_imports'                => array('0', '4,16', '*', '*', '*'),
        'cron_site_data_weekly'                     => array('55', '23', '*', '*', '6'),
        'cron_site_data_daily'                      => array('51', '23', '*', '*', '*'),
        'cron_check_for_updates'                    => array(rand(0, 59), rand(0, 23), '*', '*', '*'),
        'cron_clean_internal_activity_notifications'=> array(45, 22, '*', '*', '*'),
977
        'cron_sitemap_daily'                        => array(0, 1, '*', '*', '*'),
978
        'file_cleanup_old_cached_files'             => array(0, 1, '*', '*', '*'),
979
        'user_login_tries_to_zero'                  => array('*/5', '*', '*', '*', '*'),
980
        'cron_institution_registration_data'        => array(rand(0, 59), rand(0, 23), '*', '*', rand(0, 6)),
981
982
        'cron_institution_data_weekly'              => array('55', '23', '*', '*', '6'),
        'cron_institution_data_daily'               => array('51', '23', '*', '*', '*'),
983
        'check_imap_for_bounces'                    => array('*', '*', '*', '*', '*'),
984
        'cron_event_log_expire'                     => array('7', '23', '*', '*', '*'),
985
        'watchlist_process_notifications'           => array('*', '*', '*', '*', '*'),
986
987
988
989
990
991
992
993
994
995
996
    );
    foreach ($cronjobs as $callfunction => $times) {
        $cron = new StdClass;
        $cron->callfunction = $callfunction;
        $cron->minute       = $times[0];
        $cron->hour         = $times[1];
        $cron->day          = $times[2];
        $cron->month        = $times[3];
        $cron->dayofweek    = $times[4];
        insert_record('cron', $cron);
    }
Elliot Pahl's avatar
Elliot Pahl committed
997

998
    $viewtypes = array('dashboard', 'portfolio', 'profile', 'grouphomepage');
999
1000
1001
1002
1003
    foreach ($viewtypes as $vt) {
        insert_record('view_type', (object)array(
            'type' => $vt,
        ));
    }
1004
1005
1006
1007
    db_commit();
}


Elliot Pahl's avatar
Elliot Pahl committed
1008
1009
1010
/**
 * xmldb will pass us the xml file and we can perform any substitution as necessary
 */
1011
function local_xmldb_contents_sub(&$contents) {
1012
    // the main install.xml file needs to sub in plugintype tables.
1013
1014
1015
1016
1017
1018
1019
1020
1021
    $searchstring = '<!-- PLUGIN_TYPE_SUBSTITUTION -->';
    if (strstr($contents, $searchstring) === 0) {
        return;
    }
    // ok, we're in the main file and we need to install all the plugin tables
    // get the basic skeleton structure
    $plugintables = file_get_contents(get_config('docroot') . 'lib/db/plugintables.xml');
    $tosub = '';
    foreach (plugin_types() as $plugin) {
Elliot Pahl's avatar
Elliot Pahl committed
1022
        // any that want their own stuff can put it in docroot/plugintype/lib/db/plugintables.xml
1023
        //- like auth is a bit special
1024
1025
1026
        $specialcase = get_config('docroot') . $plugin . '/plugintables.xml';
        if (is_readable($specialcase)) {
            $tosub .= file_get_contents($specialcase) . "\n";
Elliot Pahl's avatar
Elliot Pahl committed
1027
        }
1028
        else {
1029
1030
1031
1032
1033
1034
1035
1036
            $replaced = '';
            // look for tables to put at the start...
            $pretables = get_config('docroot') . $plugin . '/beforetables.xml';
            if (is_readable($pretables)) {
                $replaced = file_get_contents($pretables) . "\n";
            }

            // perform any additional once off substitutions
1037
1038
            require_once(get_config('docroot') . $plugin . '/lib.php');
            if (method_exists(generate_class_name($plugin), 'extra_xmldb_substitution')) {
1039
                $replaced  .= call_static_method(generate_class_name($plugin), 'extra_xmldb_substitution', $plugintables);
1040
            }
1041
            else {
1042
                $replaced .= $plugintables;
1043
            }
1044
            $tosub .= str_replace('__PLUGINTYPE__', $plugin, $replaced) . "\n";
1045
            // look for any tables to put at the end..
1046
1047
1048
            $extratables = get_config('docroot') . $plugin . '/extratables.xml';
            if (is_readable($extratables)) {
                $tosub .= file_get_contents($extratables) . "\n";
1049
            }
1050
1051
        }
    }
1052
    $contents = str_replace($searchstring, $tosub, $contents);
1053
1054
}

1055
1056
1057
1058
1059
1060
1061
1062

/**
 * validates a plugin for installation
 * @throws InstallationException
*/
function validate_plugin($plugintype, $pluginname, $pluginpath='') {

    if (empty($pluginpath)) {
1063
        $pluginpath = get_config('docroot') . $plugintype . '/' . $pluginname;
1064
1065
1066
1067
    }
    if (!file_exists($pluginpath . '/version.php')) {
        throw new InstallationException(get_string('versionphpmissing', 'error', $plugintype, $pluginname));
    }
1068
1069
1070
1071
1072
1073
    safe_require($plugintype, $pluginname);
    $classname = generate_class_name($plugintype, $pluginname);
    if (!class_exists($classname)) {
        throw new InstallationException(get_string('classmissing', 'error', $classname, $plugintype, $pluginname));
    }
    require_once(get_config('docroot') . $plugintype . '/lib.php');
1074
1075
1076
1077
1078
    $funname = $plugintype . '_check_plugin_sanity';
    if (function_exists($funname)) {
        $funname($pluginname);
    }
}
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096

/*
* the order things are installed/upgraded in matters
*/

function sort_upgrades($k1, $k2) {
    if ($k1 == 'core') {
        return -1;
    }