MDL-58139 completion: bulk update completion
[moodle.git] / completion / classes / manager.php
CommitLineData
0b620801
AG
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * Bulk activity completion manager class
20 *
21 * @package core_completion
22 * @category completion
23 * @copyright 2017 Adrian Greeve
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 */
26
27namespace core_completion;
28
29use stdClass;
e8a71f85 30use context_course;
0b620801
AG
31
32/**
33 * Bulk activity completion manager class
34 *
35 * @package core_completion
36 * @category completion
37 * @copyright 2017 Adrian Greeve
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 */
40class manager {
41
42 protected $courseid;
43
44 public function __construct($courseid) {
45 $this->courseid = $courseid;
46 }
47
48 /**
49 * Gets the data (context) to be used with the bulkactivitycompletion template.
50 *
51 * @return stdClass data for use with the bulkactivitycompletion template.
52 */
53 public function get_activities_and_headings() {
54 global $OUTPUT;
55 $moduleinfo = get_fast_modinfo($this->courseid);
56 $sections = $moduleinfo->get_sections();
57 $data = new stdClass;
58 $data->courseid = $this->courseid;
59 $data->sesskey = sesskey();
60 $data->helpicon = $OUTPUT->help_icon('temphelp', 'moodle');
61 $data->sections = [];
62 foreach ($sections as $sectionnumber => $section) {
63 $sectioninfo = $moduleinfo->get_section_info($sectionnumber);
64
65 $sectionobject = new stdClass();
66 $sectionobject->sectionnumber = $sectionnumber;
67 $sectionobject->name = get_section_name($this->courseid, $sectioninfo);
0cbc248d
MG
68 $activitiesdata = $this->get_activities($section, true);
69 $sectionobject->activities = $activitiesdata->activities;
70 $data->sections[] = $sectionobject;
71 }
72 return $data;
73 }
0b620801 74
0cbc248d
MG
75 /**
76 * Gets the data (context) to be used with the activityinstance template
77 *
78 * @param array $cmids list of course module ids
79 * @param bool $withcompletiondetails include completion details
80 * @return \stdClass
81 */
82 public function get_activities($cmids, $withcompletiondetails = false) {
83 $moduleinfo = get_fast_modinfo($this->courseid);
84 $activities = [];
85 foreach ($cmids as $cmid) {
86 $mod = $moduleinfo->get_cm($cmid);
87 if (!$mod->uservisible) {
88 continue;
89 }
90 $moduleobject = new stdClass();
91 $moduleobject->cmid = $cmid;
92 $moduleobject->modname = $mod->get_formatted_name();
93 $moduleobject->icon = $mod->get_icon_url()->out();
94 $moduleobject->url = $mod->url;
95 $moduleobject->canmanage = $withcompletiondetails && self::can_edit_bulk_completion($this->courseid, $mod);
96
97 // Get activity completion information.
98 if ($moduleobject->canmanage) {
99 $moduleobject->completionstatus = $this->get_completion_detail($mod); // This is a placeholder only. Must be replaced later.
100 } else {
101 $moduleobject->completionstatus = ['icon' => null, 'string' => null];
0b620801 102 }
0cbc248d
MG
103
104 $activities[] = $moduleobject;
0b620801 105 }
0cbc248d 106 return (object)['activities' => $activities];
0b620801
AG
107 }
108
109 private function get_completion_detail(\cm_info $mod) {
110 global $OUTPUT;
111 $strings = [];
112 switch ($mod->completion) {
113 case 0:
114 $strings['string'] = get_string('none');
115 break;
116
117 case 1:
118 $strings['string'] = get_string('manual');
119 $strings['icon'] = $OUTPUT->pix_url('i/completion-manual-enabled')->out();
120 break;
121
122 case 2:
123 $strings['string'] = get_string('withconditions');
124
125 // Get the descriptions for all the active completion rules for the module.
126 if ($ruledescriptions = $mod->get_completion_active_rule_descriptions()) {
127 foreach ($ruledescriptions as $ruledescription) {
128 $strings['string'] .= \html_writer::empty_tag('br') . $ruledescription;
129 }
130 }
131
132 $strings['icon'] = $OUTPUT->pix_url('i/completion-auto-enabled')->out();
133 break;
134
135 default:
136 $strings['string'] = get_string('none');
137 break;
138 }
139 return $strings;
140 }
141
e8a71f85
AG
142 public function get_activities_and_resources() {
143 global $DB, $OUTPUT;
144 // Get enabled activities and resources.
145 $modules = $DB->get_records('modules', ['visible' => 1], 'name ASC');
146 $data = new stdClass();
147 $data->courseid = $this->courseid;
148 $data->sesskey = sesskey();
149 $data->helpicon = $OUTPUT->help_icon('temphelp', 'moodle');
150 // Add icon information.
151 $data->modules = array_values($modules);
152 $coursecontext = context_course::instance($this->courseid);
153 foreach ($data->modules as $module) {
154 $module->icon = $OUTPUT->pix_url('icon', $module->name)->out();
155 $module->formatedname = format_string(get_string('pluginname', 'mod_' . $module->name), true, ['context' => $coursecontext]);
156 }
157
158 return $data;
159 }
160
0cbc248d
MG
161 /**
162 * Checks if current user can edit activity completion
163 *
164 * @param int|stdClass $courseorid
165 * @param \cm_info|null $cm if specified capability for a given coursemodule will be check,
166 * if not specified capability to edit at least one activity is checked.
167 */
168 public static function can_edit_bulk_completion($courseorid, $cm = null) {
169 if ($cm) {
170 return $cm->uservisible && has_capability('moodle/course:manageactivities', $cm->context);
171 }
172 $coursecontext = context_course::instance(is_object($courseorid) ? $courseorid->id : $courseorid);
173 if (has_capability('moodle/course:manageactivities', $coursecontext)) {
174 return true;
175 }
176 $modinfo = get_fast_modinfo($courseorid);
177 foreach ($modinfo->cms as $mod) {
178 if ($mod->uservisible && has_capability('moodle/course:manageactivities', $mod->context)) {
179 return true;
180 }
181 }
182 return false;
183 }
06cdda46
MG
184
185 /**
186 * Applies completion from the bulk edit form to all selected modules
187 *
188 * @param stdClass $data data received from the core_completion_bulkedit_form
189 * @param bool $updateinstance if we need to update the instance tables of the module (i.e. 'assign', 'forum', etc.) -
190 * if no module-specific completion rules were added to the form, update of the module table is not needed.
191 */
192 public function apply_completion($data, $updateinstances) {
193 $updated = [];
194 $modinfo = get_fast_modinfo($this->courseid);
195
196 $cmids = $data->cmid;
197
198 $data = (array)$data;
199 unset($data['id']); // This is a course id, we don't want to confuse it with cmid or instance id.
200 unset($data['cmid']);
201 unset($data['submitbutton']);
202
203 foreach ($cmids as $cmid) {
204 $cm = $modinfo->get_cm($cmid);
205 if (self::can_edit_bulk_completion($this->courseid, $cm) && $this->apply_completion_cm($cm, $data, $updateinstances)) {
206 $updated[] = $cm->id;
207 }
208 }
209 if ($updated) {
210 // Now that modules are fully updated, also update completion data if required.
211 // This will wipe all user completion data and recalculate it.
212 rebuild_course_cache($this->courseid, true);
213 $modinfo = get_fast_modinfo($this->courseid);
214 $completion = new \completion_info($modinfo->get_course());
215 foreach ($updated as $cmid) {
216 $completion->reset_all_state($modinfo->get_cm($cmid));
217 }
218 }
219 }
220
221 /**
222 * Applies new completion rules to one course module
223 *
224 * @param \cm_info $cm
225 * @param array $data
226 * @param bool $updateinstance if we need to update the instance table of the module (i.e. 'assign', 'forum', etc.) -
227 * if no module-specific completion rules were added to the form, update of the module table is not needed.
228 * @return bool if module was updated
229 */
230 protected function apply_completion_cm(\cm_info $cm, $data, $updateinstance) {
231 global $DB;
232
233 $defaults = ['completion' => COMPLETION_DISABLED, 'completionview' => COMPLETION_VIEW_NOT_REQUIRED,
234 'completionexpected' => 0, 'completiongradeitemnumber' => null];
235
236 if ($cm->completion == $data['completion'] && $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
237 // If old and new completion are either both "manual" or both "none" - no changes are needed.
238 return false;
239 }
240
241 $data += ['completion' => $cm->completion,
242 'completionexpected' => $cm->completionexpected,
243 'completionview' => $cm->completionview];
244
245 if (array_key_exists('completionusegrade', $data)) {
246 // Convert the 'use grade' checkbox into a grade-item number: 0 if checked, null if not.
247 $data['completiongradeitemnumber'] = !empty($data['completionusegrade']) ? 0 : null;
248 unset($data['completionusegrade']);
249 } else {
250 $data['completiongradeitemnumber'] = $cm->completiongradeitemnumber;
251 }
252
253 // Update module instance table.
254 if ($updateinstance) {
255 $moddata = ['id' => $cm->instance, 'timemodified' => time()] + array_diff_key($data, $defaults);
256 $DB->update_record($cm->modname, $moddata);
257 }
258
259 // Update course modules table.
260 $cmdata = ['id' => $cm->id, 'timemodified' => time()] + array_intersect_key($data, $defaults);
261 $DB->update_record('course_modules', $cmdata);
262
263 \core\event\course_module_updated::create_from_cm($cm, $cm->context)->trigger();
264
265 \core\notification::add(get_string('completionupdated', 'completion', $cm->get_formatted_name()),
266 \core\notification::SUCCESS);
267 return true;
268 }
269
0b620801 270}