upgrade.php 60.8 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
539
     // install artefact types
    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
546
547
548
549
550
551
552
553
554
555
556
557
558
559
        $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
560

561
562
563
    // install blocktype categories.
    if ($plugintype == 'blocktype' && get_config('installed')) {
        install_blocktype_categories_for_plugin($pluginname);
564
        install_blocktype_viewtypes_for_plugin($pluginname);
565
566
    }

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

570
571
    db_commit();
    return true;
572
573
574
575
}

function core_postinst() {
    $status = true;
576
577

    // Attempt to create session directories
578
    $sessionpath = get_config('sessionpath');
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
    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;
    }
608

609
    $now = db_format_timestamp(time());
610
611
612
    // Set default search plugin
    set_config('searchplugin', 'internal');

613
    set_config('lang', 'en.utf8');
614
    set_config('installation_key', get_random_key());
615
    set_config('installation_time', $now);
616
    set_config('stats_installation_time', $now);
617

618
619
620
621
622
623
624
    // Pre-define SMTP settings
    set_config('smtphosts', '');
    set_config('smtpport', '');
    set_config('smtpuser', '');
    set_config('smtppass', '');
    set_config('smtpsecure', '');

625
626
627
628
629
630
631
632
633
    // 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);

634
635
636
637
638
    $table = new XMLDBTable('institution');
    $key = new XMLDBKey('logofk');
    $key->setAttributes(XMLDB_KEY_FOREIGN, array('logo'), 'artefact', array('id'));
    add_key($table, $key);

639
    // PostgreSQL supports indexes over functions of columns, MySQL does not.
640
    // We make use if this if we can
641
    if (is_postgres()) {
642
        // Improve the username index
643
644
        execute_sql('DROP INDEX {usr_use_uix}');
        execute_sql('CREATE UNIQUE INDEX {usr_use_uix} ON {usr}(LOWER(username))');
645

646
647
648
649
650
651
652
653
654
        // 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))');

655
656
        // Only one profile view per user
        execute_sql("CREATE UNIQUE INDEX {view_own_type_uix} ON {view}(owner) WHERE type = 'profile'");
657
658
    }

659
660
661
662
663
664
665
666
667
668
669
    // 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)
    )');
670
671
672
673
    execute_sql('ALTER TABLE {artefact} ADD CHECK (
        (author IS NOT NULL AND authorname IS NULL    ) OR
        (author IS NULL     AND authorname IS NOT NULL)
    )');
674
    execute_sql('ALTER TABLE {view_access} ADD CHECK (
675
676
677
678
679
        (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)
680
    )');
681
682
683
684
685
    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)
    )');
686

687
    set_antispam_defaults();
688
    reload_html_filters();
689
690
691
692
693
694
695
696
697
698
699

    // 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',
700
        'voki.com/php/'                            => 'Voki',
701
702
703
704
705
706
707
    );
    $iframedomains = array(
        'YouTube'      => 'www.youtube.com',
        'Vimeo'        => 'vimeo.com',
        'SlideShare'   => 'www.slideshare.net',
        'Glogster'     => 'www.glogster.com',
        'WikiEducator' => 'wikieducator.org',
708
        'Voki'         => 'voki.com',
709
710
711
    );
    update_safe_iframes($iframesources, $iframedomains);

712
713
714
    require_once(get_config('docroot') . 'lib/file.php');
    update_magicdb_path();

715
    return $status;
716
717
}

718
function core_install_lastcoredata_defaults() {
719
    global $USER;
720
    db_begin();
721
    $institution = new StdClass;
722
723
724
    $institution->name = 'mahara';
    $institution->displayname = 'No Institution';
    $institution->authplugin  = 'internal';
725
    $institution->theme  = 'default';
726
    $institution->priority = 0;
727
    insert_record('institution', $institution);
728

729
730
731
732
733
734
735
    $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;
736
        $page->content = get_string($page->name . 'defaultcontent', 'install', get_string('staticpageconfigdefault', 'install'));
737
738
739
740
        $page->institution = 'mahara';
        insert_record('site_content', $page);
    }

741
    $auth_instance = new StdClass;
742
    $auth_instance->instancename  = 'Internal';
743
    $auth_instance->priority='1';
744
745
    $auth_instance->institution   = 'mahara';
    $auth_instance->authname      = 'internal';
746
    $auth_instance->active        = 1;
747
748
    $auth_instance->id = insert_record('auth_instance', $auth_instance, 'id', true);

749
750
751
752
753
754
755
756
757
    // 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';
758
    $user->quota = get_config_plugin('artefact', 'file', 'defaultquota');
759
    $user->authinstance = $auth_instance->id;
760
    $user->admin = 1;
761

762
763
    if (is_mysql()) { // gratuitous mysql workaround
        $newid = insert_record('usr', $user, 'id', true);
764
        set_field('usr', 'id', 0, 'id', $newid);
765
        execute_sql('ALTER TABLE {usr} AUTO_INCREMENT=1');
766
    }
767
768
769
    else {
        insert_record('usr', $user);
    }
770

771
772
773
    // install the default layout options
    install_view_layout_defaults();

774
    require_once('group.php');
775
    install_system_profile_view();
776
    install_system_dashboard_view();
777
    install_system_grouphomepage_view();
778
779
    require_once('view.php');
    install_system_portfolio_view();
780

781
782
783
    require_once('license.php');
    install_licenses_default();

784
785
786
    require_once('skin.php');
    install_skins_default();

787
788
789
790
    // Remove admin privs from root user as it doesn't need it now
    $user->admin = 0;
    update_record('usr', $user, array('id' => 0));

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

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

818
819
820
function core_install_firstcoredata_defaults() {
    // Install the default institution
    db_begin();
Elliot Pahl's avatar
Elliot Pahl committed
821

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

    // 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',
875
        'userjoinsgroup',
876
877
        'saveartefact',
        'deleteartefact',
878
        'deleteartefacts',
879
880
        'saveview',
        'deleteview',
881
        'blockinstancecommit',
882
        'deleteblockinstance',
883
884
885
886
        'addfriend',
        'removefriend',
        'addfriendrequest',
        'removefriendrequest',
Richard Mansfield's avatar
Richard Mansfield committed
887
        'creategroup',
888
        'loginas',
889
        'clearcaches',
890
891
892
893
894
895
896
897
    );

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

898
899
900
901
902
    // install the core event subscriptions
    $subs = array(
        array(
            'event'        => 'createuser',
            'callfunction' => 'activity_set_defaults',
903
        ),
904
905
906
907
        array(
            'event'        => 'createuser',
            'callfunction' => 'add_user_to_autoadd_groups',
        ),
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
        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',
        ),
924
925
926
927
928
    );

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

930
    // Install the activity types. Name, admin, delay, allownonemethod, defaultmethod.
931
    $activitytypes = array(
932
933
934
935
936
937
938
939
940
941
        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'),
942
943
944
945
946
947
948
    );

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

    // install the cronjobs...
    $cronjobs = array(
956
        'auth_clean_partial_registrations'          => array('5', '0', '*', '*', '*'),
957
        'auth_clean_expired_password_requests'      => array('5', '0', '*', '*', '*'),
958
959
960
961
962
963
        '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', '*', '*', '*', '*'),
964
965
        'export_process_queue'                      => array('*/6', '*', '*', '*', '*'),
        'submissions_delete_removed_archive'        => array('15', '1', '1', '*', '*'),
966
967
968
969
970
971
972
        '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, '*', '*', '*'),
973
        'cron_sitemap_daily'                        => array(0, 1, '*', '*', '*'),
974
        'file_cleanup_old_cached_files'             => array(0, 1, '*', '*', '*'),
975
        'user_login_tries_to_zero'                  => array('*/5', '*', '*', '*', '*'),
976
        'cron_institution_registration_data'        => array(rand(0, 59), rand(0, 23), '*', '*', rand(0, 6)),
977
978
        'cron_institution_data_weekly'              => array('55', '23', '*', '*', '6'),
        'cron_institution_data_daily'               => array('51', '23', '*', '*', '*'),
979
        'check_imap_for_bounces'                    => array('*', '*', '*', '*', '*'),
980
        'cron_event_log_expire'                     => array('7', '23', '*', '*', '*'),
981
        'watchlist_process_notifications'           => array('*', '*', '*', '*', '*'),
982
983
984
985
986
987
988
989
990
991
992
    );
    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
993

994
    $viewtypes = array('dashboard', 'portfolio', 'profile', 'grouphomepage');
995
996
997
998
999
    foreach ($viewtypes as $vt) {
        insert_record('view_type', (object)array(
            'type' => $vt,
        ));
    }
1000
1001
1002
1003
    db_commit();
}


Elliot Pahl's avatar
Elliot Pahl committed
1004
1005
1006
/**
 * xmldb will pass us the xml file and we can perform any substitution as necessary
 */
1007
function local_xmldb_contents_sub(&$contents) {
1008
    // the main install.xml file needs to sub in plugintype tables.
1009
1010
1011
1012
1013
1014
1015
1016
1017
    $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
1018
        // any that want their own stuff can put it in docroot/plugintype/lib/db/plugintables.xml
1019
        //- like auth is a bit special
1020
1021
1022
        $specialcase = get_config('docroot') . $plugin . '/plugintables.xml';
        if (is_readable($specialcase)) {
            $tosub .= file_get_contents($specialcase) . "\n";
Elliot Pahl's avatar
Elliot Pahl committed
1023
        }
1024
        else {
1025
1026
1027
1028
1029
1030
1031
1032
            $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
1033
1034
            require_once(get_config('docroot') . $plugin . '/lib.php');
            if (method_exists(generate_class_name($plugin), 'extra_xmldb_substitution')) {
1035
                $replaced  .= call_static_method(generate_class_name($plugin), 'extra_xmldb_substitution', $plugintables);
1036
            }
1037
            else {
1038
                $replaced .= $plugintables;
1039
            }
1040
            $tosub .= str_replace('__PLUGINTYPE__', $plugin, $replaced) . "\n";
1041
            // look for any tables to put at the end..
1042
1043
1044
            $extratables = get_config('docroot') . $plugin . '/extratables.xml';
            if (is_readable($extratables)) {
                $tosub .= file_get_contents($extratables) . "\n";
1045
            }
1046
1047
        }
    }
1048
    $contents = str_replace($searchstring, $tosub, $contents);
1049
1050
}

1051
1052
1053
1054
1055
1056
1057
1058

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

    if (empty($pluginpath)) {