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
 * Course and category management helper class.
19
 *
20
 * @package    core_course
21
 * @copyright  2013 Sam Hemelryk
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace core_course\management;
26
 
27
defined('MOODLE_INTERNAL') || die;
28
 
29
require_once($CFG->dirroot . '/course/lib.php');
30
 
31
/**
32
 * Course and category management interface helper class.
33
 *
34
 * This class provides methods useful to the course and category management interfaces.
35
 * Many of the methods on this class are static and serve one of two purposes.
36
 *  1.  encapsulate functionality in an effort to ensure minimal changes between the different
37
 *      methods of interaction. Specifically browser, AJAX and webservice.
38
 *  2.  abstract logic for acquiring actions away from output so that renderers may use them without
39
 *      having to include any logic or capability checks.
40
 *
41
 * @package    core_course
42
 * @copyright  2013 Sam Hemelryk
43
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44
 */
45
class helper {
46
 
47
    /**
48
     * The expanded category structure if its already being loaded from the cache.
49
     * @var null|array
50
     */
51
    protected static $expandedcategories = null;
52
 
53
    /**
54
     * Returns course details in an array ready to be printed.
55
     *
56
     * @global \moodle_database $DB
57
     * @param \core_course_list_element $course
58
     * @return array
59
     */
60
    public static function get_course_detail_array(\core_course_list_element $course) {
61
        global $DB;
62
 
63
        $canaccess = $course->can_access();
64
 
65
        $format = \course_get_format($course->id);
66
        $modinfo = \get_fast_modinfo($course->id);
67
        $modules = $modinfo->get_used_module_names();
68
        $sections = array();
69
        if ($format->uses_sections()) {
70
            foreach ($modinfo->get_section_info_all() as $section) {
71
                if ($section->uservisible) {
72
                    $sections[] = $format->get_section_name($section);
73
                }
74
            }
75
        }
76
 
77
        $category = \core_course_category::get($course->category);
78
        $categoryurl = new \moodle_url('/course/management.php', array('categoryid' => $course->category));
79
        $categoryname = $category->get_formatted_name();
80
 
81
        $details = array(
82
            'fullname' => array(
83
                'key' => \get_string('fullname'),
84
                'value' => $course->get_formatted_fullname()
85
            ),
86
            'shortname' => array(
87
                'key' => \get_string('shortname'),
88
                'value' => $course->get_formatted_shortname()
89
            ),
90
            'idnumber' => array(
91
                'key' => \get_string('idnumber'),
92
                'value' => s($course->idnumber)
93
            ),
94
            'category' => array(
95
                'key' => \get_string('category'),
96
                'value' => \html_writer::link($categoryurl, $categoryname)
97
            )
98
        );
99
        if (has_capability('moodle/site:accessallgroups', $course->get_context())) {
100
            $groups = \groups_get_course_data($course->id);
101
            $details += array(
102
                'groupings' => array(
103
                    'key' => \get_string('groupings', 'group'),
104
                    'value' => count($groups->groupings)
105
                ),
106
                'groups' => array(
107
                    'key' => \get_string('groups'),
108
                    'value' => count($groups->groups)
109
                )
110
            );
111
        }
112
        if ($canaccess) {
113
            $names = \role_get_names($course->get_context());
114
            $sql = 'SELECT ra.roleid, COUNT(ra.id) AS rolecount
115
                      FROM {role_assignments} ra
116
                     WHERE ra.contextid = :contextid
117
                  GROUP BY ra.roleid';
118
            $rolecounts = $DB->get_records_sql($sql, array('contextid' => $course->get_context()->id));
119
            $roledetails = array();
120
            foreach ($rolecounts as $result) {
121
                $a = new \stdClass;
122
                $a->role = $names[$result->roleid]->localname;
123
                $a->count = $result->rolecount;
124
                $roledetails[] = \get_string('assignedrolecount', 'moodle', $a);
125
            }
126
 
127
            $details['roleassignments'] = array(
128
                'key' => \get_string('roleassignments'),
129
                'value' => join('<br />', $roledetails)
130
            );
131
        }
1441 ariadna 132
 
133
        $contactsdetails = self::get_contacts_by_role_details($course);
134
        if ($contactsdetails) {
135
            $details['coursecontact'] = $contactsdetails;
136
        }
137
 
1 efrain 138
        if ($course->can_review_enrolments()) {
139
            $enrolmentlines = array();
140
            $instances = \enrol_get_instances($course->id, true);
141
            $plugins = \enrol_get_plugins(true);
142
            foreach ($instances as $instance) {
143
                if (!isset($plugins[$instance->enrol])) {
144
                    // Weird.
145
                    continue;
146
                }
147
                $plugin = $plugins[$instance->enrol];
148
                $enrolmentlines[] = $plugin->get_instance_name($instance);
149
            }
150
            $details['enrolmentmethods'] = array(
151
                'key' => \get_string('enrolmentmethods'),
152
                'value' => join('<br />', $enrolmentlines)
153
            );
154
        }
155
        if ($canaccess) {
156
            $details['format'] = array(
157
                'key' => \get_string('format'),
158
                'value' => \course_get_format($course)->get_format_name()
159
            );
160
            $details['sections'] = array(
161
                'key' => \get_string('sections'),
162
                'value' => join('<br />', $sections)
163
            );
164
            $details['modulesused'] = array(
165
                'key' => \get_string('modulesused'),
166
                'value' =>  join('<br />', $modules)
167
            );
168
        }
169
        return $details;
170
    }
171
 
172
    /**
1441 ariadna 173
     * Returns the course contact details, if any.
174
     *
175
     * @param \core_course_list_element $course
176
     * @return array|null Returns null if there are no contacts, otherwise an array of contact details.
177
     */
178
    private static function get_contacts_by_role_details(\core_course_list_element $course): ?array {
179
        if (!$course->can_access()) {
180
            return null;
181
        }
182
 
183
        $contactsbyrole = [];
184
        foreach ($course->get_course_contacts() as $contact) {
185
            $rolenames = array_map(
186
                fn($role) => $role->displayname,
187
                $contact['roles']
188
            );
189
            $contacturl = new \moodle_url('/user/view.php', ['id' => $contact['user']->id]);
190
            $coursecontact = \html_writer::link($contacturl, $contact['username']);
191
 
192
            foreach ($rolenames as $rolename) {
193
                if (!array_key_exists($rolename, $contactsbyrole)) {
194
                    $contactsbyrole[$rolename] = [];
195
                }
196
                $contactsbyrole[$rolename][] = $coursecontact;
197
            }
198
        }
199
        $contactsbyrolelist = [];
200
        foreach ($contactsbyrole as $rolename => $contacts) {
201
            $contactsbyrolelist[] = get_string(
202
                'contactsbyrolelist',
203
                'moodle',
204
                (object) [
205
                    'role' => $rolename,
206
                    'contacts' => implode(', ', $contacts),
207
                ]
208
            );
209
        }
210
 
211
        if (empty($contactsbyrolelist)) {
212
            return null;
213
        }
214
        return [
215
            'key' => \get_string('coursecontact', 'admin'),
216
            'value' => join('<br>', $contactsbyrolelist),
217
        ];
218
    }
219
 
220
    /**
1 efrain 221
     * Returns an array of actions that can be performed upon a category being shown in a list.
222
     *
223
     * @param \core_course_category $category
224
     * @return array
225
     */
226
    public static function get_category_listitem_actions(\core_course_category $category) {
227
        global $CFG;
228
 
229
        $manageurl = new \moodle_url('/course/management.php', array('categoryid' => $category->id));
230
        $baseurl = new \moodle_url($manageurl, array('sesskey' => \sesskey()));
231
        $actions = array();
232
 
233
        // View link.
234
        $actions['view'] = [
235
            'url' => new \moodle_url('/course/index.php', ['categoryid' => $category->id]),
1441 ariadna 236
            'icon' => new \pix_icon('i/viewcategory', new \lang_string('view')),
1 efrain 237
            'string' => get_string('view')
238
        ];
239
 
240
        // Edit.
241
        if ($category->can_edit()) {
242
            $actions['edit'] = array(
243
                'url' => new \moodle_url('/course/editcategory.php', array('id' => $category->id)),
244
                'icon' => new \pix_icon('t/edit', new \lang_string('edit')),
245
                'string' => new \lang_string('edit')
246
            );
247
        }
248
 
249
        // Show/Hide.
250
        if ($category->can_change_visibility()) {
251
            // We always show both icons and then just toggle the display of the invalid option with CSS.
252
            $actions['hide'] = array(
253
                'url' => new \moodle_url($baseurl, array('action' => 'hidecategory')),
254
                'icon' => new \pix_icon('t/hide', new \lang_string('hide')),
255
                'string' => new \lang_string('hide')
256
            );
257
            $actions['show'] = array(
258
                'url' => new \moodle_url($baseurl, array('action' => 'showcategory')),
259
                'icon' => new \pix_icon('t/show', new \lang_string('show')),
260
                'string' => new \lang_string('show')
261
            );
262
        }
263
 
264
        // Move up/down.
265
        if ($category->can_change_sortorder()) {
266
            $actions['moveup'] = array(
267
                'url' => new \moodle_url($baseurl, array('action' => 'movecategoryup')),
268
                'icon' => new \pix_icon('t/up', new \lang_string('moveup')),
269
                'string' => new \lang_string('moveup')
270
            );
271
            $actions['movedown'] = array(
272
                'url' => new \moodle_url($baseurl, array('action' => 'movecategorydown')),
273
                'icon' => new \pix_icon('t/down', new \lang_string('movedown')),
274
                'string' => new \lang_string('movedown')
275
            );
276
        }
277
 
278
        if ($category->can_create_subcategory()) {
279
            $actions['createnewsubcategory'] = array(
280
                'url' => new \moodle_url('/course/editcategory.php', array('parent' => $category->id)),
281
                'icon' => new \pix_icon('i/withsubcat', new \lang_string('createnewsubcategory')),
282
                'string' => new \lang_string('createnewsubcategory')
283
            );
284
        }
285
 
286
        // Resort.
287
        if ($category->can_resort_subcategories() && $category->has_children()) {
288
            $actions['resortbyname'] = array(
289
                'url' => new \moodle_url($baseurl, array('action' => 'resortcategories', 'resort' => 'name')),
290
                'icon' => new \pix_icon('t/sort', new \lang_string('sort')),
291
                'string' => new \lang_string('resortsubcategoriesby', 'moodle' , get_string('categoryname'))
292
            );
293
            $actions['resortbynamedesc'] = array(
294
                'url' => new \moodle_url($baseurl, array('action' => 'resortcategories', 'resort' => 'namedesc')),
295
                'icon' => new \pix_icon('t/sort', new \lang_string('sort')),
296
                'string' => new \lang_string('resortsubcategoriesbyreverse', 'moodle', get_string('categoryname'))
297
            );
298
            $actions['resortbyidnumber'] = array(
299
                'url' => new \moodle_url($baseurl, array('action' => 'resortcategories', 'resort' => 'idnumber')),
300
                'icon' => new \pix_icon('t/sort', new \lang_string('sort')),
301
                'string' => new \lang_string('resortsubcategoriesby', 'moodle', get_string('idnumbercoursecategory'))
302
            );
303
            $actions['resortbyidnumberdesc'] = array(
304
                'url' => new \moodle_url($baseurl, array('action' => 'resortcategories', 'resort' => 'idnumberdesc')),
305
                'icon' => new \pix_icon('t/sort', new \lang_string('sort')),
306
                'string' => new \lang_string('resortsubcategoriesbyreverse', 'moodle', get_string('idnumbercoursecategory'))
307
            );
308
        }
309
 
310
        // Delete.
311
        if (!empty($category->move_content_targets_list()) || $category->can_delete_full()) {
312
            $actions['delete'] = array(
313
                'url' => new \moodle_url($baseurl, array('action' => 'deletecategory')),
314
                'icon' => new \pix_icon('t/delete', new \lang_string('delete')),
315
                'string' => new \lang_string('delete')
316
            );
317
        }
318
 
319
        // Permissions.
320
        if ($category->can_review_permissions()) {
321
            $actions['permissions'] = array(
322
                'url' => new \moodle_url('/admin/roles/permissions.php', ['contextid' => $category->get_context()->id]),
323
                'icon' => new \pix_icon('i/permissions', new \lang_string('permissions', 'role')),
324
                'string' => new \lang_string('permissions', 'role')
325
            );
326
        }
327
 
328
        // Context locking.
329
        if (!empty($CFG->contextlocking) && has_capability('moodle/site:managecontextlocks', $category->get_context())) {
330
            $parentcontext = $category->get_context()->get_parent_context();
331
            if (empty($parentcontext) || !$parentcontext->locked) {
332
                if ($category->get_context()->locked) {
333
                    $lockicon = 'i/unlock';
334
                    $lockstring = get_string('managecontextunlock', 'admin');
335
                } else {
336
                    $lockicon = 'i/lock';
337
                    $lockstring = get_string('managecontextlock', 'admin');
338
                }
339
                $actions['managecontextlock'] = [
340
                    'url' => new \moodle_url('/admin/lock.php', [
341
                            'id' => $category->get_context()->id,
342
                            'returnurl' => $manageurl->out_as_local_url(false),
343
                        ]),
344
                    'icon' => new \pix_icon($lockicon, $lockstring),
345
                    'string' => $lockstring,
346
                ];
347
            }
348
        }
349
 
350
        // Cohorts.
351
        if ($category->can_review_cohorts()) {
352
            $actions['cohorts'] = array(
353
                'url' => new \moodle_url('/cohort/index.php', array('contextid' => $category->get_context()->id)),
354
                'icon' => new \pix_icon('t/cohort', new \lang_string('cohorts', 'cohort')),
355
                'string' => new \lang_string('cohorts', 'cohort')
356
            );
357
        }
358
 
359
        // Filters.
360
        if ($category->can_review_filters()) {
361
            $actions['filters'] = array(
362
                'url' => new \moodle_url('/filter/manage.php', array('contextid' => $category->get_context()->id,
363
                    'return' => 'management')),
364
                'icon' => new \pix_icon('i/filter', new \lang_string('filters', 'admin')),
365
                'string' => new \lang_string('filters', 'admin')
366
            );
367
        }
368
 
369
        if ($category->can_restore_courses_into()) {
370
            $actions['restore'] = array(
371
                'url' => new \moodle_url('/backup/restorefile.php', array('contextid' => $category->get_context()->id)),
372
                'icon' => new \pix_icon('i/restore', new \lang_string('restorecourse', 'admin')),
373
                'string' => new \lang_string('restorecourse', 'admin')
374
            );
375
        }
376
        // Recyclebyn.
377
        if (\tool_recyclebin\category_bin::is_enabled()) {
378
            $categorybin = new \tool_recyclebin\category_bin($category->id);
379
            if ($categorybin->can_view()) {
380
                $autohide = get_config('tool_recyclebin', 'autohide');
381
                if ($autohide) {
382
                    $items = $categorybin->get_items();
383
                } else {
384
                    $items = [];
385
                }
386
                if (!$autohide || !empty($items)) {
387
                    $pluginname = get_string('pluginname', 'tool_recyclebin');
388
                    $actions['recyclebin'] = [
389
                       'url' => new \moodle_url('/admin/tool/recyclebin/index.php', ['contextid' => $category->get_context()->id]),
390
                       'icon' => new \pix_icon('trash', $pluginname, 'tool_recyclebin'),
391
                       'string' => $pluginname
392
                    ];
393
                }
394
            }
395
        }
396
 
397
        // Content bank.
398
        if ($category->has_contentbank()) {
399
            $url = new \moodle_url('/contentbank/index.php', ['contextid' => $category->get_context()->id]);
400
            $actions['contentbank'] = [
401
                'url' => $url,
402
                'icon' => new \pix_icon('i/contentbank', ''),
403
                'string' => get_string('contentbank')
404
            ];
405
        }
406
 
407
        return $actions;
408
    }
409
 
410
    /**
411
     * Returns an array of actions for a course listitem.
412
     *
413
     * @param \core_course_category $category
414
     * @param \core_course_list_element $course
415
     * @return array
416
     */
417
    public static function get_course_listitem_actions(\core_course_category $category, \core_course_list_element $course) {
418
        $baseurl = new \moodle_url(
419
            '/course/management.php',
420
            array('courseid' => $course->id, 'categoryid' => $course->category, 'sesskey' => \sesskey())
421
        );
422
        $actions = array();
423
        // Edit.
424
        if ($course->can_edit()) {
425
            $actions[] = array(
426
                'url' => new \moodle_url('/course/edit.php', array('id' => $course->id, 'returnto' => 'catmanage')),
427
                'icon' => new \pix_icon('t/edit', \get_string('edit')),
428
                'attributes' => array('class' => 'action-edit')
429
            );
430
        }
431
        // Copy.
432
        if (self::can_copy_course($course->id)) {
433
            $actions[] = array(
434
                'url' => new \moodle_url('/backup/copy.php', array('id' => $course->id, 'returnto' => 'catmanage')),
435
                'icon' => new \pix_icon('t/copy', \get_string('copycourse')),
436
                'attributes' => array('class' => 'action-copy')
437
            );
438
        }
439
        // Delete.
440
        if ($course->can_delete()) {
441
            $actions[] = array(
442
                'url' => new \moodle_url('/course/delete.php', array('id' => $course->id)),
443
                'icon' => new \pix_icon('t/delete', \get_string('delete')),
444
                'attributes' => array('class' => 'action-delete')
445
            );
446
        }
447
        // Show/Hide.
448
        if ($course->can_change_visibility()) {
449
            $actions[] = array(
450
                'url' => new \moodle_url($baseurl, array('action' => 'hidecourse')),
451
                'icon' => new \pix_icon('t/hide', \get_string('hide')),
452
                'attributes' => array('data-action' => 'hide', 'class' => 'action-hide')
453
            );
454
            $actions[] = array(
455
                'url' => new \moodle_url($baseurl, array('action' => 'showcourse')),
456
                'icon' => new \pix_icon('t/show', \get_string('show')),
457
                'attributes' => array('data-action' => 'show', 'class' => 'action-show')
458
            );
459
        }
460
        // Move up/down.
461
        if ($category->can_resort_courses()) {
462
            $actions[] = array(
463
                'url' => new \moodle_url($baseurl, array('action' => 'movecourseup')),
464
                'icon' => new \pix_icon('t/up', \get_string('moveup')),
465
                'attributes' => array('data-action' => 'moveup', 'class' => 'action-moveup')
466
            );
467
            $actions[] = array(
468
                'url' => new \moodle_url($baseurl, array('action' => 'movecoursedown')),
469
                'icon' => new \pix_icon('t/down', \get_string('movedown')),
470
                'attributes' => array('data-action' => 'movedown', 'class' => 'action-movedown')
471
            );
472
        }
473
        return $actions;
474
    }
475
 
476
    /**
477
     * Returns an array of actions that can be performed on the course being displayed.
478
     *
479
     * @param \core_course_list_element $course
480
     * @return array
481
     */
482
    public static function get_course_detail_actions(\core_course_list_element $course) {
483
        $params = array('courseid' => $course->id, 'categoryid' => $course->category, 'sesskey' => \sesskey());
484
        $baseurl = new \moodle_url('/course/management.php', $params);
485
        $actions = array();
486
        // View.
487
        $actions['view'] = array(
488
            'url' => new \moodle_url('/course/view.php', array('id' => $course->id)),
489
            'string' => \get_string('view')
490
        );
491
        // Edit.
492
        if ($course->can_edit()) {
493
            $actions['edit'] = array(
494
                'url' => new \moodle_url('/course/edit.php', array('id' => $course->id)),
495
                'string' => \get_string('edit')
496
            );
497
        }
498
        // Permissions.
499
        if ($course->can_review_enrolments()) {
500
            $actions['enrolledusers'] = array(
501
                'url' => new \moodle_url('/user/index.php', array('id' => $course->id)),
502
                'string' => \get_string('enrolledusers', 'enrol')
503
            );
504
        }
505
        // Delete.
506
        if ($course->can_delete()) {
507
            $actions['delete'] = array(
508
                'url' => new \moodle_url('/course/delete.php', array('id' => $course->id)),
509
                'string' => \get_string('delete')
510
            );
511
        }
512
        // Show/Hide.
513
        if ($course->can_change_visibility()) {
514
            if ($course->visible) {
515
                $actions['hide'] = array(
516
                    'url' => new \moodle_url($baseurl, array('action' => 'hidecourse')),
517
                    'string' => \get_string('hide')
518
                );
519
            } else {
520
                $actions['show'] = array(
521
                    'url' => new \moodle_url($baseurl, array('action' => 'showcourse')),
522
                    'string' => \get_string('show')
523
                );
524
            }
525
        }
526
        // Backup.
527
        if ($course->can_backup()) {
528
            $actions['backup'] = array(
529
                'url' => new \moodle_url('/backup/backup.php', array('id' => $course->id)),
530
                'string' => \get_string('backup')
531
            );
532
        }
533
        // Restore.
534
        if ($course->can_restore()) {
535
            $actions['restore'] = array(
536
                'url' => new \moodle_url('/backup/restorefile.php', array('contextid' => $course->get_context()->id)),
537
                'string' => \get_string('restore')
538
            );
539
        }
540
        return $actions;
541
    }
542
 
543
    /**
544
     * Resorts the courses within a category moving the given course up by one.
545
     *
546
     * @param \core_course_list_element $course
547
     * @param \core_course_category $category
548
     * @return bool
549
     * @throws \moodle_exception
550
     */
551
    public static function action_course_change_sortorder_up_one(\core_course_list_element $course,
552
                                                                 \core_course_category $category) {
553
        if (!$category->can_resort_courses()) {
554
            throw new \moodle_exception('permissiondenied', 'error', '', null, 'core_course_category::can_resort');
555
        }
556
        return \course_change_sortorder_by_one($course, true);
557
    }
558
 
559
    /**
560
     * Resorts the courses within a category moving the given course down by one.
561
     *
562
     * @param \core_course_list_element $course
563
     * @param \core_course_category $category
564
     * @return bool
565
     * @throws \moodle_exception
566
     */
567
    public static function action_course_change_sortorder_down_one(\core_course_list_element $course,
568
                                                                   \core_course_category $category) {
569
        if (!$category->can_resort_courses()) {
570
            throw new \moodle_exception('permissiondenied', 'error', '', null, 'core_course_category::can_resort');
571
        }
572
        return \course_change_sortorder_by_one($course, false);
573
    }
574
 
575
    /**
576
     * Resorts the courses within a category moving the given course up by one.
577
     *
578
     * @global \moodle_database $DB
579
     * @param int|\stdClass $courserecordorid
580
     * @return bool
581
     */
582
    public static function action_course_change_sortorder_up_one_by_record($courserecordorid) {
583
        if (is_int($courserecordorid)) {
584
            $courserecordorid = get_course($courserecordorid);
585
        }
586
        $course = new \core_course_list_element($courserecordorid);
587
        $category = \core_course_category::get($course->category);
588
        return self::action_course_change_sortorder_up_one($course, $category);
589
    }
590
 
591
    /**
592
     * Resorts the courses within a category moving the given course down by one.
593
     *
594
     * @global \moodle_database $DB
595
     * @param int|\stdClass $courserecordorid
596
     * @return bool
597
     */
598
    public static function action_course_change_sortorder_down_one_by_record($courserecordorid) {
599
        if (is_int($courserecordorid)) {
600
            $courserecordorid = get_course($courserecordorid);
601
        }
602
        $course = new \core_course_list_element($courserecordorid);
603
        $category = \core_course_category::get($course->category);
604
        return self::action_course_change_sortorder_down_one($course, $category);
605
    }
606
 
607
    /**
608
     * Changes the sort order so that the first course appears after the second course.
609
     *
610
     * @param int|\stdClass $courserecordorid
611
     * @param int $moveaftercourseid
612
     * @return bool
613
     * @throws \moodle_exception
614
     */
615
    public static function action_course_change_sortorder_after_course($courserecordorid, $moveaftercourseid) {
616
        $course = \get_course($courserecordorid);
617
        $category = \core_course_category::get($course->category);
618
        if (!$category->can_resort_courses()) {
619
            $url = '/course/management.php?categoryid='.$course->category;
620
            throw new \moodle_exception('nopermissions', 'error', $url, \get_string('resortcourses', 'moodle'));
621
        }
622
        return \course_change_sortorder_after_course($course, $moveaftercourseid);
623
    }
624
 
625
    /**
626
     * Makes a course visible given a \core_course_list_element object.
627
     *
628
     * @param \core_course_list_element $course
629
     * @return bool
630
     * @throws \moodle_exception
631
     */
632
    public static function action_course_show(\core_course_list_element $course) {
633
        if (!$course->can_change_visibility()) {
634
            throw new \moodle_exception('permissiondenied', 'error', '', null,
635
                'core_course_list_element::can_change_visbility');
636
        }
637
        return course_change_visibility($course->id, true);
638
    }
639
 
640
    /**
641
     * Makes a course hidden given a \core_course_list_element object.
642
     *
643
     * @param \core_course_list_element $course
644
     * @return bool
645
     * @throws \moodle_exception
646
     */
647
    public static function action_course_hide(\core_course_list_element $course) {
648
        if (!$course->can_change_visibility()) {
649
            throw new \moodle_exception('permissiondenied', 'error', '', null,
650
                'core_course_list_element::can_change_visbility');
651
        }
652
        return course_change_visibility($course->id, false);
653
    }
654
 
655
    /**
656
     * Makes a course visible given a course id or a database record.
657
     *
658
     * @global \moodle_database $DB
659
     * @param int|\stdClass $courserecordorid
660
     * @return bool
661
     */
662
    public static function action_course_show_by_record($courserecordorid) {
663
        if (is_int($courserecordorid)) {
664
            $courserecordorid = get_course($courserecordorid);
665
        }
666
        $course = new \core_course_list_element($courserecordorid);
667
        return self::action_course_show($course);
668
    }
669
 
670
    /**
671
     * Makes a course hidden given a course id or a database record.
672
     *
673
     * @global \moodle_database $DB
674
     * @param int|\stdClass $courserecordorid
675
     * @return bool
676
     */
677
    public static function action_course_hide_by_record($courserecordorid) {
678
        if (is_int($courserecordorid)) {
679
            $courserecordorid = get_course($courserecordorid);
680
        }
681
        $course = new \core_course_list_element($courserecordorid);
682
        return self::action_course_hide($course);
683
    }
684
 
685
    /**
686
     * Resort a categories subcategories shifting the given category up one.
687
     *
688
     * @param \core_course_category $category
689
     * @return bool
690
     * @throws \moodle_exception
691
     */
692
    public static function action_category_change_sortorder_up_one(\core_course_category $category) {
693
        if (!$category->can_change_sortorder()) {
694
            throw new \moodle_exception('permissiondenied', 'error', '', null, 'core_course_category::can_change_sortorder');
695
        }
696
        return $category->change_sortorder_by_one(true);
697
    }
698
 
699
    /**
700
     * Resort a categories subcategories shifting the given category down one.
701
     *
702
     * @param \core_course_category $category
703
     * @return bool
704
     * @throws \moodle_exception
705
     */
706
    public static function action_category_change_sortorder_down_one(\core_course_category $category) {
707
        if (!$category->can_change_sortorder()) {
708
            throw new \moodle_exception('permissiondenied', 'error', '', null, 'core_course_category::can_change_sortorder');
709
        }
710
        return $category->change_sortorder_by_one(false);
711
    }
712
 
713
    /**
714
     * Resort a categories subcategories shifting the given category up one.
715
     *
716
     * @param int $categoryid
717
     * @return bool
718
     */
719
    public static function action_category_change_sortorder_up_one_by_id($categoryid) {
720
        $category = \core_course_category::get($categoryid);
721
        return self::action_category_change_sortorder_up_one($category);
722
    }
723
 
724
    /**
725
     * Resort a categories subcategories shifting the given category down one.
726
     *
727
     * @param int $categoryid
728
     * @return bool
729
     */
730
    public static function action_category_change_sortorder_down_one_by_id($categoryid) {
731
        $category = \core_course_category::get($categoryid);
732
        return self::action_category_change_sortorder_down_one($category);
733
    }
734
 
735
    /**
736
     * Makes a category hidden given a core_course_category object.
737
     *
738
     * @param \core_course_category $category
739
     * @return bool
740
     * @throws \moodle_exception
741
     */
742
    public static function action_category_hide(\core_course_category $category) {
743
        if (!$category->can_change_visibility()) {
744
            throw new \moodle_exception('permissiondenied', 'error', '', null, 'core_course_category::can_change_visbility');
745
        }
746
        $category->hide();
747
        return true;
748
    }
749
 
750
    /**
751
     * Makes a category visible given a core_course_category object.
752
     *
753
     * @param \core_course_category $category
754
     * @return bool
755
     * @throws \moodle_exception
756
     */
757
    public static function action_category_show(\core_course_category $category) {
758
        if (!$category->can_change_visibility()) {
759
            throw new \moodle_exception('permissiondenied', 'error', '', null, 'core_course_category::can_change_visbility');
760
        }
761
        $category->show();
762
        return true;
763
    }
764
 
765
    /**
766
     * Makes a category visible given a course category id or database record.
767
     *
768
     * @param int|\stdClass $categoryid
769
     * @return bool
770
     */
771
    public static function action_category_show_by_id($categoryid) {
772
        return self::action_category_show(\core_course_category::get($categoryid));
773
    }
774
 
775
    /**
776
     * Makes a category hidden given a course category id or database record.
777
     *
778
     * @param int|\stdClass $categoryid
779
     * @return bool
780
     */
781
    public static function action_category_hide_by_id($categoryid) {
782
        return self::action_category_hide(\core_course_category::get($categoryid));
783
    }
784
 
785
    /**
786
     * Resorts the sub categories of the given category.
787
     *
788
     * @param \core_course_category $category
789
     * @param string $sort One of idnumber or name.
790
     * @param bool $cleanup If true cleanup will be done, if false you will need to do it manually later.
791
     * @return bool
792
     * @throws \moodle_exception
793
     */
794
    public static function action_category_resort_subcategories(\core_course_category $category, $sort, $cleanup = true) {
795
        if (!$category->can_resort_subcategories()) {
796
            throw new \moodle_exception('permissiondenied', 'error', '', null, 'core_course_category::can_resort');
797
        }
798
        return $category->resort_subcategories($sort, $cleanup);
799
    }
800
 
801
    /**
802
     * Resorts the courses within the given category.
803
     *
804
     * @param \core_course_category $category
805
     * @param string $sort One of fullname, shortname or idnumber
806
     * @param bool $cleanup If true cleanup will be done, if false you will need to do it manually later.
807
     * @return bool
808
     * @throws \moodle_exception
809
     */
810
    public static function action_category_resort_courses(\core_course_category $category, $sort, $cleanup = true) {
811
        if (!$category->can_resort_courses()) {
812
            throw new \moodle_exception('permissiondenied', 'error', '', null, 'core_course_category::can_resort');
813
        }
814
        return $category->resort_courses($sort, $cleanup);
815
    }
816
 
817
    /**
818
     * Moves courses out of one category and into a new category.
819
     *
820
     * @param \core_course_category $oldcategory The category we are moving courses out of.
821
     * @param \core_course_category $newcategory The category we are moving courses into.
822
     * @param array $courseids The ID's of the courses we want to move.
823
     * @return bool True on success.
824
     * @throws \moodle_exception
825
     */
826
    public static function action_category_move_courses_into(\core_course_category $oldcategory,
827
                                                             \core_course_category $newcategory, array $courseids) {
828
        global $DB;
829
 
830
        list($where, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
831
        $params['categoryid'] = $oldcategory->id;
832
        $sql = "SELECT c.id FROM {course} c WHERE c.id {$where} AND c.category <> :categoryid";
833
        if ($DB->record_exists_sql($sql, $params)) {
834
            // Likely being tinkered with.
835
            throw new \moodle_exception('coursedoesnotbelongtocategory');
836
        }
837
 
838
        // All courses are currently within the old category.
839
        return self::move_courses_into_category($newcategory, $courseids);
840
    }
841
 
842
    /**
843
     * Returns the view modes for the management interface.
844
     * @return array
845
     */
846
    public static function get_management_viewmodes() {
847
        return array(
848
            'combined' => new \lang_string('categoriesandcourses'),
849
            'categories' => new \lang_string('categories'),
850
            'courses' => new \lang_string('courses')
851
        );
852
    }
853
 
854
    /**
855
     * Search for courses with matching params.
856
     *
857
     * Please note that only one of search, blocklist, or modulelist can be specified at a time.
858
     * Specifying more than one will result in only the first being used.
859
     *
860
     * @param string $search Words to search for. We search fullname, shortname, idnumber and summary.
861
     * @param int $blocklist The ID of a block, courses will only be returned if they use this block.
862
     * @param string $modulelist The name of a module (relates to database table name). Only courses containing this module
863
     *      will be returned.
864
     * @param int $page The page number to display, starting at 0.
865
     * @param int $perpage The number of courses to display per page.
866
     * @return array
867
     */
868
    public static function search_courses($search, $blocklist, $modulelist, $page = 0, $perpage = null) {
869
        global $CFG;
870
 
871
        if ($perpage === null) {
872
            $perpage = $CFG->coursesperpage;
873
        }
874
 
875
        $searchcriteria = array();
876
        if (!empty($search)) {
877
            $searchcriteria = array('search' => $search);
878
        } else if (!empty($blocklist)) {
879
            $searchcriteria = array('blocklist' => $blocklist);
880
        } else if (!empty($modulelist)) {
881
            $searchcriteria = array('modulelist' => $modulelist);
882
        }
883
 
884
        $topcat = \core_course_category::top();
885
        $courses = $topcat->search_courses($searchcriteria, array(
886
            'recursive' => true,
887
            'offset' => $page * $perpage,
888
            'limit' => $perpage,
889
            'sort' => array('fullname' => 1)
890
        ));
891
        $totalcount = $topcat->search_courses_count($searchcriteria, array('recursive' => true));
892
 
893
        return array($courses, \count($courses), $totalcount);
894
    }
895
 
896
    /**
897
     * Moves one or more courses out of the category they are currently in and into a new category.
898
     *
899
     * This function works much the same way as action_category_move_courses_into however it allows courses from multiple
900
     * categories to be moved into a single category.
901
     *
902
     * @param int|\core_course_category $categoryorid The category to move them into.
903
     * @param array|int $courseids An array of course id's or optionally just a single course id.
904
     * @return bool True on success or false on failure.
905
     * @throws \moodle_exception
906
     */
907
    public static function move_courses_into_category($categoryorid, $courseids = array()) {
908
        global $DB;
909
        if (!is_array($courseids)) {
910
            // Just a single course ID.
911
            $courseids = array($courseids);
912
        }
913
        // Bulk move courses from one category to another.
914
        if (count($courseids) === 0) {
915
            return false;
916
        }
917
        if ($categoryorid instanceof \core_course_category) {
918
            $moveto = $categoryorid;
919
        } else {
920
            $moveto = \core_course_category::get($categoryorid);
921
        }
922
        if (!$moveto->can_move_courses_out_of() || !$moveto->can_move_courses_into()) {
923
            throw new \moodle_exception('cannotmovecourses');
924
        }
925
 
926
        list($where, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
927
        $sql = "SELECT c.id, c.category FROM {course} c WHERE c.id {$where}";
928
        $courses = $DB->get_records_sql($sql, $params);
929
        $checks = array();
930
        foreach ($courseids as $id) {
931
            if (!isset($courses[$id])) {
932
                throw new \moodle_exception('invalidcourseid');
933
            }
934
            $catid = $courses[$id]->category;
935
            if (!isset($checks[$catid])) {
936
                $coursecat = \core_course_category::get($catid);
937
                $checks[$catid] = $coursecat->can_move_courses_out_of() && $coursecat->can_move_courses_into();
938
            }
939
            if (!$checks[$catid]) {
940
                throw new \moodle_exception('cannotmovecourses');
941
            }
942
        }
943
        return \move_courses($courseids, $moveto->id);
944
    }
945
 
946
    /**
947
     * Returns an array of courseids and visiblity for all courses within the given category.
948
     * @param int $categoryid
949
     * @return array
950
     */
951
    public static function get_category_courses_visibility($categoryid) {
952
        global $DB;
953
        $sql = "SELECT c.id, c.visible
954
                  FROM {course} c
955
                 WHERE c.category = :category";
956
        $params = array('category' => (int)$categoryid);
957
        return $DB->get_records_sql($sql, $params);
958
    }
959
 
960
    /**
961
     * Returns an array of all categoryids that have the given category as a parent and their visible value.
962
     * @param int $categoryid
963
     * @return array
964
     */
965
    public static function get_category_children_visibility($categoryid) {
966
        global $DB;
967
        $category = \core_course_category::get($categoryid);
968
        $select = $DB->sql_like('path', ':path');
969
        $path = $category->path . '/%';
970
 
971
        $sql = "SELECT c.id, c.visible
972
                  FROM {course_categories} c
973
                 WHERE ".$select;
974
        $params = array('path' => $path);
975
        return $DB->get_records_sql($sql, $params);
976
    }
977
 
978
    /**
979
     * Records when a category is expanded or collapsed so that when the user
980
     *
981
     * @param \core_course_category $coursecat The category we're working with.
982
     * @param bool $expanded True if the category is expanded now.
983
     */
984
    public static function record_expanded_category(\core_course_category $coursecat, $expanded = true) {
985
        // If this ever changes we are going to reset it and reload the categories as required.
986
        self::$expandedcategories = null;
987
        $categoryid = $coursecat->id;
988
        $path = $coursecat->get_parents();
989
        /* @var \cache_session $cache */
990
        $cache = \cache::make('core', 'userselections');
991
        $categories = $cache->get('categorymanagementexpanded');
992
        if (!is_array($categories)) {
993
            if (!$expanded) {
994
                // No categories recorded, nothing to remove.
995
                return;
996
            }
997
            $categories = array();
998
        }
999
        if ($expanded) {
1000
            $ref =& $categories;
1001
            foreach ($coursecat->get_parents() as $path) {
1002
                if (!isset($ref[$path]) || !is_array($ref[$path])) {
1003
                    $ref[$path] = array();
1004
                }
1005
                $ref =& $ref[$path];
1006
            }
1007
            if (!isset($ref[$categoryid])) {
1008
                $ref[$categoryid] = true;
1009
            }
1010
        } else {
1011
            $found = true;
1012
            $ref =& $categories;
1013
            foreach ($coursecat->get_parents() as $path) {
1014
                if (!isset($ref[$path])) {
1015
                    $found = false;
1016
                    break;
1017
                }
1018
                $ref =& $ref[$path];
1019
            }
1020
            if ($found) {
1021
                $ref[$categoryid] = null;
1022
                unset($ref[$categoryid]);
1023
            }
1024
        }
1025
        $cache->set('categorymanagementexpanded', $categories);
1026
    }
1027
 
1028
    /**
1029
     * Returns the categories that should be expanded when displaying the interface.
1030
     *
1031
     * @param int|null $withpath If specified a path to require as the parent.
1032
     * @return \core_course_category[] An array of Category ID's to expand.
1033
     */
1034
    public static function get_expanded_categories($withpath = null) {
1035
        if (self::$expandedcategories === null) {
1036
            /* @var \cache_session $cache */
1037
            $cache = \cache::make('core', 'userselections');
1038
            self::$expandedcategories = $cache->get('categorymanagementexpanded');
1039
            if (self::$expandedcategories === false) {
1040
                self::$expandedcategories = array();
1041
            }
1042
        }
1043
        if (empty($withpath)) {
1044
            return array_keys(self::$expandedcategories);
1045
        }
1046
        $parents = explode('/', trim($withpath, '/'));
1047
        $ref =& self::$expandedcategories;
1048
        foreach ($parents as $parent) {
1049
            if (!isset($ref[$parent])) {
1050
                return array();
1051
            }
1052
            $ref =& $ref[$parent];
1053
        }
1054
        if (is_array($ref)) {
1055
            return array_keys($ref);
1056
        } else {
1057
            return array($parent);
1058
        }
1059
    }
1060
 
1061
    /**
1062
     * Get an array of the capabilities required to copy a course.
1063
     *
1064
     * @return array
1065
     */
1066
    public static function get_course_copy_capabilities(): array {
1067
        return array('moodle/backup:backupcourse', 'moodle/restore:restorecourse', 'moodle/course:create');
1068
    }
1069
 
1070
    /**
1071
     * Returns true if the current user can copy this course.
1072
     *
1073
     * @param int $courseid
1074
     * @return bool
1075
     */
1076
    public static function can_copy_course(int $courseid): bool {
1077
        $coursecontext = \context_course::instance($courseid);
1078
        return has_all_capabilities(self::get_course_copy_capabilities(), $coursecontext);
1079
    }
1080
}