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
 * File containing the helper class.
19
 *
20
 * @package    tool_uploadcourse
21
 * @copyright  2013 Frédéric Massart
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
defined('MOODLE_INTERNAL') || die();
26
require_once($CFG->dirroot . '/cache/lib.php');
27
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
28
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
29
 
30
/**
31
 * Class containing a set of helpers.
32
 *
33
 * @package    tool_uploadcourse
34
 * @copyright  2013 Frédéric Massart
35
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 */
37
class tool_uploadcourse_helper {
38
 
39
    /**
40
     * Generate a shortname based on a template.
41
     *
42
     * @param array|object $data course data.
43
     * @param string $templateshortname template of shortname.
44
     * @return null|string shortname based on the template, or null when an error occured.
45
     */
46
    public static function generate_shortname($data, $templateshortname) {
47
        if (empty($templateshortname) && !is_numeric($templateshortname)) {
48
            return null;
49
        }
50
        if (strpos($templateshortname, '%') === false) {
51
            return $templateshortname;
52
        }
53
 
54
        $course = (object) $data;
55
        $fullname   = isset($course->fullname) ? $course->fullname : '';
56
        $idnumber   = isset($course->idnumber) ? $course->idnumber  : '';
57
 
58
        $callback = partial(array('tool_uploadcourse_helper', 'generate_shortname_callback'), $fullname, $idnumber);
59
        $result = preg_replace_callback('/(?<!%)%([+~-])?(\d)*([fi])/', $callback, $templateshortname);
60
 
61
        if (!is_null($result)) {
62
            $result = clean_param($result, PARAM_TEXT);
63
        }
64
 
65
        if (empty($result) && !is_numeric($result)) {
66
            $result = null;
67
        }
68
 
69
        return $result;
70
    }
71
 
72
    /**
73
     * Callback used when generating a shortname based on a template.
74
     *
75
     * @param string $fullname full name.
76
     * @param string $idnumber ID number.
77
     * @param array $block result from preg_replace_callback.
78
     * @return string
79
     */
80
    public static function generate_shortname_callback($fullname, $idnumber, $block) {
81
        switch ($block[3]) {
82
            case 'f':
83
                $repl = $fullname;
84
                break;
85
            case 'i':
86
                $repl = $idnumber;
87
                break;
88
            default:
89
                return $block[0];
90
        }
91
 
92
        switch ($block[1]) {
93
            case '+':
94
                $repl = core_text::strtoupper($repl);
95
                break;
96
            case '-':
97
                $repl = core_text::strtolower($repl);
98
                break;
99
            case '~':
100
                $repl = core_text::strtotitle($repl);
101
                break;
102
        }
103
 
104
        if (!empty($block[2])) {
105
            $repl = core_text::substr($repl, 0, $block[2]);
106
        }
107
 
108
        return $repl;
109
    }
110
 
111
    /**
112
     * Return the available course formats.
113
     *
114
     * @return array
115
     */
116
    public static function get_course_formats() {
117
        return array_keys(core_component::get_plugin_list('format'));
118
    }
119
 
120
    /**
121
     * Extract enrolment data from passed data.
122
     *
123
     * Constructs an array of methods, and their options:
124
     * array(
125
     *     'method1' => array(
126
     *         'option1' => value,
127
     *         'option2' => value
128
     *     ),
129
     *     'method2' => array(
130
     *         'option1' => value,
131
     *         'option2' => value
132
     *     )
133
     * )
134
     *
135
     * @param array $data data to extract the enrolment data from.
136
     * @return array
137
     */
138
    public static function get_enrolment_data($data) {
139
        $enrolmethods = array();
140
        $enroloptions = array();
141
        foreach ($data as $field => $value) {
142
 
143
            // Enrolmnent data.
144
            $matches = array();
145
            if (preg_match('/^enrolment_(\d+)(_(.+))?$/', $field, $matches)) {
146
                $key = $matches[1];
147
                if (!isset($enroloptions[$key])) {
148
                    $enroloptions[$key] = array();
149
                }
150
                if (empty($matches[3])) {
151
                    $enrolmethods[$key] = $value;
152
                } else {
153
                    $enroloptions[$key][$matches[3]] = $value;
154
                }
155
            }
156
        }
157
 
158
        // Combining enrolment methods and their options in a single array.
159
        $enrolmentdata = array();
160
        if (!empty($enrolmethods)) {
161
            $enrolmentplugins = self::get_enrolment_plugins();
162
            foreach ($enrolmethods as $key => $method) {
163
                if (!array_key_exists($method, $enrolmentplugins)) {
164
                    // Error!
165
                    continue;
166
                }
167
                $enrolmentdata[$enrolmethods[$key]] = $enroloptions[$key];
168
            }
169
        }
170
        return $enrolmentdata;
171
    }
172
 
173
    /**
174
     * Return the enrolment plugins.
175
     *
176
     * The result is cached for faster execution.
177
     *
178
     * @return enrol_plugin[]
179
     */
180
    public static function get_enrolment_plugins() {
181
        $cache = cache::make('tool_uploadcourse', 'helper');
182
        if (($enrol = $cache->get('enrol')) === false) {
183
            $enrol = enrol_get_plugins(false);
184
            $cache->set('enrol', $enrol);
185
        }
186
        return $enrol;
187
    }
188
 
189
    /**
190
     * Get the restore content tempdir.
191
     *
192
     * The tempdir is the sub directory in which the backup has been extracted.
193
     *
194
     * This caches the result for better performance, but $CFG->keeptempdirectoriesonbackup
195
     * needs to be enabled, otherwise the cache is ignored.
196
     *
197
     * @param string $backupfile path to a backup file.
198
     * @param string $shortname shortname of a course.
199
     * @param array $errors will be populated with errors found.
200
     * @return string|false false when the backup couldn't retrieved.
201
     */
202
    public static function get_restore_content_dir($backupfile = null, $shortname = null, &$errors = array()) {
203
        global $CFG, $DB, $USER;
204
 
205
        $cachekey = null;
206
        if (!empty($backupfile)) {
207
            $backupfile = realpath($backupfile);
208
            if (empty($backupfile) || !is_readable($backupfile)) {
209
                $errors['cannotreadbackupfile'] = new lang_string('cannotreadbackupfile', 'tool_uploadcourse');
210
                return false;
211
            }
212
            $cachekey = 'backup_path:' . $backupfile;
213
        } else if (!empty($shortname) || is_numeric($shortname)) {
214
            $cachekey = 'backup_sn:' . $shortname;
215
        }
216
 
217
        if (empty($cachekey)) {
218
            return false;
219
        }
220
 
221
        // If $CFG->keeptempdirectoriesonbackup is not set to true, any restore happening would
222
        // automatically delete the backup directory... causing the cache to return an unexisting directory.
223
        $usecache = !empty($CFG->keeptempdirectoriesonbackup);
224
        if ($usecache) {
225
            $cache = cache::make('tool_uploadcourse', 'helper');
226
        }
227
 
228
        // If we don't use the cache, or if we do and not set, or the directory doesn't exist any more.
229
        if (!$usecache || (($backupid = $cache->get($cachekey)) === false || !is_dir(get_backup_temp_directory($backupid)))) {
230
 
231
            // Use null instead of false because it would consider that the cache key has not been set.
232
            $backupid = null;
233
 
234
            if (!empty($backupfile)) {
235
                // Extracting the backup file.
236
                $packer = get_file_packer('application/vnd.moodle.backup');
237
                $backupid = restore_controller::get_tempdir_name(SITEID, $USER->id);
238
                $path = make_backup_temp_directory($backupid, false);
239
                $result = $packer->extract_to_pathname($backupfile, $path);
240
                if (!$result) {
241
                    $errors['invalidbackupfile'] = new lang_string('invalidbackupfile', 'tool_uploadcourse');
242
                }
243
            } else if (!empty($shortname) || is_numeric($shortname)) {
244
                // Creating restore from an existing course.
245
                $courseid = $DB->get_field('course', 'id', array('shortname' => $shortname), IGNORE_MISSING);
246
                if (!empty($courseid)) {
247
                    $bc = new backup_controller(backup::TYPE_1COURSE, $courseid, backup::FORMAT_MOODLE,
248
                        backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
249
                    $bc->execute_plan();
250
                    $backupid = $bc->get_backupid();
251
                    $bc->destroy();
252
                } else {
253
                    $errors['coursetorestorefromdoesnotexist'] =
254
                        new lang_string('coursetorestorefromdoesnotexist', 'tool_uploadcourse');
255
                }
256
            }
257
 
258
            if ($usecache) {
259
                $cache->set($cachekey, $backupid);
260
            }
261
        }
262
 
263
        if ($backupid === null) {
264
            $backupid = false;
265
        }
266
        return $backupid;
267
    }
268
 
269
    /**
270
     * Return the role IDs.
271
     *
272
     * The result is cached for faster execution.
273
     *
274
     * @return array
275
     */
276
    public static function get_role_ids() {
277
        $cache = cache::make('tool_uploadcourse', 'helper');
278
        if (($roles = $cache->get('roles')) === false) {
279
            $roles = array();
280
            $rolesraw = get_all_roles();
281
            foreach ($rolesraw as $role) {
282
                $roles[$role->shortname] = $role->id;
283
            }
284
            $cache->set('roles', $roles);
285
        }
286
        return $roles;
287
    }
288
 
289
    /**
290
     * Helper to detect how many sections a course with a given shortname has.
291
     *
292
     * @param string $shortname shortname of a course to count sections from.
293
     * @return integer count of sections.
294
     */
295
    public static function get_coursesection_count($shortname) {
296
        global $DB;
297
        if (!empty($shortname) || is_numeric($shortname)) {
298
            // Creating restore from an existing course.
299
            $course = $DB->get_record('course', array('shortname' => $shortname));
300
        }
301
        if (!empty($course)) {
302
            $courseformat = course_get_format($course);
303
            return $courseformat->get_last_section_number();
304
        }
305
        return 0;
306
    }
307
 
308
    /**
309
     * Get the role renaming data from the passed data.
310
     *
311
     * @param array $data data to extract the names from.
312
     * @param array $errors will be populated with errors found.
313
     * @return array where the key is the role_<id>, the value is the new name.
314
     */
315
    public static function get_role_names($data, &$errors = array()) {
316
        $rolenames = array();
317
        $rolesids = self::get_role_ids();
318
        $invalidroles = array();
319
        foreach ($data as $field => $value) {
320
 
321
            $matches = array();
322
            if (preg_match('/^role_(.+)?$/', $field, $matches)) {
323
                if (!isset($rolesids[$matches[1]])) {
324
                    $invalidroles[] = $matches[1];
325
                    continue;
326
                }
327
                $rolenames['role_' . $rolesids[$matches[1]]] = $value;
328
            } else if (preg_match('/^(.+)?_role$/', $field, $matches)) {
329
                if (!isset($rolesids[$value])) {
330
                    $invalidroles[] = $value;
331
                    break;
332
                }
333
            }
334
 
335
        }
336
 
337
        if (!empty($invalidroles)) {
338
            $errors['invalidroles'] = new lang_string('invalidroles', 'tool_uploadcourse', implode(', ', $invalidroles));
339
        }
340
 
341
        // Roles names.
342
        return $rolenames;
343
    }
344
 
345
    /**
346
     * Return array of all custom course fields indexed by their shortname
347
     *
348
     * @return \core_customfield\field_controller[]
349
     */
350
    public static function get_custom_course_fields(): array {
351
        $result = [];
352
 
353
        $fields = \core_course\customfield\course_handler::create()->get_fields();
354
        foreach ($fields as $field) {
355
            $result[$field->get('shortname')] = $field;
356
        }
357
 
358
        return $result;
359
    }
360
 
361
    /**
362
     * Return array of custom field element names
363
     *
364
     * @return string[]
365
     */
366
    public static function get_custom_course_field_names(): array {
367
        $result = [];
368
 
369
        $fields = self::get_custom_course_fields();
370
        foreach ($fields as $field) {
371
            $controller = \core_customfield\data_controller::create(0, null, $field);
372
            $result[] = $controller->get_form_element_name();
373
        }
374
 
375
        return $result;
376
    }
377
 
378
    /**
379
     * Return any elements from passed $data whose key matches one of the custom course fields defined for the site
380
     *
381
     * @param array $data
382
     * @param array $defaults
383
     * @param context $context
384
     * @param array $errors Will be populated with any errors
385
     * @return array
386
     */
387
    public static function get_custom_course_field_data(array $data, array $defaults, context $context,
388
            array &$errors = []): array {
389
 
390
        $fields = self::get_custom_course_fields();
391
        $result = [];
392
 
393
        $canchangelockedfields = guess_if_creator_will_have_course_capability('moodle/course:changelockedcustomfields', $context);
394
 
395
        foreach ($data as $name => $originalvalue) {
396
            if (preg_match('/^customfield_(?<name>.*)?$/', $name, $matches)
397
                    && isset($fields[$matches['name']])) {
398
 
399
                $fieldname = $matches['name'];
400
                $field = $fields[$fieldname];
401
 
402
                // Skip field if it's locked and user doesn't have capability to change locked fields.
403
                if ($field->get_configdata_property('locked') && !$canchangelockedfields) {
404
                    continue;
405
                }
406
 
407
                // Create field data controller.
408
                $controller = \core_customfield\data_controller::create(0, null, $field);
409
                $controller->set('id', 1);
410
 
411
                $defaultvalue = $defaults["customfield_{$fieldname}"] ?? $controller->get_default_value();
412
                $value = (empty($originalvalue) ? $defaultvalue : $field->parse_value($originalvalue));
413
 
414
                // If we initially had a value, but now don't, then reset it to the default.
415
                if (!empty($originalvalue) && empty($value)) {
416
                    $value = $defaultvalue;
417
                }
418
 
419
                // Validate data with controller.
420
                $fieldformdata = [$controller->get_form_element_name() => $value];
421
                $validationerrors = $controller->instance_form_validation($fieldformdata, []);
422
                if (count($validationerrors) > 0) {
423
                    $errors['customfieldinvalid'] = new lang_string('customfieldinvalid', 'tool_uploadcourse',
424
                        $field->get_formatted_name());
425
 
426
                    continue;
427
                }
428
 
429
                $controller->set($controller->datafield(), $value);
430
 
431
                // Pass an empty object to the data controller, which will transform it to a correct name/value pair.
432
                $instance = new stdClass();
433
                $controller->instance_form_before_set_data($instance);
434
 
435
                $result = array_merge($result, (array) $instance);
436
            }
437
        }
438
 
439
        return $result;
440
    }
441
 
442
    /**
443
     * Helper to increment an ID number.
444
     *
445
     * This first checks if the ID number is in use.
446
     *
447
     * @param string $idnumber ID number to increment.
448
     * @return string new ID number.
449
     */
450
    public static function increment_idnumber($idnumber) {
451
        global $DB;
452
        while ($DB->record_exists('course', array('idnumber' => $idnumber))) {
453
            $matches = array();
454
            if (!preg_match('/(.*?)([0-9]+)$/', $idnumber, $matches)) {
455
                $newidnumber = $idnumber . '_2';
456
            } else {
457
                $newidnumber = $matches[1] . ((int) $matches[2] + 1);
458
            }
459
            $idnumber = $newidnumber;
460
        }
461
        return $idnumber;
462
    }
463
 
464
    /**
465
     * Helper to increment a shortname.
466
     *
467
     * This considers that the shortname passed has to be incremented.
468
     *
469
     * @param string $shortname shortname to increment.
470
     * @return string new shortname.
471
     */
472
    public static function increment_shortname($shortname) {
473
        global $DB;
474
        do {
475
            $matches = array();
476
            if (!preg_match('/(.*?)([0-9]+)$/', $shortname, $matches)) {
477
                $newshortname = $shortname . '_2';
478
            } else {
479
                $newshortname = $matches[1] . ($matches[2]+1);
480
            }
481
            $shortname = $newshortname;
482
        } while ($DB->record_exists('course', array('shortname' => $shortname)));
483
        return $shortname;
484
    }
485
 
486
    /**
487
     * Resolve a category based on the data passed.
488
     *
489
     * Key accepted are:
490
     * - category, which is supposed to be a category ID.
491
     * - category_idnumber
492
     * - category_path, array of categories from parent to child.
493
     *
494
     * @param array $data to resolve the category from.
495
     * @param array $errors will be populated with errors found.
496
     * @return int category ID.
497
     */
498
    public static function resolve_category($data, &$errors = array()) {
499
        $catid = null;
500
 
501
        if (!empty($data['category'])) {
502
            $category = core_course_category::get((int) $data['category'], IGNORE_MISSING);
503
            if (!empty($category) && !empty($category->id)) {
504
                $catid = $category->id;
505
            } else {
506
                $errors['couldnotresolvecatgorybyid'] =
507
                    new lang_string('couldnotresolvecatgorybyid', 'tool_uploadcourse');
508
            }
509
        }
510
 
511
        if (empty($catid) && !empty($data['category_idnumber'])) {
512
            $catid = self::resolve_category_by_idnumber($data['category_idnumber']);
513
            if (empty($catid)) {
514
                $errors['couldnotresolvecatgorybyidnumber'] =
515
                    new lang_string('couldnotresolvecatgorybyidnumber', 'tool_uploadcourse');
516
            }
517
        }
518
        if (empty($catid) && !empty($data['category_path'])) {
519
            $catid = self::resolve_category_by_path(explode(' / ', $data['category_path']));
520
            if (empty($catid)) {
521
                $errors['couldnotresolvecatgorybypath'] =
522
                    new lang_string('couldnotresolvecatgorybypath', 'tool_uploadcourse');
523
            }
524
        }
525
 
526
        return $catid;
527
    }
528
 
529
    /**
530
     * Resolve a category by ID number.
531
     *
532
     * @param string $idnumber category ID number.
533
     * @return int category ID.
534
     */
535
    public static function resolve_category_by_idnumber($idnumber) {
536
        global $DB;
537
        $cache = cache::make('tool_uploadcourse', 'helper');
538
        $cachekey = 'cat_idn_' . $idnumber;
539
        if (($id = $cache->get($cachekey)) === false) {
540
            $params = array('idnumber' => $idnumber);
541
            $id = $DB->get_field_select('course_categories', 'id', 'idnumber = :idnumber', $params, IGNORE_MISSING);
542
 
543
            if ($id && !core_course_category::get($id, IGNORE_MISSING)) {
544
                // Category is not visible to the current user.
545
                $id = false;
546
            }
547
 
548
            // Little hack to be able to differenciate between the cache not set and a category not found.
549
            if ($id === false) {
550
                $id = -1;
551
            }
552
 
553
            $cache->set($cachekey, $id);
554
        }
555
 
556
        // Little hack to be able to differenciate between the cache not set and a category not found.
557
        if ($id == -1) {
558
            $id = false;
559
        }
560
 
561
        return $id;
562
    }
563
 
564
    /**
565
     * Resolve a category by path.
566
     *
567
     * @param array $path category names indexed from parent to children.
568
     * @return int category ID.
569
     */
570
    public static function resolve_category_by_path(array $path) {
571
        global $DB;
572
        $cache = cache::make('tool_uploadcourse', 'helper');
573
        $cachekey = 'cat_path_' . serialize($path);
574
        if (($id = $cache->get($cachekey)) === false) {
575
            $parent = 0;
576
            $sql = 'name = :name AND parent = :parent';
577
            while ($name = array_shift($path)) {
578
                $params = array('name' => $name, 'parent' => $parent);
579
                if ($records = $DB->get_records_select('course_categories', $sql, $params, null, 'id, parent')) {
580
                    if (count($records) > 1) {
581
                        // Too many records with the same name!
582
                        $id = -1;
583
                        break;
584
                    }
585
                    $record = reset($records);
586
                    if (!core_course_category::get($id, IGNORE_MISSING)) {
587
                        // Category is not visible to the current user.
588
                        $id = -1;
589
                        break;
590
                    }
591
                    $id = $record->id;
592
                    $parent = $record->id;
593
                } else {
594
                    // Not found.
595
                    $id = -1;
596
                    break;
597
                }
598
            }
599
            $cache->set($cachekey, $id);
600
        }
601
 
602
        // We save -1 when the category has not been found to be able to know if the cache was set.
603
        if ($id == -1) {
604
            $id = false;
605
        }
606
        return $id;
607
    }
608
}