Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Upgrade helper functions
19
 *
20
 * This file is used for special upgrade functions - for example groups and gradebook.
21
 * These functions must use SQL and database related functions only- no other Moodle API,
22
 * because it might depend on db structures that are not yet present during upgrade.
23
 * (Do not use functions from accesslib.php, grades classes or group functions at all!)
24
 *
25
 * @package   core_install
26
 * @category  upgrade
27
 * @copyright 2007 Petr Skoda (http://skodak.org)
28
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29
 */
30
 
31
defined('MOODLE_INTERNAL') || die();
32
 
33
/**
34
 * Returns all non-view and non-temp tables with sane names.
35
 * Prints list of non-supported tables using $OUTPUT->notification()
36
 *
37
 * @return array
38
 */
39
function upgrade_mysql_get_supported_tables() {
40
    global $OUTPUT, $DB;
41
 
42
    $tables = array();
43
    $patprefix = str_replace('_', '\\_', $DB->get_prefix());
44
    $pregprefix = preg_quote($DB->get_prefix(), '/');
45
 
46
    $sql = "SHOW FULL TABLES LIKE '$patprefix%'";
47
    $rs = $DB->get_recordset_sql($sql);
48
    foreach ($rs as $record) {
49
        $record = array_change_key_case((array)$record, CASE_LOWER);
50
        $type = $record['table_type'];
51
        unset($record['table_type']);
52
        $fullname = array_shift($record);
53
 
54
        if ($pregprefix === '') {
55
            $name = $fullname;
56
        } else {
57
            $count = null;
58
            $name = preg_replace("/^$pregprefix/", '', $fullname, -1, $count);
59
            if ($count !== 1) {
60
                continue;
61
            }
62
        }
63
 
64
        if (!preg_match("/^[a-z][a-z0-9_]*$/", $name)) {
65
            echo $OUTPUT->notification("Database table with invalid name '$fullname' detected, skipping.", 'notifyproblem');
66
            continue;
67
        }
68
        if ($type === 'VIEW') {
69
            echo $OUTPUT->notification("Unsupported database table view '$fullname' detected, skipping.", 'notifyproblem');
70
            continue;
71
        }
72
        $tables[$name] = $name;
73
    }
74
    $rs->close();
75
 
76
    return $tables;
77
}
78
 
79
/**
80
 * Using data for a single course-module that has groupmembersonly enabled,
81
 * returns the new availability value that incorporates the correct
82
 * groupmembersonly option.
83
 *
84
 * Included as a function so that it can be shared between upgrade and restore,
85
 * and unit-tested.
86
 *
87
 * @param int $groupingid Grouping id for the course-module (0 if none)
88
 * @param string $availability Availability JSON data for the module (null if none)
89
 * @return string New value for availability for the module
90
 */
91
function upgrade_group_members_only($groupingid, $availability) {
92
    // Work out the new JSON object representing this option.
93
    if ($groupingid) {
94
        // Require specific grouping.
95
        $condition = (object)array('type' => 'grouping', 'id' => (int)$groupingid);
96
    } else {
97
        // No grouping specified, so require membership of any group.
98
        $condition = (object)array('type' => 'group');
99
    }
100
 
101
    if (is_null($availability)) {
102
        // If there are no conditions using the new API then just set it.
103
        $tree = (object)array('op' => '&', 'c' => array($condition), 'showc' => array(false));
104
    } else {
105
        // There are existing conditions.
106
        $tree = json_decode($availability);
107
        switch ($tree->op) {
108
            case '&' :
109
                // For & conditions we can just add this one.
110
                $tree->c[] = $condition;
111
                $tree->showc[] = false;
112
                break;
113
            case '!|' :
114
                // For 'not or' conditions we can add this one
115
                // but negated.
116
                $tree->c[] = (object)array('op' => '!&', 'c' => array($condition));
117
                $tree->showc[] = false;
118
                break;
119
            default:
120
                // For the other two (OR and NOT AND) we have to add
121
                // an extra level to the tree.
122
                $tree = (object)array('op' => '&', 'c' => array($tree, $condition),
123
                        'showc' => array($tree->show, false));
124
                // Inner trees do not have a show option, so remove it.
125
                unset($tree->c[0]->show);
126
                break;
127
        }
128
    }
129
 
130
    return json_encode($tree);
131
}
132
 
133
/**
134
 * Marks all courses with changes in extra credit weight calculation
135
 *
136
 * Used during upgrade and in course restore process
137
 *
138
 * This upgrade script is needed because we changed the algorithm for calculating the automatic weights of extra
139
 * credit items and want to prevent changes in the existing student grades.
140
 *
141
 * @param int $onlycourseid
142
 */
143
function upgrade_extra_credit_weightoverride($onlycourseid = 0) {
144
    global $DB;
145
 
146
    // Find all courses that have categories in Natural aggregation method where there is at least one extra credit
147
    // item and at least one item with overridden weight.
148
    $courses = $DB->get_fieldset_sql(
149
        "SELECT DISTINCT gc.courseid
150
          FROM {grade_categories} gc
151
          INNER JOIN {grade_items} gi ON gc.id = gi.categoryid AND gi.weightoverride = :weightoverriden
152
          INNER JOIN {grade_items} gie ON gc.id = gie.categoryid AND gie.aggregationcoef = :extracredit
153
          WHERE gc.aggregation = :naturalaggmethod" . ($onlycourseid ? " AND gc.courseid = :onlycourseid" : ''),
154
        array('naturalaggmethod' => 13,
155
            'weightoverriden' => 1,
156
            'extracredit' => 1,
157
            'onlycourseid' => $onlycourseid,
158
        )
159
    );
160
    foreach ($courses as $courseid) {
161
        $gradebookfreeze = get_config('core', 'gradebook_calculations_freeze_' . $courseid);
162
        if (!$gradebookfreeze) {
163
            set_config('gradebook_calculations_freeze_' . $courseid, 20150619);
164
        }
165
    }
166
}
167
 
168
/**
169
 * Marks all courses that require calculated grade items be updated.
170
 *
171
 * Used during upgrade and in course restore process.
172
 *
173
 * This upgrade script is needed because the calculated grade items were stuck with a maximum of 100 and could be changed.
174
 * This flags the courses that are affected and the grade book is frozen to retain grade integrity.
175
 *
176
 * @param int $courseid Specify a course ID to run this script on just one course.
177
 */
178
function upgrade_calculated_grade_items($courseid = null) {
179
    global $DB, $CFG;
180
 
181
    $affectedcourses = array();
182
    $possiblecourseids = array();
183
    $params = array();
184
    $singlecoursesql = '';
185
    if (isset($courseid)) {
186
        $singlecoursesql = "AND ns.id = :courseid";
187
        $params['courseid'] = $courseid;
188
    }
189
    $siteminmaxtouse = 1;
190
    if (isset($CFG->grade_minmaxtouse)) {
191
        $siteminmaxtouse = $CFG->grade_minmaxtouse;
192
    }
193
    $courseidsql = "SELECT ns.id
194
                      FROM (
195
                        SELECT c.id, coalesce(" . $DB->sql_compare_text('gs.value') . ", :siteminmax) AS gradevalue
196
                          FROM {course} c
197
                          LEFT JOIN {grade_settings} gs
198
                            ON c.id = gs.courseid
199
                           AND ((gs.name = 'minmaxtouse' AND " . $DB->sql_compare_text('gs.value') . " = '2'))
200
                        ) ns
201
                    WHERE " . $DB->sql_compare_text('ns.gradevalue') . " = '2' $singlecoursesql";
202
    $params['siteminmax'] = $siteminmaxtouse;
203
    $courses = $DB->get_records_sql($courseidsql, $params);
204
    foreach ($courses as $course) {
205
        $possiblecourseids[$course->id] = $course->id;
206
    }
207
 
208
    if (!empty($possiblecourseids)) {
209
        list($sql, $params) = $DB->get_in_or_equal($possiblecourseids);
210
        // A calculated grade item grade min != 0 and grade max != 100 and the course setting is set to
211
        // "Initial min and max grades".
212
        $coursesql = "SELECT DISTINCT courseid
213
                        FROM {grade_items}
214
                       WHERE calculation IS NOT NULL
215
                         AND itemtype = 'manual'
216
                         AND (grademax <> 100 OR grademin <> 0)
217
                         AND courseid $sql";
218
        $affectedcourses = $DB->get_records_sql($coursesql, $params);
219
    }
220
 
221
    // Check for second type of affected courses.
222
    // If we already have the courseid parameter set in the affectedcourses then there is no need to run through this section.
223
    if (!isset($courseid) || !in_array($courseid, $affectedcourses)) {
224
        $singlecoursesql = '';
225
        $params = array();
226
        if (isset($courseid)) {
227
            $singlecoursesql = "AND courseid = :courseid";
228
            $params['courseid'] = $courseid;
229
        }
230
        $nestedsql = "SELECT id
231
                        FROM {grade_items}
232
                       WHERE itemtype = 'category'
233
                         AND calculation IS NOT NULL $singlecoursesql";
234
        $calculatedgradecategories = $DB->get_records_sql($nestedsql, $params);
235
        $categoryids = array();
236
        foreach ($calculatedgradecategories as $key => $gradecategory) {
237
            $categoryids[$key] = $gradecategory->id;
238
        }
239
 
240
        if (!empty($categoryids)) {
241
            list($sql, $params) = $DB->get_in_or_equal($categoryids);
242
            // A category with a calculation where the raw grade min and the raw grade max don't match the grade min and grade max
243
            // for the category.
244
            $coursesql = "SELECT DISTINCT gi.courseid
245
                            FROM {grade_grades} gg, {grade_items} gi
246
                           WHERE gi.id = gg.itemid
247
                             AND (gg.rawgrademax <> gi.grademax OR gg.rawgrademin <> gi.grademin)
248
                             AND gi.id $sql";
249
            $additionalcourses = $DB->get_records_sql($coursesql, $params);
250
            foreach ($additionalcourses as $key => $additionalcourse) {
251
                if (!array_key_exists($key, $affectedcourses)) {
252
                    $affectedcourses[$key] = $additionalcourse;
253
                }
254
            }
255
        }
256
    }
257
 
258
    foreach ($affectedcourses as $affectedcourseid) {
259
        if (isset($CFG->upgrade_calculatedgradeitemsonlyregrade) && !($courseid)) {
260
            $DB->set_field('grade_items', 'needsupdate', 1, array('courseid' => $affectedcourseid->courseid));
261
        } else {
262
            // Check to see if the gradebook freeze is already in affect.
263
            $gradebookfreeze = get_config('core', 'gradebook_calculations_freeze_' . $affectedcourseid->courseid);
264
            if (!$gradebookfreeze) {
265
                set_config('gradebook_calculations_freeze_' . $affectedcourseid->courseid, 20150627);
266
            }
267
        }
268
    }
269
}
270
 
271
/**
272
 * This function creates a default separated/connected scale
273
 * so there's something in the database.  The locations of
274
 * strings and files is a bit odd, but this is because we
275
 * need to maintain backward compatibility with many different
276
 * existing language translations and older sites.
277
 *
278
 * @global object
279
 * @return void
280
 */
281
function make_default_scale() {
282
    global $DB;
283
 
284
    $defaultscale = new stdClass();
285
    $defaultscale->courseid = 0;
286
    $defaultscale->userid = 0;
287
    $defaultscale->name  = get_string('separateandconnected');
288
    $defaultscale->description = get_string('separateandconnectedinfo');
289
    $defaultscale->scale = get_string('postrating1', 'forum').','.
290
                           get_string('postrating2', 'forum').','.
291
                           get_string('postrating3', 'forum');
292
    $defaultscale->timemodified = time();
293
 
294
    $defaultscale->id = $DB->insert_record('scale', $defaultscale);
295
    return $defaultscale;
296
}
297
 
298
 
299
/**
300
 * Create another default scale.
301
 *
302
 * @param int $oldversion
303
 * @return bool always true
304
 */
305
function make_competence_scale() {
306
    global $DB;
307
 
308
    $defaultscale = new stdClass();
309
    $defaultscale->courseid = 0;
310
    $defaultscale->userid = 0;
311
    $defaultscale->name  = get_string('defaultcompetencescale');
312
    $defaultscale->description = get_string('defaultcompetencescaledesc');
313
    $defaultscale->scale = get_string('defaultcompetencescalenotproficient').','.
314
                           get_string('defaultcompetencescaleproficient');
315
    $defaultscale->timemodified = time();
316
 
317
    $defaultscale->id = $DB->insert_record('scale', $defaultscale);
318
    return $defaultscale;
319
}
320
 
321
/**
322
 * Marks all courses that require rounded grade items be updated.
323
 *
324
 * Used during upgrade and in course restore process.
325
 *
326
 * This upgrade script is needed because it has been decided that if a grade is rounded up, and it will changed a letter
327
 * grade or satisfy a course completion grade criteria, then it should be set as so, and the letter will be awarded and or
328
 * the course completion grade will be awarded.
329
 *
330
 * @param int $courseid Specify a course ID to run this script on just one course.
331
 */
332
function upgrade_course_letter_boundary($courseid = null) {
333
    global $DB, $CFG;
334
 
335
    $coursesql = '';
336
    $params = array('contextlevel' => CONTEXT_COURSE);
337
    if (!empty($courseid)) {
338
        $coursesql = 'AND c.id = :courseid';
339
        $params['courseid'] = $courseid;
340
    }
341
 
342
    // Check to see if the system letter boundaries are borked.
343
    $systemcontext = context_system::instance();
344
    $systemneedsfreeze = upgrade_letter_boundary_needs_freeze($systemcontext);
345
 
346
    // Check the setting for showing the letter grade in a column (default is false).
347
    $usergradelettercolumnsetting = 0;
348
    if (isset($CFG->grade_report_user_showlettergrade)) {
349
        $usergradelettercolumnsetting = (int)$CFG->grade_report_user_showlettergrade;
350
    }
351
    $lettercolumnsql = '';
352
    if ($usergradelettercolumnsetting) {
353
        // The system default is to show a column with letters (and the course uses the defaults).
354
        $lettercolumnsql = '(gss.value is NULL OR ' . $DB->sql_compare_text('gss.value') .  ' <> \'0\')';
355
    } else {
356
        // The course displays a column with letters.
357
        $lettercolumnsql = $DB->sql_compare_text('gss.value') .  ' = \'1\'';
358
    }
359
 
360
    // 3, 13, 23, 31, and 32 are the grade display types that incorporate showing letters. See lib/grade/constants/php.
361
    $systemusesletters = (int) (isset($CFG->grade_displaytype) && in_array($CFG->grade_displaytype, array(3, 13, 23, 31, 32)));
362
    $systemletters = $systemusesletters || $usergradelettercolumnsetting;
363
 
364
    $contextselect = context_helper::get_preload_record_columns_sql('ctx');
365
 
366
    if ($systemletters && $systemneedsfreeze) {
367
        // Select courses with no grade setting for display and a grade item that is using the default display,
368
        // but have not altered the course letter boundary configuration. These courses are definitely affected.
369
 
370
        $sql = "SELECT DISTINCT c.id AS courseid
371
                  FROM {course} c
372
                  JOIN {grade_items} gi ON c.id = gi.courseid
373
                  JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
374
             LEFT JOIN {grade_settings} gs ON gs.courseid = c.id AND gs.name = 'displaytype'
375
             LEFT JOIN {grade_settings} gss ON gss.courseid = c.id AND gss.name = 'report_user_showlettergrade'
376
             LEFT JOIN {grade_letters} gl ON gl.contextid = ctx.id
377
                 WHERE gi.display = 0
378
                 AND ((gs.value is NULL)
379
                      AND ($lettercolumnsql))
380
                 AND gl.id is NULL $coursesql";
381
        $affectedcourseids = $DB->get_recordset_sql($sql, $params);
382
        foreach ($affectedcourseids as $courseid) {
383
            set_config('gradebook_calculations_freeze_' . $courseid->courseid, 20160518);
384
        }
385
        $affectedcourseids->close();
386
    }
387
 
388
    // If the system letter boundary is okay proceed to check grade item and course grade display settings.
389
    $sql = "SELECT DISTINCT c.id AS courseid, $contextselect
390
              FROM {course} c
391
              JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
392
              JOIN {grade_items} gi ON c.id = gi.courseid
393
         LEFT JOIN {grade_settings} gs ON c.id = gs.courseid AND gs.name = 'displaytype'
394
         LEFT JOIN {grade_settings} gss ON gss.courseid = c.id AND gss.name = 'report_user_showlettergrade'
395
             WHERE
396
                (
397
                    -- A grade item is using letters
398
                    (gi.display IN (3, 13, 23, 31, 32))
399
                    -- OR the course is using letters
400
                    OR (" . $DB->sql_compare_text('gs.value') . " IN ('3', '13', '23', '31', '32')
401
                        -- OR the course using the system default which is letters
402
                        OR (gs.value IS NULL AND $systemusesletters = 1)
403
                    )
404
                    OR ($lettercolumnsql)
405
                )
406
                -- AND the course matches
407
                $coursesql";
408
 
409
    $potentialcourses = $DB->get_recordset_sql($sql, $params);
410
 
411
    foreach ($potentialcourses as $value) {
412
        $gradebookfreeze = 'gradebook_calculations_freeze_' . $value->courseid;
413
 
414
        // Check also if this course id has already been frozen.
415
        // If we already have this course ID then move on to the next record.
416
        if (!property_exists($CFG, $gradebookfreeze)) {
417
            // Check for 57 letter grade issue.
418
            context_helper::preload_from_record($value);
419
            $coursecontext = context_course::instance($value->courseid);
420
            if (upgrade_letter_boundary_needs_freeze($coursecontext)) {
421
                // We have a course with a possible score standardisation problem. Flag for freeze.
422
                // Flag this course as being frozen.
423
                set_config('gradebook_calculations_freeze_' . $value->courseid, 20160518);
424
            }
425
        }
426
    }
427
    $potentialcourses->close();
428
}
429
 
430
/**
431
 * Checks the letter boundary of the provided context to see if it needs freezing.
432
 * Each letter boundary is tested to see if receiving that boundary number will
433
 * result in achieving the cosponsoring letter.
434
 *
435
 * @param object $context Context object
436
 * @return bool if the letter boundary for this context should be frozen.
437
 */
438
function upgrade_letter_boundary_needs_freeze($context) {
439
    global $DB;
440
 
441
    $contexts = $context->get_parent_context_ids();
442
    array_unshift($contexts, $context->id);
443
 
444
    foreach ($contexts as $ctxid) {
445
 
446
        $letters = $DB->get_records_menu('grade_letters', array('contextid' => $ctxid), 'lowerboundary DESC',
447
                'lowerboundary, letter');
448
 
449
        if (!empty($letters)) {
450
            foreach ($letters as $boundary => $notused) {
451
                $standardisedboundary = upgrade_standardise_score($boundary, 0, 100, 0, 100);
452
                if ($standardisedboundary < $boundary) {
453
                    return true;
454
                }
455
            }
456
            // We found letters but we have no boundary problem.
457
            return false;
458
        }
459
    }
460
    return false;
461
}
462
 
463
/**
464
 * Given a float value situated between a source minimum and a source maximum, converts it to the
465
 * corresponding value situated between a target minimum and a target maximum. Thanks to Darlene
466
 * for the formula :-)
467
 *
468
 * @param float $rawgrade
469
 * @param float $sourcemin
470
 * @param float $sourcemax
471
 * @param float $targetmin
472
 * @param float $targetmax
473
 * @return float Converted value
474
 */
475
function upgrade_standardise_score($rawgrade, $sourcemin, $sourcemax, $targetmin, $targetmax) {
476
    if (is_null($rawgrade)) {
477
        return null;
478
    }
479
 
480
    if ($sourcemax == $sourcemin or $targetmin == $targetmax) {
481
        // Prevent division by 0.
482
        return $targetmax;
483
    }
484
 
485
    $factor = ($rawgrade - $sourcemin) / ($sourcemax - $sourcemin);
486
    $diff = $targetmax - $targetmin;
487
    $standardisedvalue = $factor * $diff + $targetmin;
488
    return $standardisedvalue;
489
}
490
 
491
/**
492
 * Provides a way to check and update a serialized string that uses the deprecated object class.
493
 *
494
 * @param  string $serializeddata Serialized string which may contain the now deprecated object.
495
 * @return array Returns an array where the first variable is a bool with a status of whether the initial data was changed
496
 * or not. The second variable is the said data.
497
 */
498
function upgrade_fix_serialized_objects($serializeddata) {
499
    $updated = false;
500
    if (strpos($serializeddata, ":6:\"object") !== false) {
501
        $serializeddata = str_replace(":6:\"object", ":8:\"stdClass", $serializeddata);
502
        $updated = true;
503
    }
504
    return [$updated, $serializeddata];
505
}
506
 
507
/**
508
 * Deletes file records which have their repository deleted.
509
 *
510
 */
511
function upgrade_delete_orphaned_file_records() {
512
    global $DB;
513
 
514
    $sql = "SELECT f.id, f.contextid, f.component, f.filearea, f.itemid, fr.id AS referencefileid
515
              FROM {files} f
516
              JOIN {files_reference} fr ON f.referencefileid = fr.id
517
         LEFT JOIN {repository_instances} ri ON fr.repositoryid = ri.id
518
             WHERE ri.id IS NULL";
519
 
520
    $deletedfiles = $DB->get_recordset_sql($sql);
521
 
522
    $deletedfileids = array();
523
 
524
    $fs = get_file_storage();
525
    foreach ($deletedfiles as $deletedfile) {
526
        $fs->delete_area_files($deletedfile->contextid, $deletedfile->component, $deletedfile->filearea, $deletedfile->itemid);
527
        $deletedfileids[] = $deletedfile->referencefileid;
528
    }
529
    $deletedfiles->close();
530
 
531
    $DB->delete_records_list('files_reference', 'id', $deletedfileids);
532
}
533
 
534
/**
535
 * Upgrade core licenses shipped with Moodle.
536
 */
537
function upgrade_core_licenses() {
538
    global $CFG, $DB;
539
 
540
    $expectedlicenses = json_decode(file_get_contents($CFG->dirroot . '/lib/licenses.json'))->licenses;
541
    if (!is_array($expectedlicenses)) {
542
        $expectedlicenses = [];
543
    }
544
    $corelicenses = $DB->get_records('license', ['custom' => 0]);
545
 
546
    // Disable core licenses which are no longer current.
547
    $todisable = array_diff(
548
        array_map(fn ($license) => $license->shortname, $corelicenses),
549
        array_map(fn ($license) => $license->shortname, $expectedlicenses),
550
    );
551
 
552
    // Disable any old *core* license that does not exist in the licenses.json file.
553
    if (count($todisable)) {
554
        [$where, $params] = $DB->get_in_or_equal($todisable, SQL_PARAMS_NAMED);
555
        $DB->set_field_select(
556
            'license',
557
            'enabled',
558
            0,
559
            "shortname {$where}",
560
            $params
561
        );
562
    }
563
 
564
    // Add any new licenses.
565
    foreach ($expectedlicenses as $expectedlicense) {
566
        if (!$expectedlicense->enabled) {
567
            // Skip any license which is no longer enabled.
568
            continue;
569
        }
570
        if (!$DB->record_exists('license', ['shortname' => $expectedlicense->shortname])) {
571
            // If the license replaces an older one, check whether this old license was enabled or not.
572
            $isreplacement = false;
573
            foreach (array_reverse($expectedlicense->replaces ?? []) as $item) {
574
                foreach ($corelicenses as $corelicense) {
575
                    if ($corelicense->shortname === $item) {
576
                        $expectedlicense->enabled = $corelicense->enabled;
577
                        // Also, keep the old sort order.
578
                        $expectedlicense->sortorder = $corelicense->sortorder * 100;
579
                        $isreplacement = true;
580
                        break 2;
581
                    }
582
                }
583
            }
584
            if (!isset($CFG->upgraderunning) || during_initial_install() || $isreplacement) {
585
                // Only install missing core licenses if not upgrading or during initial installation.
586
                $DB->insert_record('license', $expectedlicense);
587
            }
588
        }
589
    }
590
 
591
    // Add/renumber sortorder to all licenses.
592
    $licenses = $DB->get_records('license', null, 'sortorder');
593
    $sortorder = 1;
594
    foreach ($licenses as $license) {
595
        $license->sortorder = $sortorder++;
596
        $DB->update_record('license', $license);
597
    }
598
 
599
    // Set the license config values, used by file repository for rendering licenses at front end.
600
    $activelicenses = $DB->get_records_menu('license', ['enabled' => 1], 'id', 'id, shortname');
601
    set_config('licenses', implode(',', $activelicenses));
602
 
603
    $sitedefaultlicense = get_config('', 'sitedefaultlicense');
604
    if (empty($sitedefaultlicense) || !in_array($sitedefaultlicense, $activelicenses)) {
605
        set_config('sitedefaultlicense', reset($activelicenses));
606
    }
607
}
608
 
609
/**
610
 * Detects if the site may need to get the calendar events fixed or no. With optional output.
611
 *
612
 * @param bool $output true if the function must output information, false if not.
613
 * @return bool true if the site needs to run the fixes, false if not.
614
 */
615
function upgrade_calendar_site_status(bool $output = true): bool {
616
    global $DB;
617
 
618
    // List of upgrade steps where the bug happened.
619
    $badsteps = [
620
        '3.9.5'   => '2020061504.08',
621
        '3.10.2'  => '2020110901.09',
622
        '3.11dev' => '2021022600.02',
623
        '4.0dev'  => '2021052500.65',
624
    ];
625
 
626
    // List of upgrade steps that ran the fixer.
627
    $fixsteps = [
628
        '3.9.6+'  => '2020061506.05',
629
        '3.10.3+' => '2020110903.05',
630
        '3.11dev' => '2021042100.02',
631
        '4.0dev'  => '2021052500.85',
632
    ];
633
 
634
    $targetsteps = array_merge(array_values($badsteps), array_values( $fixsteps));
635
    list($insql, $inparams) = $DB->get_in_or_equal($targetsteps);
636
    $foundsteps = $DB->get_fieldset_sql("
637
        SELECT DISTINCT version
638
          FROM {upgrade_log}
639
         WHERE plugin = 'core'
640
           AND version " . $insql . "
641
      ORDER BY version", $inparams);
642
 
643
    // Analyse the found steps, to decide if the site needs upgrading or no.
644
    $badfound = false;
645
    $fixfound = false;
646
    foreach ($foundsteps as $foundstep) {
647
        $badfound = $badfound ?: array_search($foundstep, $badsteps, true);
648
        $fixfound = $fixfound ?: array_search($foundstep, $fixsteps, true);
649
    }
650
    $needsfix = $badfound && !$fixfound;
651
 
652
    // Let's output some textual information if required to.
653
    if ($output) {
654
        mtrace("");
655
        if ($badfound) {
656
            mtrace("This site has executed the problematic upgrade step {$badsteps[$badfound]} present in {$badfound}.");
657
        } else {
658
            mtrace("Problematic upgrade steps were NOT found, site should be safe.");
659
        }
660
        if ($fixfound) {
661
            mtrace("This site has executed the fix upgrade step {$fixsteps[$fixfound]} present in {$fixfound}.");
662
        } else {
663
            mtrace("Fix upgrade steps were NOT found.");
664
        }
665
        mtrace("");
666
        if ($needsfix) {
667
            mtrace("This site NEEDS to run the calendar events fix!");
668
            mtrace('');
669
            mtrace("You can use this CLI tool or upgrade to a version of Moodle that includes");
670
            mtrace("the fix and will be executed as part of the normal upgrade procedure.");
671
            mtrace("The following versions or up are known candidates to upgrade to:");
672
            foreach ($fixsteps as $key => $value) {
673
                mtrace("  - {$key}: {$value}");
674
            }
675
            mtrace("");
676
        }
677
    }
678
    return $needsfix;
679
}
680
 
681
/**
682
 * Detects the calendar events needing to be fixed. With optional output.
683
 *
684
 * @param bool $output true if the function must output information, false if not.
685
 * @return stdClass[] an array of event types (as keys) with total and bad counters, plus sql to retrieve them.
686
 */
687
function upgrade_calendar_events_status(bool $output = true): array {
688
    global $DB;
689
 
690
    // Calculate the list of standard (core) activity plugins.
691
    $plugins = core_plugin_manager::standard_plugins_list('mod');
692
    $coremodules = "modulename IN ('" . implode("', '", $plugins) . "')";
693
 
694
    // Some query parts go here.
695
    $brokenevents = "(userid = 0 AND (eventtype <> 'user' OR priority <> 0))"; // From the original bad upgrade step.
696
    $standardevents = "(eventtype IN ('site', 'category', 'course', 'group', 'user') AND subscriptionid IS NULL)";
697
    $subscriptionevents = "(subscriptionid IS NOT NULL)";
698
    $overrideevents = "({$coremodules} AND priority IS NOT NULL)";
699
    $actionevents = "({$coremodules} AND instance > 0 and priority IS NULL)";
700
    $otherevents = "(NOT ({$standardevents} OR {$subscriptionevents} OR {$overrideevents} OR {$actionevents}))";
701
 
702
    // Detailed query template.
703
    $detailstemplate = "
704
        SELECT ##group## AS groupname, COUNT(1) AS count
705
          FROM {event}
706
         WHERE ##groupconditions##
707
      GROUP BY ##group##";
708
 
709
    // Count total and potentially broken events.
710
    $total = $DB->count_records_select('event', '');
711
    $totalbadsql = $brokenevents;
712
    $totalbad = $DB->count_records_select('event', $totalbadsql);
713
 
714
    // Standard events.
715
    $standard = $DB->count_records_select('event', $standardevents);
716
    $standardbadsql = "{$brokenevents} AND {$standardevents}";
717
    $standardbad = $DB->count_records_select('event', $standardbadsql);
718
    $standarddetails = $DB->get_records_sql(
719
        str_replace(
720
            ['##group##', '##groupconditions##'],
721
            ['eventtype', $standardbadsql],
722
            $detailstemplate
723
        )
724
    );
725
    array_walk($standarddetails, function (&$rec) {
726
        $rec = $rec->groupname . ': ' . $rec->count;
727
    });
728
    $standarddetails = $standarddetails ? '(' . implode(', ', $standarddetails) . ')' : '- all good!';
729
 
730
    // Subscription events.
731
    $subscription = $DB->count_records_select('event', $subscriptionevents);
732
    $subscriptionbadsql = "{$brokenevents} AND {$subscriptionevents}";
733
    $subscriptionbad = $DB->count_records_select('event', $subscriptionbadsql);
734
    $subscriptiondetails = $DB->get_records_sql(
735
        str_replace(
736
            ['##group##', '##groupconditions##'],
737
            ['eventtype', $subscriptionbadsql],
738
            $detailstemplate
739
        )
740
    );
741
    array_walk($subscriptiondetails, function (&$rec) {
742
        $rec = $rec->groupname . ': ' . $rec->count;
743
    });
744
    $subscriptiondetails = $subscriptiondetails ? '(' . implode(', ', $subscriptiondetails) . ')' : '- all good!';
745
 
746
    // Override events.
747
    $override = $DB->count_records_select('event', $overrideevents);
748
    $overridebadsql = "{$brokenevents} AND {$overrideevents}";
749
    $overridebad = $DB->count_records_select('event', $overridebadsql);
750
    $overridedetails = $DB->get_records_sql(
751
        str_replace(
752
            ['##group##', '##groupconditions##'],
753
            ['modulename', $overridebadsql],
754
            $detailstemplate
755
        )
756
    );
757
    array_walk($overridedetails, function (&$rec) {
758
        $rec = $rec->groupname . ': ' . $rec->count;
759
    });
760
    $overridedetails = $overridedetails ? '(' . implode(', ', $overridedetails) . ')' : '- all good!';
761
 
762
    // Action events.
763
    $action = $DB->count_records_select('event', $actionevents);
764
    $actionbadsql = "{$brokenevents} AND {$actionevents}";
765
    $actionbad = $DB->count_records_select('event', $actionbadsql);
766
    $actiondetails = $DB->get_records_sql(
767
        str_replace(
768
            ['##group##', '##groupconditions##'],
769
            ['modulename', $actionbadsql],
770
            $detailstemplate
771
        )
772
    );
773
    array_walk($actiondetails, function (&$rec) {
774
        $rec = $rec->groupname . ': ' . $rec->count;
775
    });
776
    $actiondetails = $actiondetails ? '(' . implode(', ', $actiondetails) . ')' : '- all good!';
777
 
778
    // Other events.
779
    $other = $DB->count_records_select('event', $otherevents);
780
    $otherbadsql = "{$brokenevents} AND {$otherevents}";
781
    $otherbad = $DB->count_records_select('event', $otherbadsql);
782
    $otherdetails = $DB->get_records_sql(
783
        str_replace(
784
            ['##group##', '##groupconditions##'],
785
            ['COALESCE(component, modulename)', $otherbadsql],
786
            $detailstemplate
787
        )
788
    );
789
    array_walk($otherdetails, function (&$rec) {
790
        $rec = ($rec->groupname ?: 'unknown') . ': ' . $rec->count;
791
    });
792
    $otherdetails = $otherdetails ? '(' . implode(', ', $otherdetails) . ')' : '- all good!';
793
 
794
    // Let's output some textual information if required to.
795
    if ($output) {
796
        mtrace("");
797
        mtrace("Totals: {$total} / {$totalbad} (total / wrong)");
798
        mtrace("  - standards events: {$standard} / {$standardbad} {$standarddetails}");
799
        mtrace("  - subscription events: {$subscription} / {$subscriptionbad} {$subscriptiondetails}");
800
        mtrace("  - override events: {$override} / {$overridebad} {$overridedetails}");
801
        mtrace("  - action events: {$action} / {$actionbad} {$actiondetails}");
802
        mtrace("  - other events: {$other} / {$otherbad} {$otherdetails}");
803
        mtrace("");
804
    }
805
 
806
    return [
807
        'total' => (object)['count' => $total, 'bad' => $totalbad, 'sql' => $totalbadsql],
808
        'standard' => (object)['count' => $standard, 'bad' => $standardbad, 'sql' => $standardbadsql],
809
        'subscription' => (object)['count' => $subscription, 'bad' => $subscriptionbad, 'sql' => $subscriptionbadsql],
810
        'override' => (object)['count' => $override, 'bad' => $overridebad, 'sql' => $overridebadsql],
811
        'action' => (object)['count' => $action, 'bad' => $actionbad, 'sql' => $actionbadsql],
812
        'other' => (object)['count' => $other, 'bad' => $otherbad, 'sql' => $otherbadsql],
813
    ];
814
}
815
 
816
/**
817
 * Detects the calendar events needing to be fixed. With optional output.
818
 *
819
 * @param stdClass[] an array of event types (as keys) with total and bad counters, plus sql to retrieve them.
820
 * @param bool $output true if the function must output information, false if not.
821
 * @param int $maxseconds Number of seconds the function will run as max, with zero meaning no limit.
822
 * @return bool true if the function has not finished fixing everything, false if it has finished.
823
 */
824
function upgrade_calendar_events_fix_remaining(array $info, bool $output = true, int $maxseconds = 0): bool {
825
    global $DB;
826
 
827
    upgrade_calendar_events_mtrace('', $output);
828
 
829
    // Initial preparations.
830
    $starttime = time();
831
    $endtime = $maxseconds ? ($starttime + $maxseconds) : 0;
832
 
833
    // No bad events, or all bad events are "other" events, finished.
834
    if ($info['total']->bad == 0 || $info['total']->bad == $info['other']->bad) {
835
        return false;
836
    }
837
 
838
    // Let's fix overriden events first (they are the ones performing worse with the missing userid).
839
    if ($info['override']->bad != 0) {
840
        if (upgrade_calendar_override_events_fix($info['override'], $output, $endtime)) {
841
            return true; // Not finished yet.
842
        }
843
    }
844
 
845
    // Let's fix the subscription events (like standard ones, but with the event_subscriptions table).
846
    if ($info['subscription']->bad != 0) {
847
        if (upgrade_calendar_subscription_events_fix($info['subscription'], $output, $endtime)) {
848
            return true; // Not finished yet.
849
        }
850
    }
851
 
852
    // Let's fix the standard events (site, category, course, group).
853
    if ($info['standard']->bad != 0) {
854
        if (upgrade_calendar_standard_events_fix($info['standard'], $output, $endtime)) {
855
            return true; // Not finished yet.
856
        }
857
    }
858
 
859
    // Let's fix the action events (all them are "general" ones, not user-specific in core).
860
    if ($info['action']->bad != 0) {
861
        if (upgrade_calendar_action_events_fix($info['action'], $output, $endtime)) {
862
            return true; // Not finished yet.
863
        }
864
    }
865
 
866
    // Have arrived here, finished!
867
    return false;
868
}
869
 
870
/**
871
 * Wrapper over mtrace() to allow a few more things to be specified.
872
 *
873
 * @param string $string string to output.
874
 * @param bool $output true to perform the output, false to avoid it.
875
 */
876
function upgrade_calendar_events_mtrace(string $string, bool $output): void {
877
    static $cols = 0;
878
 
879
    // No output, do nothing.
880
    if (!$output) {
881
        return;
882
    }
883
 
884
    // Printing dots... let's output them slightly nicer.
885
    if ($string === '.') {
886
        $cols++;
887
        // Up to 60 cols.
888
        if ($cols < 60) {
889
            mtrace($string, '');
890
        } else {
891
            mtrace($string);
892
            $cols = 0;
893
        }
894
        return;
895
    }
896
 
897
    // Reset cols, have ended printing dots.
898
    if ($cols) {
899
        $cols = 0;
900
        mtrace('');
901
    }
902
 
903
    // Normal output.
904
    mtrace($string);
905
}
906
 
907
/**
908
 * Get a valid editing teacher for a given courseid
909
 *
910
 * @param int $courseid The course to look for editing teachers.
911
 * @return int A user id of an editing teacher or, if missing, the admin userid.
912
 */
913
function upgrade_calendar_events_get_teacherid(int $courseid): int {
914
 
915
    if ($context = context_course::instance($courseid, IGNORE_MISSING)) {
916
        if ($havemanage = get_users_by_capability($context, 'moodle/course:manageactivities', 'u.id')) {
917
            return array_keys($havemanage)[0];
918
        }
919
    }
920
    return get_admin()->id; // Could not find a teacher, default to admin.
921
}
922
 
923
/**
924
 * Detects the calendar standard events needing to be fixed. With optional output.
925
 *
926
 * @param stdClass $info an object with total and bad counters, plus sql to retrieve them.
927
 * @param bool $output true if the function must output information, false if not.
928
 * @param int $endtime cutoff time when the process must stop (0 means no cutoff).
929
 * @return bool true if the function has not finished fixing everything, false if it has finished.
930
 */
931
function upgrade_calendar_standard_events_fix(stdClass $info, bool $output = true, int $endtime = 0): bool {
932
    global $DB;
933
 
934
    $return = false; // Let's assume the function is going to finish by default.
935
    $status = "Finished!"; // To decide the message to be presented on return.
936
 
937
    upgrade_calendar_events_mtrace('Processing standard events', $output);
938
 
939
    $rs = $DB->get_recordset_sql("
940
        SELECT DISTINCT eventtype, courseid
941
          FROM {event}
942
         WHERE {$info->sql}");
943
 
944
    foreach ($rs as $record) {
945
        switch ($record->eventtype) {
946
            case 'site':
947
            case 'category':
948
                // These are created by admin.
949
                $DB->set_field('event', 'userid', get_admin()->id, ['eventtype' => $record->eventtype]);
950
                break;
951
            case 'course':
952
            case 'group':
953
                // These are created by course teacher.
954
                $DB->set_field('event', 'userid', upgrade_calendar_events_get_teacherid($record->courseid),
955
                    ['eventtype' => $record->eventtype, 'courseid' => $record->courseid]);
956
                break;
957
        }
958
 
959
        // Cutoff time, let's exit.
960
        if ($endtime && $endtime <= time()) {
961
            $status = 'Remaining standard events pending';
962
            $return = true; // Not finished yet.
963
            break;
964
        }
965
        upgrade_calendar_events_mtrace('.', $output);
966
    }
967
    $rs->close();
968
    upgrade_calendar_events_mtrace($status, $output);
969
    upgrade_calendar_events_mtrace('', $output);
970
    return $return;
971
}
972
 
973
/**
974
 * Detects the calendar subscription events needing to be fixed. With optional output.
975
 *
976
 * @param stdClass $info an object with total and bad counters, plus sql to retrieve them.
977
 * @param bool $output true if the function must output information, false if not.
978
 * @param int $endtime cutoff time when the process must stop (0 means no cutoff).
979
 * @return bool true if the function has not finished fixing everything, false if it has finished.
980
 */
981
function upgrade_calendar_subscription_events_fix(stdClass $info, bool $output = true, int $endtime = 0): bool {
982
    global $DB;
983
 
984
    $return = false; // Let's assume the function is going to finish by default.
985
    $status = "Finished!"; // To decide the message to be presented on return.
986
 
987
    upgrade_calendar_events_mtrace('Processing subscription events', $output);
988
 
989
    $rs = $DB->get_recordset_sql("
990
        SELECT DISTINCT subscriptionid AS id
991
          FROM {event}
992
         WHERE {$info->sql}");
993
 
994
    foreach ($rs as $subscription) {
995
        // Subscriptions can be site or category level, let's put the admin as userid.
996
        // (note that "user" subscription weren't deleted so there is nothing to recover with them.
997
        $DB->set_field('event_subscriptions', 'userid', get_admin()->id, ['id' => $subscription->id]);
998
        $DB->set_field('event', 'userid', get_admin()->id, ['subscriptionid' => $subscription->id]);
999
 
1000
        // Cutoff time, let's exit.
1001
        if ($endtime && $endtime <= time()) {
1002
            $status = 'Remaining subscription events pending';
1003
            $return = true; // Not finished yet.
1004
            break;
1005
        }
1006
        upgrade_calendar_events_mtrace('.', $output);
1007
    }
1008
    $rs->close();
1009
    upgrade_calendar_events_mtrace($status, $output);
1010
    upgrade_calendar_events_mtrace('', $output);
1011
    return $return;
1012
}
1013
 
1014
/**
1015
 * Detects the calendar action events needing to be fixed. With optional output.
1016
 *
1017
 * @param stdClass $info an object with total and bad counters, plus sql to retrieve them.
1018
 * @param bool $output true if the function must output information, false if not.
1019
 * @param int $endtime cutoff time when the process must stop (0 means no cutoff).
1020
 * @return bool true if the function has not finished fixing everything, false if it has finished.
1021
 */
1022
function upgrade_calendar_action_events_fix(stdClass $info, bool $output = true, int $endtime = 0): bool {
1023
    global $DB;
1024
 
1025
    $return = false; // Let's assume the function is going to finish by default.
1026
    $status = "Finished!"; // To decide the message to be presented on return.
1027
 
1028
    upgrade_calendar_events_mtrace('Processing action events', $output);
1029
 
1030
    $rs = $DB->get_recordset_sql("
1031
        SELECT DISTINCT modulename, instance, courseid
1032
          FROM {event}
1033
         WHERE {$info->sql}");
1034
 
1035
    foreach ($rs as $record) {
1036
        // These are created by course teacher.
1037
        $DB->set_field('event', 'userid', upgrade_calendar_events_get_teacherid($record->courseid),
1038
            ['modulename' => $record->modulename, 'instance' => $record->instance, 'courseid' => $record->courseid]);
1039
 
1040
        // Cutoff time, let's exit.
1041
        if ($endtime && $endtime <= time()) {
1042
            $status = 'Remaining action events pending';
1043
            $return = true; // Not finished yet.
1044
            break;
1045
        }
1046
        upgrade_calendar_events_mtrace('.', $output);
1047
    }
1048
    $rs->close();
1049
    upgrade_calendar_events_mtrace($status, $output);
1050
    upgrade_calendar_events_mtrace('', $output);
1051
    return $return;
1052
}
1053
 
1054
/**
1055
 * Detects the calendar override events needing to be fixed. With optional output.
1056
 *
1057
 * @param stdClass $info an object with total and bad counters, plus sql to retrieve them.
1058
 * @param bool $output true if the function must output information, false if not.
1059
 * @param int $endtime cutoff time when the process must stop (0 means no cutoff).
1060
 * @return bool true if the function has not finished fixing everything, false if it has finished.
1061
 */
1062
function upgrade_calendar_override_events_fix(stdClass $info, bool $output = true, int $endtime = 0): bool {
1063
    global $CFG, $DB;
1064
 
1065
    include_once($CFG->dirroot. '/course/lib.php');
1066
    include_once($CFG->dirroot. '/mod/assign/lib.php');
1067
    include_once($CFG->dirroot. '/mod/assign/locallib.php');
1068
    include_once($CFG->dirroot. '/mod/lesson/lib.php');
1069
    include_once($CFG->dirroot. '/mod/lesson/locallib.php');
1070
    include_once($CFG->dirroot. '/mod/quiz/lib.php');
1071
    include_once($CFG->dirroot. '/mod/quiz/locallib.php');
1072
 
1073
    $return = false; // Let's assume the function is going to finish by default.
1074
    $status = "Finished!"; // To decide the message to be presented on return.
1075
 
1076
    upgrade_calendar_events_mtrace('Processing override events', $output);
1077
 
1078
    $rs = $DB->get_recordset_sql("
1079
        SELECT DISTINCT modulename, instance
1080
          FROM {event}
1081
         WHERE {$info->sql}");
1082
 
1083
    foreach ($rs as $module) {
1084
        // Remove all the records from the events table for the module.
1085
        $DB->delete_records('event', ['modulename' => $module->modulename, 'instance' => $module->instance]);
1086
 
1087
        // Get the activity record.
1088
        if (!$activityrecord = $DB->get_record($module->modulename, ['id' => $module->instance])) {
1089
            // Orphaned calendar event (activity doesn't exists), skip.
1090
            continue;
1091
        }
1092
 
1093
        // Let's rebuild it by calling to each module API.
1094
        switch ($module->modulename) {
1095
            case 'assign';
1096
                if (function_exists('assign_prepare_update_events')) {
1097
                    assign_prepare_update_events($activityrecord);
1098
                }
1099
                break;
1100
            case 'lesson':
1101
                if (function_exists('lesson_update_events')) {
1102
                    lesson_update_events($activityrecord);
1103
                }
1104
                break;
1105
            case 'quiz':
1106
                if (function_exists('quiz_update_events')) {
1107
                    quiz_update_events($activityrecord);
1108
                }
1109
                break;
1110
        }
1111
 
1112
        // Sometimes, some (group) overrides are created without userid, when that happens, they deserve
1113
        // some user (teacher or admin). This doesn't affect to groups calendar events behaviour,
1114
        // but allows counters to detect already processed group overrides and makes things
1115
        // consistent.
1116
        $DB->set_field_select('event', 'userid', upgrade_calendar_events_get_teacherid($activityrecord->course),
1117
            'modulename = ? AND instance = ? and priority != 0 and userid = 0',
1118
            ['modulename' => $module->modulename, 'instance' => $module->instance]);
1119
 
1120
        // Cutoff time, let's exit.
1121
        if ($endtime && $endtime <= time()) {
1122
            $status = 'Remaining override events pending';
1123
            $return = true; // Not finished yet.
1124
            break;
1125
        }
1126
        upgrade_calendar_events_mtrace('.', $output);
1127
    }
1128
    $rs->close();
1129
    upgrade_calendar_events_mtrace($status, $output);
1130
    upgrade_calendar_events_mtrace('', $output);
1131
    return $return;
1132
}
1133
 
1134
/**
1135
 * Add a new item at the end of the usermenu.
1136
 *
1137
 * @param string $menuitem
1138
 */
1139
function upgrade_add_item_to_usermenu(string $menuitem): void {
1140
    global $CFG;
1141
 
1142
    // Get current configuration data.
1143
    $currentcustomusermenuitems = str_replace(["\r\n", "\r"], "\n", $CFG->customusermenuitems);
1144
    $lines = preg_split('/\n/', $currentcustomusermenuitems, -1, PREG_SPLIT_NO_EMPTY);
1145
    $lines = array_map('trim', $lines);
1146
 
1147
    if (!in_array($menuitem, $lines)) {
1148
        // Add the item to the menu.
1149
        $lines[] = $menuitem;
1150
        set_config('customusermenuitems', implode("\n", $lines));
1151
    }
1152
}
1153
 
1154
/**
1155
 * Update all instances of a block shown on a pagetype to a new default region, adding missing block instances where
1156
 * none is found.
1157
 *
1158
 * Note: This is intended as a helper to add blocks to all instances of the standard my-page. It will only work where
1159
 * the subpagepattern is a string representation of an integer. If there are any string values this will not work.
1160
 *
1161
 * @param string $blockname The block name, without the block_ frankenstyle component
1162
 * @param string $pagename The type of my-page to match
1163
 * @param string $pagetypepattern The page type pattern to match for the block
1164
 * @param string $newdefaultregion The new region to set
1165
 */
1166
function upgrade_block_set_defaultregion(
1167
    string $blockname,
1168
    string $pagename,
1169
    string $pagetypepattern,
1170
    string $newdefaultregion
1171
): void {
1172
    global $DB;
1173
 
1174
    // The subpagepattern is a string.
1175
    // In all core blocks it contains a string represnetation of an integer, but it is theoretically possible for a
1176
    // community block to do something different.
1177
    // This function is not suited to those cases.
1178
    $subpagepattern = $DB->sql_cast_char2int('bi.subpagepattern');
1179
    $subpageempty = $DB->sql_isnotempty('block_instances', 'bi.subpagepattern', true, false);
1180
 
1181
    // If a subquery returns any NULL then the NOT IN returns no results at all.
1182
    // By adding a join in the inner select on my_pages we remove any possible nulls and prevent any need for
1183
    // additional casting to filter out the nulls.
1184
    $sql = <<<EOF
1185
        INSERT INTO {block_instances} (
1186
            blockname,
1187
            parentcontextid,
1188
            showinsubcontexts,
1189
            pagetypepattern,
1190
            subpagepattern,
1191
            defaultregion,
1192
            defaultweight,
1193
            timecreated,
1194
            timemodified
1195
        ) SELECT
1196
            :selectblockname AS blockname,
1197
            c.id AS parentcontextid,
1198
 
1199
            :selectpagetypepattern AS pagetypepattern,
1200
            mp.id AS subpagepattern,
1201
            :selectdefaultregion AS defaultregion,
1202
 
1203
            :selecttimecreated AS timecreated,
1204
            :selecttimemodified AS timemodified
1205
          FROM {my_pages} mp
1206
          JOIN {context} c ON c.instanceid = mp.userid AND c.contextlevel = :contextuser
1207
         WHERE mp.id NOT IN (
1208
            SELECT mpi.id FROM {my_pages} mpi
1209
              JOIN {block_instances} bi
1210
                    ON bi.blockname = :blockname
1211
                   AND bi.subpagepattern IS NOT NULL AND {$subpageempty}
1212
                   AND bi.pagetypepattern = :pagetypepattern
1213
                   AND {$subpagepattern} = mpi.id
1214
         )
1215
         AND mp.private = 1
1216
         AND mp.name = :pagename
1217
    EOF;
1218
 
1219
    $DB->execute($sql, [
1220
        'selectblockname' => $blockname,
1221
        'contextuser' => CONTEXT_USER,
1222
        'selectpagetypepattern' => $pagetypepattern,
1223
        'selectdefaultregion' => $newdefaultregion,
1224
        'selecttimecreated' => time(),
1225
        'selecttimemodified' => time(),
1226
        'pagetypepattern' => $pagetypepattern,
1227
        'blockname' => $blockname,
1228
        'pagename' => $pagename,
1229
    ]);
1230
 
1231
    // Update the existing instances.
1232
    $sql = <<<EOF
1233
        UPDATE {block_instances}
1234
           SET defaultregion = :newdefaultregion
1235
         WHERE id IN (
1236
            SELECT * FROM (
1237
                SELECT bi.id
1238
                  FROM {my_pages} mp
1239
                  JOIN {block_instances} bi
1240
                        ON bi.blockname = :blockname
1241
                       AND bi.subpagepattern IS NOT NULL AND {$subpageempty}
1242
                       AND bi.pagetypepattern = :pagetypepattern
1243
                       AND {$subpagepattern} = mp.id
1244
                 WHERE mp.private = 1
1245
                   AND mp.name = :pagename
1246
                   AND bi.defaultregion <> :existingnewdefaultregion
1247
            ) bid
1248
         )
1249
    EOF;
1250
 
1251
    $DB->execute($sql, [
1252
        'newdefaultregion' => $newdefaultregion,
1253
        'pagetypepattern' => $pagetypepattern,
1254
        'blockname' => $blockname,
1255
        'existingnewdefaultregion' => $newdefaultregion,
1256
        'pagename' => $pagename,
1257
    ]);
1258
 
1259
    // Note: This can be time consuming!
1260
    \context_helper::create_instances(CONTEXT_BLOCK);
1261
}
1262
 
1263
/**
1264
 * Remove all instances of a block on pages of the specified pagetypepattern.
1265
 *
1266
 * Note: This is intended as a helper to add blocks to all instances of the standard my-page. It will only work where
1267
 * the subpagepattern is a string representation of an integer. If there are any string values this will not work.
1268
 *
1269
 * @param string $blockname The block name, without the block_ frankenstyle component
1270
 * @param string $pagename The type of my-page to match
1271
 * @param string $pagetypepattern This is typically used on the 'my-index'
1272
 */
1273
function upgrade_block_delete_instances(
1274
    string $blockname,
1275
    string $pagename,
1276
    string $pagetypepattern
1277
): void {
1278
    global $DB;
1279
 
1280
    $deleteblockinstances = function (string $instanceselect, array $instanceparams) use ($DB) {
1281
        $deletesql = <<<EOF
1282
            SELECT c.id AS cid
1283
              FROM {context} c
1284
              JOIN {block_instances} bi ON bi.id = c.instanceid AND c.contextlevel = :contextlevel
1285
             WHERE {$instanceselect}
1286
        EOF;
1287
        $DB->delete_records_subquery('context', 'id', 'cid', $deletesql, array_merge($instanceparams, [
1288
            'contextlevel' => CONTEXT_BLOCK,
1289
        ]));
1290
 
1291
        $deletesql = <<<EOF
1292
            SELECT bp.id AS bpid
1293
              FROM {block_positions} bp
1294
              JOIN {block_instances} bi ON bi.id = bp.blockinstanceid
1295
             WHERE {$instanceselect}
1296
        EOF;
1297
        $DB->delete_records_subquery('block_positions', 'id', 'bpid', $deletesql, $instanceparams);
1298
 
1299
        $blockhidden = $DB->sql_concat("'block'", 'bi.id', "'hidden'");
1300
        $blockdocked = $DB->sql_concat("'docked_block_instance_'", 'bi.id');
1301
        $deletesql = <<<EOF
1302
            SELECT p.id AS pid
1303
              FROM {user_preferences} p
1304
              JOIN {block_instances} bi ON p.name IN ({$blockhidden}, {$blockdocked})
1305
             WHERE {$instanceselect}
1306
        EOF;
1307
        $DB->delete_records_subquery('user_preferences', 'id', 'pid', $deletesql, $instanceparams);
1308
 
1309
        $deletesql = <<<EOF
1310
            SELECT bi.id AS bid
1311
              FROM {block_instances} bi
1312
             WHERE {$instanceselect}
1313
        EOF;
1314
        $DB->delete_records_subquery('block_instances', 'id', 'bid', $deletesql, $instanceparams);
1315
    };
1316
 
1317
    // Delete the default indexsys version of the block.
1318
    $subpagepattern = $DB->get_record('my_pages', [
1319
        'userid' => null,
1320
        'name' => $pagename,
1321
        'private' => MY_PAGE_PRIVATE,
1322
    ], 'id', IGNORE_MULTIPLE)->id;
1323
 
1324
    $instanceselect = <<<EOF
1325
            blockname = :blockname
1326
        AND pagetypepattern = :pagetypepattern
1327
        AND subpagepattern = :subpagepattern
1328
    EOF;
1329
 
1330
    $params = [
1331
        'blockname' => $blockname,
1332
        'pagetypepattern' => $pagetypepattern,
1333
        'subpagepattern' => $subpagepattern,
1334
    ];
1335
    $deleteblockinstances($instanceselect, $params);
1336
 
1337
    // The subpagepattern is a string.
1338
    // In all core blocks it contains a string represnetation of an integer, but it is theoretically possible for a
1339
    // community block to do something different.
1340
    // This function is not suited to those cases.
1341
    $subpagepattern = $DB->sql_cast_char2int('bi.subpagepattern');
1342
 
1343
    // Look for any and all instances of the block in customised /my pages.
1344
    $subpageempty = $DB->sql_isnotempty('block_instances', 'bi.subpagepattern', true, false);
1345
    $instanceselect = <<<EOF
1346
         bi.id IN (
1347
            SELECT * FROM (
1348
                SELECT bi.id
1349
                  FROM {my_pages} mp
1350
                  JOIN {block_instances} bi
1351
                        ON bi.blockname = :blockname
1352
                       AND bi.subpagepattern IS NOT NULL AND {$subpageempty}
1353
                       AND bi.pagetypepattern = :pagetypepattern
1354
                       AND {$subpagepattern} = mp.id
1355
                 WHERE mp.private = :private
1356
                   AND mp.name = :pagename
1357
            ) bid
1358
         )
1359
    EOF;
1360
 
1361
    $params = [
1362
        'blockname' => $blockname,
1363
        'pagetypepattern' => $pagetypepattern,
1364
        'pagename' => $pagename,
1365
        'private' => MY_PAGE_PRIVATE,
1366
    ];
1367
 
1368
    $deleteblockinstances($instanceselect, $params);
1369
}
1370
 
1371
/**
1372
 * Update the block instance parentcontext to point to the correct user context id for the specified block on a my page.
1373
 *
1374
 * @param string $blockname
1375
 * @param string $pagename
1376
 * @param string $pagetypepattern
1377
 */
1378
function upgrade_block_set_my_user_parent_context(
1379
    string $blockname,
1380
    string $pagename,
1381
    string $pagetypepattern
1382
): void {
1383
    global $DB;
1384
 
1385
    $subpagepattern = $DB->sql_cast_char2int('bi.subpagepattern');
1386
    // Look for any and all instances of the block in customised /my pages.
1387
    $subpageempty = $DB->sql_isnotempty('block_instances', 'bi.subpagepattern', true, false);
1388
 
1389
    $dbman = $DB->get_manager();
1390
    $temptablename = 'block_instance_context';
1391
    $xmldbtable = new \xmldb_table($temptablename);
1392
    $xmldbtable->add_field('instanceid', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, null);
1393
    $xmldbtable->add_field('contextid', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, null);
1394
    $xmldbtable->add_key('primary', XMLDB_KEY_PRIMARY, ['instanceid']);
1395
    $dbman->create_temp_table($xmldbtable);
1396
 
1397
    $sql = <<<EOF
1398
        INSERT INTO {block_instance_context} (
1399
            instanceid,
1400
            contextid
1401
        ) SELECT
1402
            bi.id as instanceid,
1403
            c.id as contextid
1404
           FROM {my_pages} mp
1405
           JOIN {context} c ON c.instanceid = mp.userid AND c.contextlevel = :contextuser
1406
           JOIN {block_instances} bi
1407
                ON bi.blockname = :blockname
1408
               AND bi.subpagepattern IS NOT NULL AND {$subpageempty}
1409
               AND bi.pagetypepattern = :pagetypepattern
1410
               AND {$subpagepattern} = mp.id
1411
          WHERE mp.name = :pagename AND bi.parentcontextid <> c.id
1412
    EOF;
1413
 
1414
    $DB->execute($sql, [
1415
        'blockname' => $blockname,
1416
        'pagetypepattern' => $pagetypepattern,
1417
        'contextuser' => CONTEXT_USER,
1418
        'pagename' => $pagename,
1419
    ]);
1420
 
1421
    $dbfamily = $DB->get_dbfamily();
1422
    if ($dbfamily === 'mysql') {
1423
        // MariaDB and MySQL.
1424
        $sql = <<<EOF
1425
            UPDATE {block_instances} bi, {block_instance_context} bic
1426
               SET bi.parentcontextid = bic.contextid
1427
             WHERE bi.id = bic.instanceid
1428
        EOF;
1429
    } else if ($dbfamily === 'oracle') {
1430
        $sql = <<<EOF
1431
            UPDATE {block_instances} bi
1432
            SET (bi.parentcontextid) = (
1433
                SELECT bic.contextid
1434
                  FROM {block_instance_context} bic
1435
                 WHERE bic.instanceid = bi.id
1436
            ) WHERE EXISTS (
1437
                SELECT 'x'
1438
                  FROM {block_instance_context} bic
1439
                 WHERE bic.instanceid = bi.id
1440
            )
1441
        EOF;
1442
    } else {
1443
        // Postgres and sqlsrv.
1444
        $sql = <<<EOF
1445
            UPDATE {block_instances}
1446
            SET parentcontextid = bic.contextid
1447
            FROM {block_instance_context} bic
1448
            WHERE {block_instances}.id = bic.instanceid
1449
        EOF;
1450
    }
1451
 
1452
    $DB->execute($sql);
1453
 
1454
    $dbman->drop_table($xmldbtable);
1455
}
1456
 
1457
/**
1458
 * Fix the timestamps for files where their timestamps are older
1459
 * than the directory listing that they are contained in.
1460
 */
1461
function upgrade_fix_file_timestamps() {
1462
    global $DB;
1463
 
1464
    // Due to incompatability in SQL syntax for updates with joins,
1465
    // These will be updated in a select + separate update.
1466
    $sql = "SELECT f.id, f2.timecreated
1467
              FROM {files} f
1468
              JOIN {files} f2
1469
                    ON f2.contextid = f.contextid
1470
                   AND f2.filepath = f.filepath
1471
                   AND f2.component = f.component
1472
                   AND f2.filearea = f.filearea
1473
                   AND f2.itemid = f.itemid
1474
                   AND f2.filename = '.'
1475
             WHERE f2.timecreated > f.timecreated";
1476
 
1477
    $recordset = $DB->get_recordset_sql($sql);
1478
 
1479
    if (!$recordset->valid()) {
1480
        $recordset->close();
1481
        return;
1482
    }
1483
 
1484
    foreach ($recordset as $record) {
1485
        $record->timemodified = $record->timecreated;
1486
        $DB->update_record('files', $record);
1487
    }
1488
 
1489
    $recordset->close();
1490
}
1491
 
1492
/**
1493
 * Upgrade helper to add foreign keys and indexes for MDL-49795
1494
 */
1495
function upgrade_add_foreign_key_and_indexes() {
1496
    global $DB;
1497
 
1498
    $dbman = $DB->get_manager();
1499
    // Define key originalcourseid (foreign) to be added to course.
1500
    $table = new xmldb_table('course');
1501
    $key = new xmldb_key('originalcourseid', XMLDB_KEY_FOREIGN, ['originalcourseid'], 'course', ['id']);
1502
    // Launch add key originalcourseid.
1503
    $dbman->add_key($table, $key);
1504
 
1505
    // Define key roleid (foreign) to be added to enrol.
1506
    $table = new xmldb_table('enrol');
1507
    $key = new xmldb_key('roleid', XMLDB_KEY_FOREIGN, ['roleid'], 'role', ['id']);
1508
    // Launch add key roleid.
1509
    $dbman->add_key($table, $key);
1510
 
1511
    // Define key userid (foreign) to be added to scale.
1512
    $table = new xmldb_table('scale');
1513
    $key = new xmldb_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
1514
    // Launch add key userid.
1515
    $dbman->add_key($table, $key);
1516
 
1517
    // Define key userid (foreign) to be added to scale_history.
1518
    $table = new xmldb_table('scale_history');
1519
    $key = new xmldb_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
1520
    // Launch add key userid.
1521
    $dbman->add_key($table, $key);
1522
 
1523
    // Define key courseid (foreign) to be added to post.
1524
    $table = new xmldb_table('post');
1525
    $key = new xmldb_key('courseid', XMLDB_KEY_FOREIGN, ['courseid'], 'course', ['id']);
1526
    // Launch add key courseid.
1527
    $dbman->add_key($table, $key);
1528
 
1529
    // Define key coursemoduleid (foreign) to be added to post.
1530
    $table = new xmldb_table('post');
1531
    $key = new xmldb_key('coursemoduleid', XMLDB_KEY_FOREIGN, ['coursemoduleid'], 'course_modules', ['id']);
1532
    // Launch add key coursemoduleid.
1533
    $dbman->add_key($table, $key);
1534
 
1535
    // Define key questionid (foreign) to be added to question_statistics.
1536
    $table = new xmldb_table('question_statistics');
1537
    $key = new xmldb_key('questionid', XMLDB_KEY_FOREIGN, ['questionid'], 'question', ['id']);
1538
    // Launch add key questionid.
1539
    $dbman->add_key($table, $key);
1540
 
1541
    // Define key questionid (foreign) to be added to question_response_analysis.
1542
    $table = new xmldb_table('question_response_analysis');
1543
    $key = new xmldb_key('questionid', XMLDB_KEY_FOREIGN, ['questionid'], 'question', ['id']);
1544
    // Launch add key questionid.
1545
    $dbman->add_key($table, $key);
1546
 
1547
    // Define index last_log_id (not unique) to be added to mnet_host.
1548
    $table = new xmldb_table('mnet_host');
1549
    $index = new xmldb_index('last_log_id', XMLDB_INDEX_NOTUNIQUE, ['last_log_id']);
1550
    // Conditionally launch add index last_log_id.
1551
    if (!$dbman->index_exists($table, $index)) {
1552
        $dbman->add_index($table, $index);
1553
    }
1554
 
1555
    // Define key userid (foreign) to be added to mnet_session.
1556
    $table = new xmldb_table('mnet_session');
1557
    $key = new xmldb_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
1558
    // Launch add key userid.
1559
    $dbman->add_key($table, $key);
1560
 
1561
    // Define key mnethostid (foreign) to be added to mnet_session.
1562
    $table = new xmldb_table('mnet_session');
1563
    $key = new xmldb_key('mnethostid', XMLDB_KEY_FOREIGN, ['mnethostid'], 'mnet_host', ['id']);
1564
    // Launch add key mnethostid.
1565
    $dbman->add_key($table, $key);
1566
 
1567
    // Define key userid (foreign) to be added to grade_import_values.
1568
    $table = new xmldb_table('grade_import_values');
1569
    $key = new xmldb_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
1570
    // Launch add key userid.
1571
    $dbman->add_key($table, $key);
1572
 
1573
    // Define key tempdataid (foreign) to be added to portfolio_log.
1574
    $table = new xmldb_table('portfolio_log');
1575
    $key = new xmldb_key('tempdataid', XMLDB_KEY_FOREIGN, ['tempdataid'], 'portfolio_tempdata', ['id']);
1576
    // Launch add key tempdataid.
1577
    $dbman->add_key($table, $key);
1578
 
1579
    // Define key usermodified (foreign) to be added to file_conversion.
1580
    $table = new xmldb_table('file_conversion');
1581
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1582
    // Launch add key usermodified.
1583
    $dbman->add_key($table, $key);
1584
 
1585
    // Define key userid (foreign) to be added to repository_instances.
1586
    $table = new xmldb_table('repository_instances');
1587
    $key = new xmldb_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
1588
    // Launch add key userid.
1589
    $dbman->add_key($table, $key);
1590
 
1591
    // Define key contextid (foreign) to be added to repository_instances.
1592
    $table = new xmldb_table('repository_instances');
1593
    $key = new xmldb_key('contextid', XMLDB_KEY_FOREIGN, ['contextid'], 'context', ['id']);
1594
    // Launch add key contextid.
1595
    $dbman->add_key($table, $key);
1596
 
1597
    // Define key scaleid (foreign) to be added to rating.
1598
    $table = new xmldb_table('rating');
1599
    $key = new xmldb_key('scaleid', XMLDB_KEY_FOREIGN, ['scaleid'], 'scale', ['id']);
1600
    // Launch add key scaleid.
1601
    $dbman->add_key($table, $key);
1602
 
1603
    // Define key courseid (foreign) to be added to course_published.
1604
    $table = new xmldb_table('course_published');
1605
    $key = new xmldb_key('courseid', XMLDB_KEY_FOREIGN, ['courseid'], 'course', ['id']);
1606
    // Launch add key courseid.
1607
    $dbman->add_key($table, $key);
1608
 
1609
    // Define index hubcourseid (not unique) to be added to course_published.
1610
    $table = new xmldb_table('course_published');
1611
    $index = new xmldb_index('hubcourseid', XMLDB_INDEX_NOTUNIQUE, ['hubcourseid']);
1612
    // Conditionally launch add index hubcourseid.
1613
    if (!$dbman->index_exists($table, $index)) {
1614
        $dbman->add_index($table, $index);
1615
    }
1616
 
1617
    // Define key courseid (foreign) to be added to event_subscriptions.
1618
    $table = new xmldb_table('event_subscriptions');
1619
    $key = new xmldb_key('courseid', XMLDB_KEY_FOREIGN, ['courseid'], 'course', ['id']);
1620
    // Launch add key courseid.
1621
    $dbman->add_key($table, $key);
1622
 
1623
    // Define key userid (foreign) to be added to event_subscriptions.
1624
    $table = new xmldb_table('event_subscriptions');
1625
    $key = new xmldb_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
1626
    // Launch add key userid.
1627
    $dbman->add_key($table, $key);
1628
 
1629
    // Define key userid (foreign) to be added to task_log.
1630
    $table = new xmldb_table('task_log');
1631
    $key = new xmldb_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
1632
    // Launch add key userid.
1633
    $dbman->add_key($table, $key);
1634
 
1635
    // Define key scaleid (foreign) to be added to competency.
1636
    $table = new xmldb_table('competency');
1637
    $key = new xmldb_key('scaleid', XMLDB_KEY_FOREIGN, ['scaleid'], 'scale', ['id']);
1638
    // Launch add key scaleid.
1639
    $dbman->add_key($table, $key);
1640
 
1641
    // Define key usermodified (foreign) to be added to competency.
1642
    $table = new xmldb_table('competency');
1643
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1644
    // Launch add key usermodified.
1645
    $dbman->add_key($table, $key);
1646
 
1647
    // Define key usermodified (foreign) to be added to competency_coursecompsetting.
1648
    $table = new xmldb_table('competency_coursecompsetting');
1649
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1650
    // Launch add key usermodified.
1651
    $dbman->add_key($table, $key);
1652
 
1653
    // Define key contextid (foreign) to be added to competency_framework.
1654
    $table = new xmldb_table('competency_framework');
1655
    $key = new xmldb_key('contextid', XMLDB_KEY_FOREIGN, ['contextid'], 'context', ['id']);
1656
    // Launch add key contextid.
1657
    $dbman->add_key($table, $key);
1658
 
1659
    // Define key scaleid (foreign) to be added to competency_framework.
1660
    $table = new xmldb_table('competency_framework');
1661
    $key = new xmldb_key('scaleid', XMLDB_KEY_FOREIGN, ['scaleid'], 'scale', ['id']);
1662
    // Launch add key scaleid.
1663
    $dbman->add_key($table, $key);
1664
 
1665
    // Define key usermodified (foreign) to be added to competency_framework.
1666
    $table = new xmldb_table('competency_framework');
1667
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1668
    // Launch add key usermodified.
1669
    $dbman->add_key($table, $key);
1670
 
1671
    // Define key usermodified (foreign) to be added to competency_coursecomp.
1672
    $table = new xmldb_table('competency_coursecomp');
1673
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1674
    // Launch add key usermodified.
1675
    $dbman->add_key($table, $key);
1676
 
1677
    // Define key actionuserid (foreign) to be added to competency_evidence.
1678
    $table = new xmldb_table('competency_evidence');
1679
    $key = new xmldb_key('actionuserid', XMLDB_KEY_FOREIGN, ['actionuserid'], 'user', ['id']);
1680
    // Launch add key actionuserid.
1681
    $dbman->add_key($table, $key);
1682
 
1683
    // Define key contextid (foreign) to be added to competency_evidence.
1684
    $table = new xmldb_table('competency_evidence');
1685
    $key = new xmldb_key('contextid', XMLDB_KEY_FOREIGN, ['contextid'], 'context', ['id']);
1686
    // Launch add key contextid.
1687
    $dbman->add_key($table, $key);
1688
 
1689
    // Define key usermodified (foreign) to be added to competency_evidence.
1690
    $table = new xmldb_table('competency_evidence');
1691
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1692
    // Launch add key usermodified.
1693
    $dbman->add_key($table, $key);
1694
 
1695
    // Define key usermodified (foreign) to be added to competency_userevidence.
1696
    $table = new xmldb_table('competency_userevidence');
1697
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1698
    // Launch add key usermodified.
1699
    $dbman->add_key($table, $key);
1700
 
1701
    // Define key usermodified (foreign) to be added to competency_plan.
1702
    $table = new xmldb_table('competency_plan');
1703
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1704
    // Launch add key usermodified.
1705
    $dbman->add_key($table, $key);
1706
 
1707
    // Define key usermodified (foreign) to be added to competency_template.
1708
    $table = new xmldb_table('competency_template');
1709
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1710
    // Launch add key usermodified.
1711
    $dbman->add_key($table, $key);
1712
 
1713
    // Define key contextid (foreign) to be added to competency_template.
1714
    $table = new xmldb_table('competency_template');
1715
    $key = new xmldb_key('contextid', XMLDB_KEY_FOREIGN, ['contextid'], 'context', ['id']);
1716
    // Launch add key contextid.
1717
    $dbman->add_key($table, $key);
1718
 
1719
    // Define key usermodified (foreign) to be added to competency_templatecomp.
1720
    $table = new xmldb_table('competency_templatecomp');
1721
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1722
    // Launch add key usermodified.
1723
    $dbman->add_key($table, $key);
1724
 
1725
    // Define key usermodified (foreign) to be added to competency_templatecohort.
1726
    $table = new xmldb_table('competency_templatecohort');
1727
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1728
    // Launch add key usermodified.
1729
    $dbman->add_key($table, $key);
1730
 
1731
    // Define key competencyid (foreign) to be added to competency_relatedcomp.
1732
    $table = new xmldb_table('competency_relatedcomp');
1733
    $key = new xmldb_key('competencyid', XMLDB_KEY_FOREIGN, ['competencyid'], 'competency', ['id']);
1734
    // Launch add key competencyid.
1735
    $dbman->add_key($table, $key);
1736
 
1737
    // Define key relatedcompetencyid (foreign) to be added to competency_relatedcomp.
1738
    $table = new xmldb_table('competency_relatedcomp');
1739
    $key = new xmldb_key('relatedcompetencyid', XMLDB_KEY_FOREIGN, ['relatedcompetencyid'], 'competency', ['id']);
1740
    // Launch add key relatedcompetencyid.
1741
    $dbman->add_key($table, $key);
1742
 
1743
    // Define key usermodified (foreign) to be added to competency_relatedcomp.
1744
    $table = new xmldb_table('competency_relatedcomp');
1745
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1746
    // Launch add key usermodified.
1747
    $dbman->add_key($table, $key);
1748
 
1749
    // Define key usermodified (foreign) to be added to competency_usercomp.
1750
    $table = new xmldb_table('competency_usercomp');
1751
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1752
    // Launch add key usermodified.
1753
    $dbman->add_key($table, $key);
1754
 
1755
    // Define key usermodified (foreign) to be added to competency_usercompcourse.
1756
    $table = new xmldb_table('competency_usercompcourse');
1757
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1758
    // Launch add key usermodified.
1759
    $dbman->add_key($table, $key);
1760
 
1761
    // Define key usermodified (foreign) to be added to competency_usercompplan.
1762
    $table = new xmldb_table('competency_usercompplan');
1763
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1764
    // Launch add key usermodified.
1765
    $dbman->add_key($table, $key);
1766
 
1767
    // Define key usermodified (foreign) to be added to competency_plancomp.
1768
    $table = new xmldb_table('competency_plancomp');
1769
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1770
    // Launch add key usermodified.
1771
    $dbman->add_key($table, $key);
1772
 
1773
    // Define key usermodified (foreign) to be added to competency_userevidencecomp.
1774
    $table = new xmldb_table('competency_userevidencecomp');
1775
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1776
    // Launch add key usermodified.
1777
    $dbman->add_key($table, $key);
1778
 
1779
    // Define key usermodified (foreign) to be added to competency_modulecomp.
1780
    $table = new xmldb_table('competency_modulecomp');
1781
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1782
    // Launch add key usermodified.
1783
    $dbman->add_key($table, $key);
1784
 
1785
    // Define key usermodified (foreign) to be added to oauth2_endpoint.
1786
    $table = new xmldb_table('oauth2_endpoint');
1787
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1788
    // Launch add key usermodified.
1789
    $dbman->add_key($table, $key);
1790
 
1791
    // Define key usermodified (foreign) to be added to oauth2_system_account.
1792
    $table = new xmldb_table('oauth2_system_account');
1793
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1794
    // Launch add key usermodified.
1795
    $dbman->add_key($table, $key);
1796
 
1797
    // Define key usermodified (foreign) to be added to oauth2_user_field_mapping.
1798
    $table = new xmldb_table('oauth2_user_field_mapping');
1799
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1800
    // Launch add key usermodified.
1801
    $dbman->add_key($table, $key);
1802
 
1803
    // Define key usermodified (foreign) to be added to analytics_models.
1804
    $table = new xmldb_table('analytics_models');
1805
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1806
    // Launch add key usermodified.
1807
    $dbman->add_key($table, $key);
1808
 
1809
    // Define key usermodified (foreign) to be added to analytics_models_log.
1810
    $table = new xmldb_table('analytics_models_log');
1811
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1812
    // Launch add key usermodified.
1813
    $dbman->add_key($table, $key);
1814
 
1815
    // Define key usermodified (foreign) to be added to oauth2_access_token.
1816
    $table = new xmldb_table('oauth2_access_token');
1817
    $key = new xmldb_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1818
    // Launch add key usermodified.
1819
    $dbman->add_key($table, $key);
1820
 
1821
    // Define key contextid (foreign) to be added to payment_accounts.
1822
    $table = new xmldb_table('payment_accounts');
1823
    $key = new xmldb_key('contextid', XMLDB_KEY_FOREIGN, ['contextid'], 'context', ['id']);
1824
    // Launch add key contextid.
1825
    $dbman->add_key($table, $key);
1826
}