Proyectos de Subversion Moodle

Rev

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