upgrade.php 62.4 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
    $newinstalls = array();
47
48
49
50
51
52

    require('version.php');
    // check core first...
    if (empty($name) || $name == 'core') {
        try {
            $coreversion = get_config('version');
Elliot Pahl's avatar
Elliot Pahl committed
53
        }
54
55
56
        catch (Exception $e) {
            $coreversion = 0;
        }
57
58
59
60
        $core = new stdClass();
        $core->to = $config->version;
        $core->torelease = $config->release;
        $core->toseries = $config->series;
61
        $toupgrade['core'] = $core;
62
        if (empty($coreversion)) {
63
            if (is_mysql()) { // Show a more informative error message if using mysql with skip-innodb
64
                // In MySQL 5.6.x, we run the command 'SHOW ENGINES' to check if InnoDB is enabled or not
65
                global $db;
66
67
68
69
70
71
72
73
74
75
76
                $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) {
77
78
79
                    throw new ConfigSanityException("Mahara requires InnoDB tables.  Please ensure InnoDB tables are enabled in your MySQL server.");
                }
            }
80
81
            $core->install = true;
            $installing = true;
Elliot Pahl's avatar
Elliot Pahl committed
82
        }
83
        else if ($config->version > $coreversion) {
84
            $corerelease = get_config('release');
Elliot Pahl's avatar
Elliot Pahl committed
85
            if (isset($config->minupgradefrom) && isset($config->minupgraderelease)
86
                && $coreversion < $config->minupgradefrom) {
87
                throw new ConfigSanityException("Must upgrade to $config->minupgradefrom "
88
89
                                          . "($config->minupgraderelease) first "
                                          . " (you have $coreversion ($corerelease)");
90
            }
91
            $toupgradecount ++;
92
93
            $core->upgrade = true;
            $core->from = $coreversion;
94
            $core->fromrelease = $corerelease;
95
        }
96
97
98
99
        else {
            // Core doesn't need to be upgraded. Remove it from the list!
            unset($toupgrade['core']);
        }
100
101
    }

102
    // If we were just checking if the core needed to be upgraded, we can stop here
103
104
105
106
    if ($name == 'core') {
        return $toupgrade['core'];
    }

107
108
109
110
111
112
113
114
115
116
117
118
    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) {
119
            $toupgradecount ++;
120
121
122
123
124
125
126
127
128
129
130
131
132
133
            $toupgrade['local'] = (object) array(
                'upgrade'     => true,
                'from'        => $localversion,
                'fromrelease' => $localrelease,
                'to'          => $config->version,
                'torelease'   => $config->release,
            );
        }

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

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

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

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

196
197
198
199
200
201
        if ($plugintype == 'blocktype' && strpos($pluginname, '/') !== false) {
            // sigh.. we're a bit special...
            $bits = explode('/', $pluginname);
            $pluginpath = 'artefact/' . $bits[0] . '/blocktype/' . $bits[1];
        }

202
203
204
        // Don't try to get the plugin info if we are installing - it will
        // definitely fail
        $pluginversion = 0;
205
206
207
208
209
210
211
212
213
        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) {
214
215
216
                $pluginversion = $installed->version;
                $pluginrelease =  $installed->release;
            }
217
        }
218
219

        $config = new StdClass;
220
        require(get_config('docroot') . $pluginpath . '/version.php');
221
222

        if (empty($pluginversion)) {
223
            $newinstall = false;
224
            if (empty($installing) && $pluginkey != $name) {
225
                $newinstall = true;
226
            }
227
228
229
230
            $plugininfo = new StdClass;
            $plugininfo->install = true;
            $plugininfo->to = $config->version;
            $plugininfo->torelease = $config->release;
231
232
233
234
235
236
            if (property_exists($config, 'requires_config')) {
                $plugininfo->requires_config = $config->requires_config;
            }
            if (property_exists($config, 'requires_parent')) {
                $plugininfo->requires_parent = $config->requires_parent;
            }
237
238
239

            $classname = generate_class_name($plugintype, $pluginname);
            safe_require($plugintype, $pluginname);
240
            // Check if there is a displayname
241
            $plugininfo->displayname = call_static_method($classname, 'get_plugin_display_name');
242

243
244
245
246
247
248
249
250
251
            try {
                $classname::sanity_check();
            }
            catch (InstallationException $exc) {
                $plugininfo->to = get_string('notinstalled', 'admin');
                $plugininfo->torelease = get_string('notinstalled', 'admin');
                $plugininfo->errormsg = $exc->getMessage();
            }

252
253
254
255
256
257
258
259
260
261
            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;
            }
262
263
        }
        else if ($config->version > $pluginversion) {
264
265
266
267
268
            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))");
269
            }
270
            $toupgradecount++;
271
272
273
274
275
276
            $plugininfo = new StdClass;
            $plugininfo->upgrade = true;
            $plugininfo->from = $pluginversion;
            $plugininfo->fromrelease = $pluginrelease;
            $plugininfo->to = $config->version;
            $plugininfo->torelease = $config->release;
277
278
279
280
281
282
            if (property_exists($config, 'requires_config')) {
                $plugininfo->requires_config = $config->requires_config;
            }
            if (property_exists($config, 'requires_parent')) {
                $plugininfo->requires_parent = $config->requires_parent;
            }
283
284
285

            $classname = generate_class_name($plugintype, $pluginname);
            safe_require($plugintype, $pluginname);
286
            // Check if there is a displayname
287
            $plugininfo->displayname = call_static_method($classname, 'get_plugin_display_name');
288
289
290
291
292
293
294
295
296
297
298
299
            try {
                $classname::sanity_check();
            }
            catch (InstallationException $exc) {
                $plugininfo->to = $config->version;
                $plugininfo->torelease = $pluginrelease;
                $plugininfo->errormsg = $exc->getMessage();
                $toupgrade[$pluginkey] = $plugininfo;

                continue;
            }

300
301
302
303
304
            $toupgrade[$pluginkey] = $plugininfo;
        }
    }

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

    // If we get here, it's because we have an array of objects to return
320
    uksort($toupgrade, 'sort_upgrades');
321
    $settings['newinstallcount'] = $newinstallcount;
322
    $settings['newinstalls'] = $newinstalls;
323
324
    $settings['toupgradecount'] = $toupgradecount;
    $toupgrade['settings'] = $settings;
325
326
327
    return $toupgrade;
}

328
329
330
331
332
/**
 * Upgrades the core system to given upgrade version.
 *
 * @param object $upgrade   The version to upgrade to
 * @return bool             Whether the upgrade succeeded or not
333
 * @throws SQLException     If the upgrade failed due to a database error
334
 */
335
336
337
function upgrade_core($upgrade) {
    global $db;

338
    $location = get_config('libroot') . 'db/';
Penny Leach's avatar
Penny Leach committed
339
340

    db_begin();
341
342

    if (!empty($upgrade->install)) {
343
        install_from_xmldb_file($location . 'install.xml');
344
345
346
    }
    else {
        require_once($location . 'upgrade.php');
Penny Leach's avatar
Penny Leach committed
347
        xmldb_core_upgrade($upgrade->from);
348
349
    }

Penny Leach's avatar
Penny Leach committed
350
351
    set_config('version', $upgrade->to);
    set_config('release', $upgrade->torelease);
352
    set_config('series', $upgrade->toseries);
353
    bump_cache_version();
Elliot Pahl's avatar
Elliot Pahl committed
354

355
    if (!empty($upgrade->install)) {
Penny Leach's avatar
Penny Leach committed
356
        core_postinst();
357
    }
358

Penny Leach's avatar
Penny Leach committed
359
360
    db_commit();
    return true;
361
362
}

363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
/**
 * 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);
378
    bump_cache_version();
379
380
381
382
383

    db_commit();
    return true;
}

384
385
386
/**
 * Upgrades the plugin to a new version
 *
387
388
389
390
 * Note: This function is sometimes executed during upgrades from
 * ancient databases.  Avoid rash assumptions about what's installed
 * or these upgrades may fail.
 *
391
392
 * @param object $upgrade   Information about the plugin to upgrade
 * @return bool             Whether the upgrade succeeded or not
393
 * @throws SQLException     If the upgrade failed due to a database error
394
 */
395
396
397
398
399
400
401
402
function upgrade_plugin($upgrade) {
    global $db;

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

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

403
404
    if ($plugintype == 'blocktype' && strpos($pluginname, '/') !== false) {
        list($artefactplugin, $blocktypename) = explode('/', $pluginname);
405
406
407
408
409
410
        $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
411
    }
412

413
    db_begin();
414
415
416

    if (!empty($upgrade->install)) {
        if (is_readable($location . 'install.xml')) {
417
            install_from_xmldb_file($location . 'install.xml');
418
419
420
421
422
        }
    }
    else {
        if (is_readable($location .  'upgrade.php')) {
            require_once($location . 'upgrade.php');
423
424
425
            if (!$function($upgrade->from)) {
                throw new InstallationException("Failed to run " . $function . " (check logs for errors)");
            }
426
427
428
429
430
431
432
        }
    }

    $installed = new StdClass;
    $installed->name = $pluginname;
    $installed->version = $upgrade->to;
    $installed->release = $upgrade->torelease;
433
434
435
436
437
438
439
440
    if ($plugintype == 'blocktype') {
        if (!empty($blocktypename)) {
            $installed->name = $blocktypename;
        }
        if (!empty($artefactplugin)) { // blocks come from artefactplugins.
            $installed->artefactplugin = $artefactplugin;
        }
    }
441
442
443
444
445
446
    if (property_exists($upgrade, 'requires_config')) {
        $installed->requires_config = $upgrade->requires_config;
    }
    if (property_exists($upgrade, 'requires_parent')) {
        $installed->requires_parent = $upgrade->requires_parent;
    }
447
    $installtable = $plugintype . '_installed';
448
449
450

    if (!empty($upgrade->install)) {
        insert_record($installtable,$installed);
Elliot Pahl's avatar
Elliot Pahl committed
451
    }
452
453
454
    else {
        update_record($installtable, $installed, 'name');
    }
455
    bump_cache_version();
456
457

    // postinst stuff...
Nigel McNie's avatar
Nigel McNie committed
458
    safe_require($plugintype, $pluginname);
459
    $pcname = generate_class_name($plugintype, $installed->name);
460
461
462
463
464
465
466
467
468
469
470

    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;
471
            $table = $plugintype . '_cron';
472
473
474
            if (!empty($upgrade->install)) {
                $new = true;
            }
475
            else if (!record_exists($table, 'plugin', $pluginname, 'callfunction', $cron->callfunction)) {
476
477
478
479
                $new = true;
            }
            $cron->plugin = $pluginname;
            if (!empty($new)) {
480
                insert_record($table, $cron);
481
482
            }
            else {
483
                update_record($table, $cron, array('plugin', 'callfunction'));
484
485
486
            }
        }
    }
Elliot Pahl's avatar
Elliot Pahl committed
487

488
489
490
491
    if ($events = call_static_method($pcname, 'get_event_subscriptions')) {
        foreach ($events as $event) {
            $event = (object)$event;

492
            if (!record_exists('event_type', 'name', $event->event)) {
493
494
495
496
497
498
499
500
501
                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;
502
            $table = $plugintype . '_event_subscription';
503
            $block = blocktype_namespaced_to_single($pluginname);
504
            if (empty($upgrade->install)) {
505
                $exists = get_record($table, 'plugin' , $block, 'event', $event->event);
506
            }
507
            $event->plugin = $block;
508
            if (empty($exists)) {
509
                insert_record($table, $event);
510
511
            }
            else {
512
                update_record($table, $event, array('id' => $exists->id));
513
514
515
516
            }
        }
    }

517
518
519
520
521
522
    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));
            }
523
524
525
526
527
528
529
530
531
532
533
534
            // 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);
            }
535
536
537
        }
    }

538
    // install artefact types
539
    if ($plugintype == 'artefact') {
540
541
542
        if (!is_callable(array($pcname, 'get_artefact_types'))) {
            throw new InstallationException("Artefact plugin $pcname must implement get_artefact_types and doesn't");
        }
543
544
545
        $types = call_static_method($pcname, 'get_artefact_types');
        $ph = array();
        if (is_array($types)) {
546
547
548
549
550
551
552
553
554
555
            // Check for missing plugins - don't try to remove their data.
            // Bugs 505732 and 1287344.
            $used_types = get_records_sql_assoc("SELECT t.name, count(a.id) ct, t.plugin FROM {artefact_installed_type} t
                    LEFT JOIN {artefact} a ON t.name = a.artefacttype
                    GROUP BY t.name
                    HAVING count(a.id) > 0 AND plugin = '$pluginname'");
            if ($used_types === FALSE) {
                $used_types = array();
            }

556
557
558
559
560
561
562
563
            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);
                }
564
565
566
                if (isset($used_types[$type])) {
                    unset($used_types[$type]);
                }
567
            }
568
569
570
571
572
573
574

            foreach ($used_types as $type) {
                $ph[] = '?';
            }

            $used_types = array_keys($used_types);

575
576
            $select = '(plugin = ? AND name NOT IN (' . implode(',', $ph) . '))';
            delete_records_select('artefact_installed_type', $select,
577
578
579
580
                    array_merge(array($pluginname),$types,$used_types));
            if (!empty($used_types)) {
                log_warn('Plugin for artefact type(s) "' . implode('", "', $used_types) . '" has gone away', true, false);
            }
581
582
        }
    }
Elliot Pahl's avatar
Elliot Pahl committed
583

584
585
586
    // install blocktype categories.
    if ($plugintype == 'blocktype' && get_config('installed')) {
        install_blocktype_categories_for_plugin($pluginname);
587
        install_blocktype_viewtypes_for_plugin($pluginname);
588
589
    }

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

593
594
    db_commit();
    return true;
595
596
597
}

function core_postinst() {
598
    // Attempt to create session directories
599
    $sessionpath = get_config('sessionpath');
600
    $status = Session::create_directory_levels($sessionpath);
601

602
    $now = db_format_timestamp(time());
603
604
605
    // Set default search plugin
    set_config('searchplugin', 'internal');

606
    set_config('lang', 'en.utf8');
607
    set_config('installation_key', get_random_key());
608
    set_config('installation_time', $now);
609
    set_config('stats_installation_time', $now);
610

611
612
613
614
615
616
617
    // Pre-define SMTP settings
    set_config('smtphosts', '');
    set_config('smtpport', '');
    set_config('smtpuser', '');
    set_config('smtppass', '');
    set_config('smtpsecure', '');

618
619
620
621
622
623
624
625
626
    // 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);

627
628
629
630
631
    $table = new XMLDBTable('institution');
    $key = new XMLDBKey('logofk');
    $key->setAttributes(XMLDB_KEY_FOREIGN, array('logo'), 'artefact', array('id'));
    add_key($table, $key);

632
    // PostgreSQL supports indexes over functions of columns, MySQL does not.
633
    // We make use if this if we can
634
    if (is_postgres()) {
635
        // Improve the username index
636
637
        execute_sql('DROP INDEX {usr_use_uix}');
        execute_sql('CREATE UNIQUE INDEX {usr_use_uix} ON {usr}(LOWER(username))');
638

639
640
641
642
643
644
645
646
647
        // 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))');

648
649
        // Only one profile view per user
        execute_sql("CREATE UNIQUE INDEX {view_own_type_uix} ON {view}(owner) WHERE type = 'profile'");
650
651
    }

652
653
654
655
656
657
658
659
660
661
662
    // 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)
    )');
663
664
665
666
    execute_sql('ALTER TABLE {artefact} ADD CHECK (
        (author IS NOT NULL AND authorname IS NULL    ) OR
        (author IS NULL     AND authorname IS NOT NULL)
    )');
667
    execute_sql('ALTER TABLE {view_access} ADD CHECK (
668
669
670
671
672
        (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)
673
    )');
674
675
676
677
678
    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)
    )');
679

680
    set_antispam_defaults();
681
    reload_html_filters();
682
683

    // Default set of sites from which iframe content can be embedded
684
685
    // See also the postinst() function in plugins for other valid iframes
    // by searching for 'iframe_source_icon'
686
687
688
689
690
691
692
693
694
    $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',
695
        'voki.com/php/'                            => 'Voki',
696
697
698
699
700
701
702
    );
    $iframedomains = array(
        'YouTube'      => 'www.youtube.com',
        'Vimeo'        => 'vimeo.com',
        'SlideShare'   => 'www.slideshare.net',
        'Glogster'     => 'www.glogster.com',
        'WikiEducator' => 'wikieducator.org',
703
        'Voki'         => 'voki.com',
704
705
706
    );
    update_safe_iframes($iframesources, $iframedomains);

707
708
709
    require_once(get_config('docroot') . 'lib/file.php');
    update_magicdb_path();

710
    return $status;
711
712
}

713
function core_install_lastcoredata_defaults() {
714
    global $USER;
715
    db_begin();
716
    $institution = new StdClass;
717
718
719
    $institution->name = 'mahara';
    $institution->displayname = 'No Institution';
    $institution->authplugin  = 'internal';
720
    $institution->theme  = 'default';
721
    $institution->priority = 0;
722
    insert_record('institution', $institution);
723

724
    $auth_instance = new StdClass;
725
    $auth_instance->instancename  = 'Internal';
726
    $auth_instance->priority='1';
727
728
    $auth_instance->institution   = 'mahara';
    $auth_instance->authname      = 'internal';
729
    $auth_instance->active        = 1;
730
731
    $auth_instance->id = insert_record('auth_instance', $auth_instance, 'id', true);

732
    // Insert the root user
733
    $userid = 0;
734
    $user = new StdClass;
735
    $user->id = $userid;
736
737
738
739
740
741
    $user->username = 'root';
    $user->password = '*';
    $user->salt = '*';
    $user->firstname = 'System';
    $user->lastname = 'User';
    $user->email = 'root@example.org';
742
    $user->quota = get_config_plugin('artefact', 'file', 'defaultquota');
743
    $user->authinstance = $auth_instance->id;
744
    $user->admin = 1;
745

746
747
    if (is_mysql()) { // gratuitous mysql workaround
        $newid = insert_record('usr', $user, 'id', true);
748
        set_field('usr', 'id', 0, 'id', $newid);
749
        execute_sql('ALTER TABLE {usr} AUTO_INCREMENT=1');
750
    }
751
752
753
    else {
        insert_record('usr', $user);
    }
754

755
756
757
758
759
    $pages = site_content_pages();
    $now = db_format_timestamp(time());
    foreach ($pages as $name) {
        $page = new stdClass();
        $page->ctime = $now;
760
761
762
        $page->institution = 'mahara';
        $page->content = get_string($name . 'defaultcontent', 'install', get_string('staticpageconfigdefault', 'install'));
        $page->name = $name;
763
764
765
766
767
        $page->mtime = $now;
        $page->mauthor = $userid;
        insert_record('site_content', $page);
    }

768
769
770
771
772
773
774
775
776
777
778
779
    $versionedpages = site_content_version_pages();
    foreach ($versionedpages as $name) {
        $page = new stdClass();
        $page->ctime = $now;
        $page->institution = 'mahara';
        $page->content = get_string($name . 'defaultcontent', 'install', get_string('staticpageconfigdefault', 'install'));
        $page->type = $name;
        $page->author = $userid;
        $page->version = '1.0';
        insert_record('site_content_version', $page);
    }

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

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

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

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

796
797
    install_auth_default();

798
799
800
801
    // Remove admin privs from root user as it doesn't need it now
    $user->admin = 0;
    update_record('usr', $user, array('id' => 0));

802
    // Insert the admin user
803
804
    $user = new StdClass;
    $user->username = 'admin';
805
    $user->salt = auth_get_random_salt();
806
    $user->password = crypt('mahara', '$2a$' . get_config('bcrypt_cost') . '$' . substr(md5(get_config('passwordsaltmain') . $user->salt), 0, 22));
807
    $user->password = substr($user->password, 0, 7) . substr($user->password, 7+22);
808
    $user->authinstance = $auth_instance->id;
809
810
811
812
813
    $user->passwordchange = 1;
    $user->admin = 1;
    $user->firstname = 'Admin';
    $user->lastname = 'User';
    $user->email = 'admin@example.org';
814
    $user->quota = get_config_plugin('artefact', 'file', 'defaultquota');
815
    $user->ctime = db_format_timestamp(time());
816
817
818
819
    $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);
820
    handle_event('createuser', $user, array('password'));
821
    activity_add_admin_defaults(array($user->id));
822
    db_commit();
823
824

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

830
831
832
function core_install_firstcoredata_defaults() {
    // Install the default institution
    db_begin();
Elliot Pahl's avatar
Elliot Pahl committed
833

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

    // 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',
887
        'userjoinsgroup',
888
        'userleavesgroup',
889
890
        'saveartefact',
        'deleteartefact',
891
        'deleteartefacts',
892
893
        'saveview',
        'deleteview',
894
        'blockinstancecommit',
895
        'deleteblockinstance',
896
897
898
899
        'addfriend',
        'removefriend',
        'addfriendrequest',
        'removefriendrequest',
Richard Mansfield's avatar
Richard Mansfield committed
900
        'creategroup',
901
        'loginas',
902
        'clearcaches',
903
904
905
906
907
908
        'createview',
        'createcollection',
        'updatecollection',
        'deletecollection',
        'addsubmission',
        'releasesubmission',
909
910
        'updateviewaccess',
        'sharedcommenttogroup'
911
912
913
914
915
916
917
918
    );

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

919
920
921
922
923
    // install the core event subscriptions
    $subs = array(
        array(
            'event'        => 'createuser',
            'callfunction' => 'activity_set_defaults',
924
        ),
925
926
927
928
        array(
            'event'        => 'createuser',
            'callfunction' => 'add_user_to_autoadd_groups',
        ),
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
        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',
        ),
945
946
947
948
949
    );

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

951
    // Install the activity types. Name, admin, delay, allownonemethod, defaultmethod.
952
    $activitytypes = array(
953
954
955
956
957
958
959
960
961
962
        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'),
963
964
965
966
967
968
969
    );

    foreach ($activitytypes as $at) {
        $a = new StdClass;
        $a->name = $at[0];
        $a->admin = $at[1];
        $a->delay = $at[2];
970
        $a->allownonemethod = $at[3];
971
        $a->defaultmethod = $at[4];
972
973
974
975
976
        insert_record('activity_type', $a);
    }

    // install the cronjobs...
    $cronjobs = array(
977
        'auth_clean_partial_registrations'          => array('5', '0', '*', '*', '*'),
978
        'auth_clean_expired_password_requests'      => array('5', '0', '*', '*', '*'),
979
980
981
982
983
984
        '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', '*', '*', '*', '*'),
985
986
        'export_process_queue'                      => array('*/6', '*', '*', '*', '*'),
        'submissions_delete_removed_archive'        => array('15', '1', '1', '*', '*'),
987
988
989
990
991
992
993
        '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, '*', '*', '*'),
994
        'cron_sitemap_daily'                        => array(0, 1, '*', '*', '*'),
995
        'file_cleanup_old_cached_files'             => array(0, 1, '*', '*', '*'),
996
        'user_login_tries_to_zero'                  => array('*/5', '*', '*', '*', '*'),
997
        'cron_institution_registration_data'        => array(rand(0, 59), rand(0, 23), '*', '*', rand(0, 6)),
998
999
        'cron_institution_data_weekly'              => array('55', '23', '*', '*', '6'),
        'cron_institution_data_daily'               => array('51', '23', '*', '*', '*'),
1000
        'check_imap_for_bounces'                    => array('*', '*', '*', '*', '*'),
1001
        'cron_event_log_expire'                     => array('7', '23', '*', '*', '*'),
1002
        'watchlist_process_notifications'           => array('*', '*', '*', '*', '*'),
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
    );
    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
1014

1015
    $viewtypes = array('dashboard', 'portfolio', 'profile', 'grouphomepage');
1016
1017
1018
1019
1020
    foreach ($viewtypes as $vt) {
        insert_record('view_type', (object)array(
            'type' => $vt,
        ));
    }
1021
1022
1023
1024
    db_commit();
}


Elliot Pahl's avatar
Elliot Pahl committed
1025
1026
1027
/**
 * xmldb will pass us the xml file and we can perform any substitution as necessary
 */
1028
function local_xmldb_contents_sub(&$contents) {
1029
    // the main install.xml file needs to sub in plugintype tables.
1030
1031
1032
1033
1034
1035
1036
1037
1038
    $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
1039
        // any that want their own stuff can put it in docroot/plugintype/lib/db/plugintables.xml
1040
        //- like auth is a bit special
1041
1042
1043
        $specialcase = get_config('docroot') . $plugin . '/plugintables.xml';
        if (is_readable($specialcase)) {
            $tosub .= file_get_contents($specialcase) . "\n";
Elliot Pahl's avatar
Elliot Pahl committed
1044
        }
1045
        else {
1046
1047
1048
1049
1050
1051
1052
1053
            $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
1054
1055
            require_once(get_config('docroot') . $plugin . '/lib.php');
            if (method_exists(generate_class_name($plugin), 'extra_xmldb_substitution')) {
1056
                $replaced  .= call_static_method(generate_class_name($plugin), 'extra_xmldb_substitution', $plugintables);
1057
            }