-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathavailabilitylib.php
More file actions
402 lines (380 loc) · 14.4 KB
/
availabilitylib.php
File metadata and controls
402 lines (380 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Function to interoperate with availability API.
* Developed for availability_treasurehunt in mind.
* Maybe more.
*
* @package availability_treasurehunt
* @copyright 2025 Juan Pablo de Castro <juan.pablo.de.castro@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Gets the list of course activities
* for the specified stageid, either alone or combined via AND with other restrictions.
* Those that have the availability/treasurehunt restriction applied are marked with
* $info->locked=true.
*
* @param int $courseid Course ID
* @param int $stageid Treasurehunt stage ID
* @return array[stdClass] Array of objects with cm_info and locked status.
*/
function availability_treasurehunt_get_activities_with_stage_restriction($courseid, $stageid) {
// Get course information.
$fastmodinfo = get_fast_modinfo($courseid);
$matchingactivities = [];
// Iterate over all course activities.
foreach ($fastmodinfo->get_cms() as $cminfo) {
// Mark each activity as controlled by availability_treasurehunt or not.
$modinfo = (object) [
'locked' => false,
'cm_info' => $cminfo,
];
// Discard activities invisible to the user.
if (!$cminfo->uservisible) {
continue;
}
// Check if the activity has availability restrictions.
if ($cminfo->availability) {
// Decode the availability JSON.
$availability = json_decode($cminfo->availability, false);
if ($availability && isset($availability->c)) {
// Search for treasurehunt restriction in the conditions.
[$found, $section] = availability_treasurehunt_check_stage_restriction($availability, $stageid);
if ($found) {
$modinfo->locked = true;
}
}
}
$matchingactivities[] = $modinfo;
}
return $matchingactivities;
}
/**
* Auxiliary function to check if a stageid is present in the restrictions
*
* @param object $availability Conditions structure for availability
* @param integer $stageid stage id to check.
* @return array(bool, &object) true if found, conditions section
*/
function availability_treasurehunt_check_stage_restriction($availability, $stageid) {
// Search only condition at root. This is a very common scenario.
if (isset($availability->c[0])) {
if (
$availability->op == "&" &&
count($availability->c) == 1 &&
isset($availability->c[0]->type) &&
$availability->c[0]->type == "treasurehunt" &&
$availability->c[0]->stageid == $stageid
) {
return [ true, null];
}
}
// Search for a group of availability_treasurehunt section.
$conditionsection = &availability_treasurehunt_get_treasurehunt_availability_section($availability);
if ($stageid) {
// Search conditions.
foreach (($conditionsection->c ?? []) as $trcondition) {
// Check if it matches with $newrestriction.
if (($trcondition->type ?? '') === 'treasurehunt') {
if (
($trcondition->stageid ?? '') == $stageid
&& ($trcondition->conditiontype ?? '') == 'current_stage'
) {
return [true, &$conditionsection];
}
}
}
}
return [false, &$conditionsection];
}
/**
* Find treasurehunt section in availability structure.
* Apply a heuristic algorithm for getting the first suitable place for conditions.
* Treasurehunt section is an array of availability_treasurehunt conditions combined by "or" operand.
* @param stdClass $availability structure of availability.
* @return stdClass|null treasurehunt section Reference or null
*/
function &availability_treasurehunt_get_treasurehunt_availability_section($availability) {
if (!isset($availability->c)) {
$nullreference = null;
return $nullreference;
}
// Search treasurehunt section.
foreach ($availability->c as $conditionsection) {
// Check if there is an "or" section at root.
if (isset($conditionsection->op) && $conditionsection->op == '|') {
return $conditionsection;
}
}
$nullreference = null;
return $nullreference;
}
/**
* Adds a treasurehunt restriction to the existing restrictions.
*
* @param course_modinfo $cm course module.
* @param stdClass $stage stage record.
* @param int $treasurehuntid Treasurehunt ID.
* @param bool $replace Whether to replace all existing restrictions.
* @return [stdClass, condition] availability structure, condition structure.
*/
function availability_treasurehunt_add_restriction($cm, $stage, $treasurehuntid, $replace = false) {
$currentavailability = $cm->availability; // phpcs:ignore PHP6602
$availability = null;
// Create an availability structure from json or from scratch.
if ($replace == false && empty($currentavailability) === false) {
// Parse availability json.
$availability = json_decode($currentavailability, false);
}
if (!$availability || !isset($availability->c)) {
// If structure is not valid, create a seed structure.
$availability = (object) [
'op' => '&',
'c' => [],
'showc' => [],
];
}
// Check if there is treasurehunt restriction into treasurehunt section.
[$found, $trsection] = availability_treasurehunt_check_stage_restriction($availability, $stage->id);
if ($found === false) {
// Add the new restriction.
if ($trsection === null) {
// Place treasurehunt availabilities in a labeled section.
// Note: label is not an official field.
$trsection = (object) [
'op' => '|',
'c' => [],
'showc' => [],
];
$availability->c[] = $trsection;
$availability->showc = array_fill(0, count($availability->c), true);
}
// Restriction to add.
$newrestriction = (object) [
'treasurehuntid' => $treasurehuntid,
'type' => 'treasurehunt',
'conditiontype' => 'current_stage',
'requiredvalue' => 0,
'stageid' => $stage->id,
];
$trsection->c[] = $newrestriction;
$trsection->showc[] = true;
}
$condition = new availability_treasurehunt\condition($newrestriction);
return [$availability, $condition];
}
/**
* Change a treasurehunt restriction to a new stageid. For restores.
*
* @param course_modinfo $cm course module.
* @param stdClass $oldstageid stage record.
* @param int $oldtreasurehuntid Treasurehunt ID.
* @param stdClass $newstageid stage record.
* @param int $newtreasurehuntid Treasurehunt ID.
* @return [condition, mixed] availability structure, condition structure.
*/
function availability_treasurehunt_get_updated_restriction(
$cm,
$oldstageid,
$oldtreasurehuntid,
$newstageid,
$newtreasurehuntid
) {
$availability = $cm->availability ? json_decode($cm->availability, false) : null;
// Check if there is treasurehunt restriction into treasurehunt section.
[$found, $trsection] = availability_treasurehunt_check_stage_restriction($availability, $oldstageid);
$updatedcondition = null;
if ($found === true) {
// Update the existing restriction.
foreach ($trsection->c as $condition) {
// Check if it matches with $newrestriction.
if (
($condition->type ?? '') === 'treasurehunt' &&
($condition->stageid ?? '') == $oldstageid &&
($condition->conditiontype ?? '') == 'current_stage' &&
($condition->treasurehuntid ?? '') == $oldtreasurehuntid
) {
// Update to new values.
$condition->stageid = $newstageid;
$condition->treasurehuntid = $newtreasurehuntid;
$condition->requiredvalue = 0;
$condition->conditiontype = 'current_stage';
$updatedcondition = new availability_treasurehunt\condition($condition);
break;
}
}
}
return [$availability, $updatedcondition];
}
/**
* Removes a specific treasurehunt restriction
*
* @param course_modinfo $cm to unlock.
* @param stdClass $stage stage record to unlock.
* @return stdClass|null availability structure.
*/
function availability_treasurehunt_remove_restriction($cm, $stage) {
if (empty($cm->availability)) { // phpcs:ignore PHP6602
return null;
}
$availability = json_decode($cm->availability, false); // phpcs:ignore PHP6602
// Search if only condition at root.
if (isset($availability->c[0])) {
if (
$availability->op == "&" &&
count($availability->c) == 1 &&
isset($availability->c[0]->type) &&
$availability->c[0]->type == "treasurehunt" &&
$availability->c[0]->stageid == $stage->id
) {
// Empty condition list.
$availability->c = [];
$availability->showc = [];
return $availability;
}
}
// Find treasurehunt section.
$trconditions = availability_treasurehunt_get_treasurehunt_availability_section($availability);
if ($trconditions !== null) {
// Filter the restrictions to remove the one that matches stageid.
$conditions = availability_treasurehunt_filter_restrictions($trconditions->c, $stage->id);
$trconditions->c = $conditions;
// Calculate showc array.
$trconditions->showc = array_fill(0, count($trconditions->c), true);
}
return $availability;
}
/**
* Update availability field and purge caches.
* @param mixed $cm
* @param stdClass $newavailability availability structure.
* @return bool
*/
function availability_treasurehunt_update_activity_availability($cm, $newavailability) {
global $DB;
// Clear cache.
$courseid = $cm->get_course()->id;
try {
// Update using the DB.
$DB->set_field(
'course_modules',
'availability',
$newavailability != null ? json_encode($newavailability) : null,
['id' => $cm->id]
);
// Invalidate course cache.
rebuild_course_cache($courseid, true);
return true;
} catch (Exception $e) {
debugging('Error updating availability: ' . $e->getMessage(), DEBUG_DEVELOPER);
return false;
}
}
/**
* Recursively filters the restrictions to remove the specific treasurehunt one
*
* @param array $conditions Array of conditions
* @param int $stageid Stage ID to remove
* @return array Filtered array of conditions
*/
function availability_treasurehunt_filter_restrictions($conditions, $stageid) {
$filtered = [];
foreach ($conditions as $condition) {
// If it's a treasurehunt restriction with the specific stageid, skip it.
if (
$condition->type === 'treasurehunt' &&
$condition->stageid == $stageid
) {
continue;
}
$filtered[] = $condition;
}
return $filtered;
}
/**
* Get intro text from module instance.
* Search for <span class="treasurehunt-return-link">...</span>.
* Create the mark at the end of the text for the return link if it is not found.
* Regenerate the return-link inside the <span>.
*
* @param cm_info $cminfo
* @param availability_treasurehunt\condition $condition
* @param bool $add whether to add the return link if not present.
* @param bool $delete whether to delete the return link if present.
* @return void
*/
function availability_treasurehunt_add_return_link(cm_info $cminfo, $condition, $add = false, $delete = false) {
global $DB;
// Get treasurehunt instance from condition.
$treasurehunt = $condition->get_treasurehunt_instance();
// Get module instance from $modinfo->instance and $modinfo->modulename.
// Get raw description text, search for extra text and reformat.
$intro = $cminfo->content;
if ($delete) {
$add = false;
$returnlinkhtml = '';
} else {
$treasurehuntcmid = get_coursemodule_from_instance(
'treasurehunt',
$treasurehunt->id,
$treasurehunt->course,
false,
MUST_EXIST
)->id;
$returnurl = new moodle_url('/mod/treasurehunt/view.php', ['id' => $treasurehuntcmid]);
$treasurehunttitle = format_string($treasurehunt->name);
$iconurl = new moodle_url("/mod/treasurehunt/pix/icon.svg");
$returnlinktext = new lang_string(
'returnlinktext',
'availability_treasurehunt',
[
'icon' => $iconurl->out(false),
'returnlink' => $returnurl->out(false),
'treasurehuntname' => $treasurehunttitle,
]
);
$returnlinkhtml = '<span class="treasurehunt-return-link">' . $returnlinktext . '</span>';
}
// Search for existing return link span.
if (strpos($intro, 'class="treasurehunt-return-link"') !== false) {
// Replace existing return link.
$newintro = preg_replace(
'/<span class="treasurehunt-return-link">.*?<\/span>/s',
$returnlinkhtml,
$intro
);
} else if ($add) {
// Append return link at the end.
$newintro = $intro . '<br>' . $returnlinkhtml;
} else {
// No changes.
$newintro = $intro;
}
// Update intro in activity.
if ($newintro == $intro) {
// No changes made, avoid updating module.
return;
}
$DB->set_field(
$cminfo->modname,
'intro',
$newintro,
['id' => $cminfo->instance]
);
// Invalidate course cache.
rebuild_course_cache($cminfo->course, clearonly: true);
}