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 |
* Bulk activity completion manager class
|
|
|
19 |
*
|
|
|
20 |
* @package core_completion
|
|
|
21 |
* @category completion
|
|
|
22 |
* @copyright 2017 Adrian Greeve
|
|
|
23 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
24 |
*/
|
|
|
25 |
|
|
|
26 |
namespace core_completion;
|
|
|
27 |
|
|
|
28 |
use core\context;
|
|
|
29 |
use stdClass;
|
|
|
30 |
use context_course;
|
|
|
31 |
use cm_info;
|
|
|
32 |
use tabobject;
|
|
|
33 |
use lang_string;
|
|
|
34 |
use moodle_url;
|
|
|
35 |
defined('MOODLE_INTERNAL') || die;
|
|
|
36 |
|
|
|
37 |
/**
|
|
|
38 |
* Bulk activity completion manager class
|
|
|
39 |
*
|
|
|
40 |
* @package core_completion
|
|
|
41 |
* @category completion
|
|
|
42 |
* @copyright 2017 Adrian Greeve
|
|
|
43 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
44 |
*/
|
|
|
45 |
class manager {
|
|
|
46 |
|
|
|
47 |
/**
|
|
|
48 |
* @var int $courseid the course id.
|
|
|
49 |
*/
|
|
|
50 |
protected $courseid;
|
|
|
51 |
|
|
|
52 |
/**
|
|
|
53 |
* manager constructor.
|
|
|
54 |
* @param int $courseid the course id.
|
|
|
55 |
*/
|
|
|
56 |
public function __construct($courseid) {
|
|
|
57 |
$this->courseid = $courseid;
|
|
|
58 |
}
|
|
|
59 |
|
|
|
60 |
/**
|
|
|
61 |
* Returns current course context or system level for $SITE courseid.
|
|
|
62 |
*
|
|
|
63 |
* @return context The course based on current courseid or system context.
|
|
|
64 |
*/
|
|
|
65 |
protected function get_context(): context {
|
|
|
66 |
global $SITE;
|
|
|
67 |
|
|
|
68 |
if ($this->courseid && $this->courseid != $SITE->id) {
|
|
|
69 |
return context_course::instance($this->courseid);
|
|
|
70 |
}
|
|
|
71 |
return \context_system::instance();
|
|
|
72 |
}
|
|
|
73 |
|
|
|
74 |
/**
|
|
|
75 |
* Gets the data (context) to be used with the bulkactivitycompletion template.
|
|
|
76 |
*
|
|
|
77 |
* @return stdClass data for use with the bulkactivitycompletion template.
|
|
|
78 |
*/
|
|
|
79 |
public function get_activities_and_headings() {
|
|
|
80 |
global $OUTPUT;
|
|
|
81 |
$moduleinfo = get_fast_modinfo($this->courseid);
|
|
|
82 |
$sections = $moduleinfo->get_sections();
|
|
|
83 |
$data = new stdClass;
|
|
|
84 |
$data->courseid = $this->courseid;
|
|
|
85 |
$data->sesskey = sesskey();
|
|
|
86 |
$data->helpicon = $OUTPUT->help_icon('bulkcompletiontracking', 'core_completion');
|
|
|
87 |
$data->sections = [];
|
|
|
88 |
foreach ($sections as $sectionnumber => $section) {
|
|
|
89 |
$sectioninfo = $moduleinfo->get_section_info($sectionnumber);
|
|
|
90 |
|
|
|
91 |
$sectionobject = new stdClass();
|
|
|
92 |
$sectionobject->sectionnumber = $sectionnumber;
|
|
|
93 |
$sectionobject->name = get_section_name($this->courseid, $sectioninfo);
|
|
|
94 |
$sectionobject->activities = $this->get_activities($section, true);
|
|
|
95 |
$data->sections[] = $sectionobject;
|
|
|
96 |
}
|
|
|
97 |
return $data;
|
|
|
98 |
}
|
|
|
99 |
|
|
|
100 |
/**
|
|
|
101 |
* Gets the data (context) to be used with the activityinstance template
|
|
|
102 |
*
|
|
|
103 |
* @param array $cmids list of course module ids
|
|
|
104 |
* @param bool $withcompletiondetails include completion details
|
|
|
105 |
* @return array
|
|
|
106 |
*/
|
|
|
107 |
public function get_activities($cmids, $withcompletiondetails = false) {
|
|
|
108 |
$moduleinfo = get_fast_modinfo($this->courseid);
|
|
|
109 |
$activities = [];
|
|
|
110 |
foreach ($cmids as $cmid) {
|
|
|
111 |
$mod = $moduleinfo->get_cm($cmid);
|
|
|
112 |
if (!$mod->uservisible) {
|
|
|
113 |
continue;
|
|
|
114 |
}
|
|
|
115 |
$moduleobject = new stdClass();
|
|
|
116 |
$moduleobject->cmid = $cmid;
|
|
|
117 |
$moduleobject->modname = $mod->get_formatted_name();
|
|
|
118 |
$moduleobject->icon = $mod->get_icon_url()->out();
|
|
|
119 |
$moduleobject->url = $mod->url;
|
|
|
120 |
$moduleobject->canmanage = $withcompletiondetails && self::can_edit_bulk_completion($this->courseid, $mod);
|
|
|
121 |
|
|
|
122 |
// Get activity completion information.
|
|
|
123 |
if ($moduleobject->canmanage) {
|
|
|
124 |
$moduleobject->completionstatus = $this->get_completion_detail($mod);
|
|
|
125 |
} else {
|
|
|
126 |
$moduleobject->completionstatus = ['icon' => null, 'string' => null];
|
|
|
127 |
}
|
|
|
128 |
if (self::can_edit_bulk_completion($this->courseid, $mod)) {
|
|
|
129 |
$activities[] = $moduleobject;
|
|
|
130 |
}
|
|
|
131 |
}
|
|
|
132 |
return $activities;
|
|
|
133 |
}
|
|
|
134 |
|
|
|
135 |
|
|
|
136 |
/**
|
|
|
137 |
* Get completion information on the selected module or module type
|
|
|
138 |
*
|
|
|
139 |
* @param cm_info|stdClass $mod either instance of cm_info (with 'customcompletionrules' in customdata) or
|
|
|
140 |
* object with fields ->completion, ->completionview, ->completionexpected, ->completionusegrade
|
|
|
141 |
* and ->customdata['customcompletionrules']
|
|
|
142 |
* @return array
|
|
|
143 |
*/
|
|
|
144 |
private function get_completion_detail($mod) {
|
|
|
145 |
global $OUTPUT;
|
|
|
146 |
$strings = [];
|
|
|
147 |
switch ($mod->completion) {
|
|
|
148 |
case COMPLETION_TRACKING_NONE:
|
|
|
149 |
$strings['string'] = get_string('none');
|
|
|
150 |
break;
|
|
|
151 |
|
|
|
152 |
case COMPLETION_TRACKING_MANUAL:
|
|
|
153 |
$strings['string'] = get_string('manual', 'completion');
|
|
|
154 |
$strings['icon'] = $OUTPUT->pix_icon('i/completion-manual-y', get_string('completion_manual', 'completion'));
|
|
|
155 |
break;
|
|
|
156 |
|
|
|
157 |
case COMPLETION_TRACKING_AUTOMATIC:
|
|
|
158 |
$strings['string'] = get_string('withconditions', 'completion');
|
|
|
159 |
$strings['icon'] = $OUTPUT->pix_icon('i/completion-auto-y', get_string('completion_automatic', 'completion'));
|
|
|
160 |
break;
|
|
|
161 |
|
|
|
162 |
default:
|
|
|
163 |
$strings['string'] = get_string('none');
|
|
|
164 |
break;
|
|
|
165 |
}
|
|
|
166 |
|
|
|
167 |
// Get the descriptions for all the active completion rules for the module.
|
|
|
168 |
if ($ruledescriptions = $this->get_completion_active_rule_descriptions($mod)) {
|
|
|
169 |
foreach ($ruledescriptions as $ruledescription) {
|
|
|
170 |
$strings['string'] .= \html_writer::empty_tag('br') . $ruledescription;
|
|
|
171 |
}
|
|
|
172 |
}
|
|
|
173 |
return $strings;
|
|
|
174 |
}
|
|
|
175 |
|
|
|
176 |
/**
|
|
|
177 |
* Get the descriptions for all active conditional completion rules for the current module.
|
|
|
178 |
*
|
|
|
179 |
* @param cm_info|stdClass $moduledata either instance of cm_info (with 'customcompletionrules' in customdata) or
|
|
|
180 |
* object with fields ->completion, ->completionview, ->completionexpected, ->completionusegrade
|
|
|
181 |
* and ->customdata['customcompletionrules']
|
|
|
182 |
* @return array $activeruledescriptions an array of strings describing the active completion rules.
|
|
|
183 |
*/
|
|
|
184 |
protected function get_completion_active_rule_descriptions($moduledata) {
|
|
|
185 |
$activeruledescriptions = [];
|
|
|
186 |
|
|
|
187 |
if ($moduledata->completion == COMPLETION_TRACKING_AUTOMATIC) {
|
|
|
188 |
// Generate the description strings for the core conditional completion rules (if set).
|
|
|
189 |
if (!empty($moduledata->completionview)) {
|
|
|
190 |
$activeruledescriptions[] = get_string('completionview_desc', 'completion');
|
|
|
191 |
}
|
|
|
192 |
if ($moduledata instanceof cm_info && !is_null($moduledata->completiongradeitemnumber) ||
|
|
|
193 |
($moduledata instanceof stdClass && !empty($moduledata->completionusegrade))) {
|
|
|
194 |
|
|
|
195 |
$description = 'completionusegrade_desc';
|
|
|
196 |
if (!empty($moduledata->completionpassgrade)) {
|
|
|
197 |
$description = 'completionpassgrade_desc';
|
|
|
198 |
}
|
|
|
199 |
|
|
|
200 |
$activeruledescriptions[] = get_string($description, 'completion');
|
|
|
201 |
}
|
|
|
202 |
|
|
|
203 |
// Now, ask the module to provide descriptions for its custom conditional completion rules.
|
|
|
204 |
if ($customruledescriptions = component_callback($moduledata->modname,
|
|
|
205 |
'get_completion_active_rule_descriptions', [$moduledata])) {
|
|
|
206 |
$activeruledescriptions = array_merge($activeruledescriptions, $customruledescriptions);
|
|
|
207 |
}
|
|
|
208 |
}
|
|
|
209 |
|
|
|
210 |
if ($moduledata->completion != COMPLETION_TRACKING_NONE) {
|
|
|
211 |
if (!empty($moduledata->completionexpected)) {
|
|
|
212 |
$activeruledescriptions[] = get_string('completionexpecteddesc', 'completion',
|
|
|
213 |
userdate($moduledata->completionexpected));
|
|
|
214 |
}
|
|
|
215 |
}
|
|
|
216 |
|
|
|
217 |
return $activeruledescriptions;
|
|
|
218 |
}
|
|
|
219 |
|
|
|
220 |
/**
|
|
|
221 |
* Gets the course modules for the current course.
|
|
|
222 |
*
|
|
|
223 |
* @param bool $includedefaults Whether the default values should be included or not.
|
|
|
224 |
* @return stdClass $data containing the modules
|
|
|
225 |
*/
|
|
|
226 |
public function get_activities_and_resources(bool $includedefaults = true) {
|
|
|
227 |
global $DB, $OUTPUT, $CFG;
|
|
|
228 |
require_once($CFG->dirroot.'/course/lib.php');
|
|
|
229 |
|
|
|
230 |
// Get enabled activities and resources.
|
|
|
231 |
$modules = $DB->get_records('modules', ['visible' => 1], 'name ASC');
|
|
|
232 |
$data = new stdClass();
|
|
|
233 |
$data->courseid = $this->courseid;
|
|
|
234 |
$data->sesskey = sesskey();
|
|
|
235 |
$data->helpicon = $OUTPUT->help_icon('bulkcompletiontracking', 'core_completion');
|
|
|
236 |
// Add icon information.
|
|
|
237 |
$data->modules = array_values($modules);
|
|
|
238 |
$context = $this->get_context();
|
|
|
239 |
$canmanage = has_capability('moodle/course:manageactivities', $context);
|
|
|
240 |
$course = get_course($this->courseid);
|
|
|
241 |
$availablemodules = [];
|
|
|
242 |
foreach ($data->modules as $module) {
|
|
|
243 |
$libfile = "$CFG->dirroot/mod/$module->name/lib.php";
|
|
|
244 |
if (!file_exists($libfile)) {
|
|
|
245 |
continue;
|
|
|
246 |
}
|
|
|
247 |
$module->icon = $OUTPUT->image_url('monologo', $module->name)->out();
|
|
|
248 |
$module->formattedname = format_string(get_string('modulename', 'mod_' . $module->name),
|
|
|
249 |
true, ['context' => $context]);
|
|
|
250 |
$module->canmanage = $canmanage && course_allowed_module($course, $module->name);
|
|
|
251 |
if ($includedefaults) {
|
|
|
252 |
$defaults = self::get_default_completion($course, $module, false);
|
|
|
253 |
$defaults->modname = $module->name;
|
|
|
254 |
$module->completionstatus = $this->get_completion_detail($defaults);
|
|
|
255 |
}
|
|
|
256 |
$availablemodules[] = $module;
|
|
|
257 |
}
|
|
|
258 |
// Order modules by displayed name.
|
|
|
259 |
usort($availablemodules, function($a, $b) {
|
|
|
260 |
return strcmp($a->formattedname, $b->formattedname);
|
|
|
261 |
});
|
|
|
262 |
$data->modules = $availablemodules;
|
|
|
263 |
|
|
|
264 |
return $data;
|
|
|
265 |
}
|
|
|
266 |
|
|
|
267 |
/**
|
|
|
268 |
* Checks if current user can edit activity completion
|
|
|
269 |
*
|
|
|
270 |
* @param int|stdClass $courseorid
|
|
|
271 |
* @param \cm_info|null $cm if specified capability for a given coursemodule will be check,
|
|
|
272 |
* if not specified capability to edit at least one activity is checked.
|
|
|
273 |
*/
|
|
|
274 |
public static function can_edit_bulk_completion($courseorid, $cm = null) {
|
|
|
275 |
if ($cm) {
|
|
|
276 |
return $cm->uservisible && has_capability('moodle/course:manageactivities', $cm->context);
|
|
|
277 |
}
|
|
|
278 |
$coursecontext = context_course::instance(is_object($courseorid) ? $courseorid->id : $courseorid);
|
|
|
279 |
if (has_capability('moodle/course:manageactivities', $coursecontext)) {
|
|
|
280 |
return true;
|
|
|
281 |
}
|
|
|
282 |
$modinfo = get_fast_modinfo($courseorid);
|
|
|
283 |
foreach ($modinfo->cms as $mod) {
|
|
|
284 |
if ($mod->uservisible && has_capability('moodle/course:manageactivities', $mod->context)) {
|
|
|
285 |
return true;
|
|
|
286 |
}
|
|
|
287 |
}
|
|
|
288 |
return false;
|
|
|
289 |
}
|
|
|
290 |
|
|
|
291 |
/**
|
|
|
292 |
* @deprecated since Moodle 4.0
|
|
|
293 |
*/
|
|
|
294 |
public static function get_available_completion_tabs() {
|
|
|
295 |
throw new \coding_exception(__FUNCTION__ . '() has been removed.');
|
|
|
296 |
}
|
|
|
297 |
|
|
|
298 |
/**
|
|
|
299 |
* Returns an array with the available completion options (url => name) for the current course and user.
|
|
|
300 |
*
|
|
|
301 |
* @param int $courseid The course id.
|
|
|
302 |
* @return array
|
|
|
303 |
*/
|
|
|
304 |
public static function get_available_completion_options(int $courseid): array {
|
|
|
305 |
$coursecontext = context_course::instance($courseid);
|
|
|
306 |
$options = [];
|
|
|
307 |
|
|
|
308 |
if (has_capability('moodle/course:update', $coursecontext)) {
|
|
|
309 |
$completionlink = new moodle_url('/course/completion.php', ['id' => $courseid]);
|
|
|
310 |
$options[$completionlink->out(false)] = get_string('coursecompletionsettings', 'completion');
|
|
|
311 |
}
|
|
|
312 |
|
|
|
313 |
if (has_capability('moodle/course:manageactivities', $coursecontext)) {
|
|
|
314 |
$defaultcompletionlink = new moodle_url('/course/defaultcompletion.php', ['id' => $courseid]);
|
|
|
315 |
$options[$defaultcompletionlink->out(false)] = get_string('defaultcompletion', 'completion');
|
|
|
316 |
}
|
|
|
317 |
|
|
|
318 |
if (self::can_edit_bulk_completion($courseid)) {
|
|
|
319 |
$bulkcompletionlink = new moodle_url('/course/bulkcompletion.php', ['id' => $courseid]);
|
|
|
320 |
$options[$bulkcompletionlink->out(false)] = get_string('bulkactivitycompletion', 'completion');
|
|
|
321 |
}
|
|
|
322 |
|
|
|
323 |
return $options;
|
|
|
324 |
}
|
|
|
325 |
|
|
|
326 |
/**
|
|
|
327 |
* Applies completion from the bulk edit form to all selected modules
|
|
|
328 |
*
|
|
|
329 |
* @param stdClass $data data received from the core_completion_bulkedit_form
|
|
|
330 |
* @param bool $updateinstances if we need to update the instance tables of the module (i.e. 'assign', 'forum', etc.) -
|
|
|
331 |
* if no module-specific completion rules were added to the form, update of the module table is not needed.
|
|
|
332 |
*/
|
|
|
333 |
public function apply_completion($data, $updateinstances) {
|
|
|
334 |
$updated = false;
|
|
|
335 |
$needreset = [];
|
|
|
336 |
$modinfo = get_fast_modinfo($this->courseid);
|
|
|
337 |
|
|
|
338 |
$cmids = $data->cmid;
|
|
|
339 |
|
|
|
340 |
$data = (array)$data;
|
|
|
341 |
unset($data['id']); // This is a course id, we don't want to confuse it with cmid or instance id.
|
|
|
342 |
unset($data['cmid']);
|
|
|
343 |
unset($data['submitbutton']);
|
|
|
344 |
|
|
|
345 |
foreach ($cmids as $cmid) {
|
|
|
346 |
$cm = $modinfo->get_cm($cmid);
|
|
|
347 |
if (self::can_edit_bulk_completion($this->courseid, $cm) && $this->apply_completion_cm($cm, $data, $updateinstances)) {
|
|
|
348 |
$updated = true;
|
|
|
349 |
if ($cm->completion != COMPLETION_TRACKING_MANUAL || $data['completion'] != COMPLETION_TRACKING_MANUAL) {
|
|
|
350 |
// If completion was changed we will need to reset it's state. Exception is when completion was and remains as manual.
|
|
|
351 |
$needreset[] = $cm->id;
|
|
|
352 |
}
|
|
|
353 |
}
|
|
|
354 |
// Update completion calendar events.
|
|
|
355 |
$completionexpected = ($data['completionexpected']) ? $data['completionexpected'] : null;
|
|
|
356 |
\core_completion\api::update_completion_date_event($cm->id, $cm->modname, $cm->instance, $completionexpected);
|
|
|
357 |
}
|
|
|
358 |
if ($updated) {
|
|
|
359 |
// Now that modules are fully updated, also update completion data if required.
|
|
|
360 |
// This will wipe all user completion data and recalculate it.
|
|
|
361 |
rebuild_course_cache($this->courseid, true);
|
|
|
362 |
$modinfo = get_fast_modinfo($this->courseid);
|
|
|
363 |
$completion = new \completion_info($modinfo->get_course());
|
|
|
364 |
foreach ($needreset as $cmid) {
|
|
|
365 |
$completion->reset_all_state($modinfo->get_cm($cmid));
|
|
|
366 |
}
|
|
|
367 |
|
|
|
368 |
// And notify the user of the result.
|
|
|
369 |
\core\notification::add(get_string('activitycompletionupdated', 'core_completion'), \core\notification::SUCCESS);
|
|
|
370 |
}
|
|
|
371 |
}
|
|
|
372 |
|
|
|
373 |
/**
|
|
|
374 |
* Applies new completion rules to one course module
|
|
|
375 |
*
|
|
|
376 |
* @param \cm_info $cm
|
|
|
377 |
* @param array $data
|
|
|
378 |
* @param bool $updateinstance if we need to update the instance table of the module (i.e. 'assign', 'forum', etc.) -
|
|
|
379 |
* if no module-specific completion rules were added to the form, update of the module table is not needed.
|
|
|
380 |
* @return bool if module was updated
|
|
|
381 |
*/
|
|
|
382 |
protected function apply_completion_cm(\cm_info $cm, $data, $updateinstance) {
|
|
|
383 |
global $DB;
|
|
|
384 |
|
|
|
385 |
$defaults = [
|
|
|
386 |
'completion' => COMPLETION_DISABLED, 'completionview' => COMPLETION_VIEW_NOT_REQUIRED,
|
|
|
387 |
'completionexpected' => 0, 'completiongradeitemnumber' => null,
|
|
|
388 |
'completionpassgrade' => 0
|
|
|
389 |
];
|
|
|
390 |
|
|
|
391 |
$data += ['completion' => $cm->completion,
|
|
|
392 |
'completionexpected' => $cm->completionexpected,
|
|
|
393 |
'completionview' => $cm->completionview];
|
|
|
394 |
|
|
|
395 |
if ($cm->completion == $data['completion'] && $cm->completion == COMPLETION_TRACKING_NONE) {
|
|
|
396 |
// If old and new completion are both "none" - no changes are needed.
|
|
|
397 |
return false;
|
|
|
398 |
}
|
|
|
399 |
|
|
|
400 |
if ($cm->completion == $data['completion'] && $cm->completion == COMPLETION_TRACKING_NONE &&
|
|
|
401 |
$cm->completionexpected == $data['completionexpected']) {
|
|
|
402 |
// If old and new completion are both "manual" and completion expected date is not changed - no changes are needed.
|
|
|
403 |
return false;
|
|
|
404 |
}
|
|
|
405 |
|
|
|
406 |
if (array_key_exists('completionusegrade', $data)) {
|
|
|
407 |
// Convert the 'use grade' checkbox into a grade-item number: 0 if checked, null if not.
|
|
|
408 |
$data['completiongradeitemnumber'] = !empty($data['completionusegrade']) ? 0 : null;
|
|
|
409 |
unset($data['completionusegrade']);
|
|
|
410 |
} else {
|
|
|
411 |
// Completion grade item number is classified in mod_edit forms as 'use grade'.
|
|
|
412 |
$data['completionusegrade'] = is_null($cm->completiongradeitemnumber) ? 0 : 1;
|
|
|
413 |
$data['completiongradeitemnumber'] = $cm->completiongradeitemnumber;
|
|
|
414 |
}
|
|
|
415 |
|
|
|
416 |
// Update module instance table.
|
|
|
417 |
if ($updateinstance) {
|
|
|
418 |
$moddata = ['id' => $cm->instance, 'timemodified' => time()] + array_diff_key($data, $defaults);
|
|
|
419 |
$DB->update_record($cm->modname, $moddata);
|
|
|
420 |
}
|
|
|
421 |
|
|
|
422 |
// Update course modules table.
|
|
|
423 |
$cmdata = ['id' => $cm->id, 'timemodified' => time()] + array_intersect_key($data, $defaults);
|
|
|
424 |
$DB->update_record('course_modules', $cmdata);
|
|
|
425 |
|
|
|
426 |
\core\event\course_module_updated::create_from_cm($cm, $cm->context)->trigger();
|
|
|
427 |
|
|
|
428 |
// We need to reset completion data for this activity.
|
|
|
429 |
return true;
|
|
|
430 |
}
|
|
|
431 |
|
|
|
432 |
|
|
|
433 |
/**
|
|
|
434 |
* Saves default completion from edit form to all selected module types
|
|
|
435 |
*
|
|
|
436 |
* @param stdClass $data data received from the core_completion_bulkedit_form
|
|
|
437 |
* @param bool $updatecustomrules if we need to update the custom rules of the module -
|
|
|
438 |
* if no module-specific completion rules were added to the form, update of the module table is not needed.
|
|
|
439 |
* @param string $suffix the suffix to add to the name of the completion rules.
|
|
|
440 |
*/
|
|
|
441 |
public function apply_default_completion($data, $updatecustomrules, string $suffix = '') {
|
|
|
442 |
global $DB;
|
|
|
443 |
|
|
|
444 |
if (!empty($suffix)) {
|
|
|
445 |
// Fields were renamed to avoid conflicts, but they need to be stored in DB with the original name.
|
|
|
446 |
$modules = property_exists($data, 'modules') ? $data->modules : null;
|
|
|
447 |
if ($modules !== null) {
|
|
|
448 |
unset($data->modules);
|
|
|
449 |
$data = (array)$data;
|
|
|
450 |
foreach ($data as $name => $value) {
|
|
|
451 |
if (str_ends_with($name, $suffix)) {
|
|
|
452 |
$data[substr($name, 0, strpos($name, $suffix))] = $value;
|
|
|
453 |
unset($data[$name]);
|
|
|
454 |
} else if ($name == 'customdata') {
|
|
|
455 |
$customrules = $value['customcompletionrules'];
|
|
|
456 |
foreach ($customrules as $rulename => $rulevalue) {
|
|
|
457 |
if (str_ends_with($rulename, $suffix)) {
|
|
|
458 |
$customrules[substr($rulename, 0, strpos($rulename, $suffix))] = $rulevalue;
|
|
|
459 |
unset($customrules[$rulename]);
|
|
|
460 |
}
|
|
|
461 |
}
|
|
|
462 |
$data['customdata'] = $customrules;
|
|
|
463 |
}
|
|
|
464 |
}
|
|
|
465 |
$data = (object)$data;
|
|
|
466 |
}
|
|
|
467 |
}
|
|
|
468 |
|
|
|
469 |
$courseid = $data->id;
|
|
|
470 |
// MDL-72375 Unset the id here, it should not be stored in customrules.
|
|
|
471 |
unset($data->id);
|
|
|
472 |
$coursecontext = context_course::instance($courseid);
|
|
|
473 |
if (!$modids = $data->modids) {
|
|
|
474 |
return;
|
|
|
475 |
}
|
|
|
476 |
$defaults = [
|
|
|
477 |
'completion' => COMPLETION_DISABLED,
|
|
|
478 |
'completionview' => COMPLETION_VIEW_NOT_REQUIRED,
|
|
|
479 |
'completionexpected' => 0,
|
|
|
480 |
'completionusegrade' => 0,
|
|
|
481 |
'completionpassgrade' => 0
|
|
|
482 |
];
|
|
|
483 |
|
|
|
484 |
$data = (array)$data;
|
|
|
485 |
if (!array_key_exists('completionusegrade', $data)) {
|
|
|
486 |
$data['completionusegrade'] = 0;
|
|
|
487 |
}
|
|
|
488 |
if (!array_key_exists('completionpassgrade', $data)) {
|
|
|
489 |
$data['completionpassgrade'] = 0;
|
|
|
490 |
}
|
|
|
491 |
if ($data['completionusegrade'] == 0) {
|
|
|
492 |
$data['completionpassgrade'] = 0;
|
|
|
493 |
}
|
|
|
494 |
|
|
|
495 |
if ($updatecustomrules) {
|
|
|
496 |
$customdata = array_diff_key($data, $defaults);
|
|
|
497 |
$data['customrules'] = $customdata ? json_encode($customdata) : null;
|
|
|
498 |
$defaults['customrules'] = null;
|
|
|
499 |
}
|
|
|
500 |
$data = array_merge($defaults, $data);
|
|
|
501 |
|
|
|
502 |
// Get names of the affected modules.
|
|
|
503 |
list($modidssql, $params) = $DB->get_in_or_equal($modids);
|
|
|
504 |
$params[] = 1;
|
|
|
505 |
$modules = $DB->get_records_select_menu('modules', 'id ' . $modidssql . ' and visible = ?', $params, '', 'id, name');
|
|
|
506 |
|
|
|
507 |
// Get an associative array of [module_id => course_completion_defaults_id].
|
|
|
508 |
list($in, $params) = $DB->get_in_or_equal($modids);
|
|
|
509 |
$params[] = $courseid;
|
|
|
510 |
$defaultsids = $DB->get_records_select_menu('course_completion_defaults', 'module ' . $in . ' and course = ?', $params, '',
|
|
|
511 |
'module, id');
|
|
|
512 |
|
|
|
513 |
foreach ($modids as $modid) {
|
|
|
514 |
if (!array_key_exists($modid, $modules)) {
|
|
|
515 |
continue;
|
|
|
516 |
}
|
|
|
517 |
if (isset($defaultsids[$modid])) {
|
|
|
518 |
$DB->update_record('course_completion_defaults', $data + ['id' => $defaultsids[$modid]]);
|
|
|
519 |
} else {
|
|
|
520 |
$defaultsids[$modid] = $DB->insert_record('course_completion_defaults', $data + ['course' => $courseid,
|
|
|
521 |
'module' => $modid]);
|
|
|
522 |
}
|
|
|
523 |
// Trigger event.
|
|
|
524 |
\core\event\completion_defaults_updated::create([
|
|
|
525 |
'objectid' => $defaultsids[$modid],
|
|
|
526 |
'context' => $coursecontext,
|
|
|
527 |
'other' => ['modulename' => $modules[$modid]],
|
|
|
528 |
])->trigger();
|
|
|
529 |
}
|
|
|
530 |
|
|
|
531 |
// Add notification.
|
|
|
532 |
\core\notification::add(get_string('defaultcompletionupdated', 'completion'), \core\notification::SUCCESS);
|
|
|
533 |
}
|
|
|
534 |
|
|
|
535 |
/**
|
|
|
536 |
* Returns default completion rules for given module type in the given course
|
|
|
537 |
*
|
|
|
538 |
* @param stdClass $course
|
|
|
539 |
* @param stdClass $module
|
|
|
540 |
* @param bool $flatten if true all module custom completion rules become properties of the same object,
|
|
|
541 |
* otherwise they can be found as array in ->customdata['customcompletionrules']
|
|
|
542 |
* @param string $suffix the suffix to add to the name of the completion rules.
|
|
|
543 |
* @return stdClass
|
|
|
544 |
*/
|
|
|
545 |
public static function get_default_completion($course, $module, $flatten = true, string $suffix = '') {
|
|
|
546 |
global $DB, $CFG, $SITE;
|
|
|
547 |
|
|
|
548 |
$fields = 'completion, completionview, completionexpected, completionusegrade, completionpassgrade, customrules';
|
|
|
549 |
// Check course default completion values.
|
|
|
550 |
$params = ['course' => $course->id, 'module' => $module->id];
|
|
|
551 |
$data = $DB->get_record('course_completion_defaults', $params, $fields);
|
|
|
552 |
if (!$data && $course->id != $SITE->id) {
|
|
|
553 |
// If there is no course default completion, check site level default completion values ($SITE->id).
|
|
|
554 |
$params['course'] = $SITE->id;
|
|
|
555 |
$data = $DB->get_record('course_completion_defaults', $params, $fields);
|
|
|
556 |
}
|
|
|
557 |
if ($data) {
|
|
|
558 |
if ($data->customrules && ($customrules = @json_decode($data->customrules, true))) {
|
|
|
559 |
// MDL-72375 This will override activity id for new mods. Skip this field, it is already exposed as courseid.
|
|
|
560 |
unset($customrules['id']);
|
|
|
561 |
|
|
|
562 |
if ($flatten) {
|
|
|
563 |
foreach ($customrules as $key => $value) {
|
|
|
564 |
$data->$key = $value;
|
|
|
565 |
}
|
|
|
566 |
} else {
|
|
|
567 |
$data->customdata['customcompletionrules'] = $customrules;
|
|
|
568 |
}
|
|
|
569 |
}
|
|
|
570 |
unset($data->customrules);
|
|
|
571 |
} else {
|
|
|
572 |
$data = new stdClass();
|
|
|
573 |
$data->completion = COMPLETION_TRACKING_NONE;
|
|
|
574 |
}
|
|
|
575 |
|
|
|
576 |
// If the suffix is not empty, the completion rules need to be renamed to avoid conflicts.
|
|
|
577 |
if (!empty($suffix)) {
|
|
|
578 |
$data = (array)$data;
|
|
|
579 |
foreach ($data as $name => $value) {
|
|
|
580 |
if (str_starts_with($name, 'completion')) {
|
|
|
581 |
$data[$name . $suffix] = $value;
|
|
|
582 |
unset($data[$name]);
|
|
|
583 |
} else if ($name == 'customdata') {
|
|
|
584 |
$customrules = $value['customcompletionrules'];
|
|
|
585 |
foreach ($customrules as $rulename => $rulevalue) {
|
|
|
586 |
if (str_starts_with($rulename, 'completion')) {
|
|
|
587 |
$customrules[$rulename . $suffix] = $rulevalue;
|
|
|
588 |
unset($customrules[$rulename]);
|
|
|
589 |
}
|
|
|
590 |
}
|
|
|
591 |
$data['customdata'] = $customrules;
|
|
|
592 |
}
|
|
|
593 |
}
|
|
|
594 |
$data = (object)$data;
|
|
|
595 |
}
|
|
|
596 |
|
|
|
597 |
return $data;
|
|
|
598 |
}
|
|
|
599 |
|
|
|
600 |
/**
|
|
|
601 |
* Return a mod_form of the given module.
|
|
|
602 |
*
|
|
|
603 |
* @param string $modname Module to get the form from.
|
|
|
604 |
* @param stdClass $course Course object.
|
|
|
605 |
* @param ?cm_info $cm cm_info object to use.
|
|
|
606 |
* @param string $suffix The suffix to add to the name of the completion rules.
|
|
|
607 |
* @return ?\moodleform_mod The moodleform_mod object if everything goes fine. Null otherwise.
|
|
|
608 |
*/
|
|
|
609 |
public static function get_module_form(
|
|
|
610 |
string $modname,
|
|
|
611 |
stdClass $course,
|
|
|
612 |
?cm_info $cm = null,
|
|
|
613 |
string $suffix = ''
|
|
|
614 |
): ?\moodleform_mod {
|
|
|
615 |
global $CFG, $PAGE;
|
|
|
616 |
|
|
|
617 |
$modmoodleform = "$CFG->dirroot/mod/$modname/mod_form.php";
|
|
|
618 |
if (file_exists($modmoodleform)) {
|
|
|
619 |
require_once($modmoodleform);
|
|
|
620 |
} else {
|
|
|
621 |
throw new \moodle_exception('noformdesc');
|
|
|
622 |
}
|
|
|
623 |
|
|
|
624 |
if ($cm) {
|
|
|
625 |
[$cmrec, $context, $module, $data, $cw] = get_moduleinfo_data($cm, $course);
|
|
|
626 |
$data->update = $modname;
|
|
|
627 |
} else {
|
|
|
628 |
[$module, $context, $cw, $cmrec, $data] = prepare_new_moduleinfo_data($course, $modname, 0, $suffix);
|
|
|
629 |
$data->add = $modname;
|
|
|
630 |
}
|
|
|
631 |
$data->return = 0;
|
|
|
632 |
$data->sr = 0;
|
|
|
633 |
|
|
|
634 |
// Initialise the form but discard all JS requirements it adds, our form has already added them.
|
|
|
635 |
$mformclassname = 'mod_'.$modname.'_mod_form';
|
|
|
636 |
$PAGE->start_collecting_javascript_requirements();
|
|
|
637 |
try {
|
|
|
638 |
$moduleform = new $mformclassname($data, 0, $cmrec, $course);
|
|
|
639 |
if (!$cm) {
|
|
|
640 |
$moduleform->set_suffix('_' . $modname);
|
|
|
641 |
}
|
|
|
642 |
} catch (\Exception $e) {
|
|
|
643 |
// The form class has thrown an error when instantiating.
|
|
|
644 |
// This could happen because some conditions for the module are not met.
|
|
|
645 |
$moduleform = null;
|
|
|
646 |
} finally {
|
|
|
647 |
$PAGE->end_collecting_javascript_requirements();
|
|
|
648 |
}
|
|
|
649 |
|
|
|
650 |
return $moduleform;
|
|
|
651 |
}
|
|
|
652 |
}
|