MDL-44070 Conditional availability enhancements (3): conditions
[moodle.git] / availability / condition / completion / classes / condition.php
CommitLineData
e01efa2c 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 * Activity completion condition.
19 *
20 * @package availability_completion
21 * @copyright 2014 The Open University
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25namespace availability_completion;
26
27defined('MOODLE_INTERNAL') || die();
28
29require_once($CFG->libdir . '/completionlib.php');
30
31/**
32 * Activity completion condition.
33 *
34 * @package availability_completion
35 * @copyright 2014 The Open University
36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 */
38class condition extends \core_availability\condition {
39 /** @var int ID of module that this depends on */
40 protected $cmid;
41
42 /** @var int Expected completion type (one of the COMPLETE_xx constants) */
43 protected $expectedcompletion;
44
45 /** @var array Array of modules used in these conditions for course */
46 protected static $modsusedincondition = array();
47
48 /**
49 * Constructor.
50 *
51 * @param stdClass $structure Data structure from JSON decode
52 * @throws coding_exception If invalid data structure.
53 */
54 public function __construct($structure) {
55 // Get cmid.
56 if (isset($structure->cm) && is_int($structure->cm)) {
57 $this->cmid = $structure->cm;
58 } else {
59 throw new \coding_exception('Missing or invalid ->cm for completion condition');
60 }
61
62 // Get expected completion.
63 if (isset($structure->e) && in_array($structure->e,
64 array(COMPLETION_COMPLETE, COMPLETION_INCOMPLETE,
65 COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL))) {
66 $this->expectedcompletion = $structure->e;
67 } else {
68 throw new \coding_exception('Missing or invalid ->e for completion condition');
69 }
70 }
71
72 public function save() {
73 return (object)array('type' => 'completion',
74 'cm' => $this->cmid, 'e' => $this->expectedcompletion);
75 }
76
77 public function is_available($not, \core_availability\info $info, $grabthelot, $userid) {
78 $modinfo = $info->get_modinfo();
79 $completion = new \completion_info($modinfo->get_course());
80 if (!array_key_exists($this->cmid, $modinfo->cms)) {
81 // If the cmid cannot be found, always return false regardless
82 // of the condition or $not state. (Will be displayed in the
83 // information message.)
84 $allow = false;
85 } else {
86 // The completion system caches its own data so no caching needed here.
87 $completiondata = $completion->get_data((object)array('id' => $this->cmid),
88 $grabthelot, $userid, $modinfo);
89
90 $allow = true;
91 if ($this->expectedcompletion == COMPLETION_COMPLETE) {
92 // Complete also allows the pass, fail states.
93 switch ($completiondata->completionstate) {
94 case COMPLETION_COMPLETE:
95 case COMPLETION_COMPLETE_FAIL:
96 case COMPLETION_COMPLETE_PASS:
97 break;
98 default:
99 $allow = false;
100 }
101 } else {
102 // Other values require exact match.
103 if ($completiondata->completionstate != $this->expectedcompletion) {
104 $allow = false;
105 }
106 }
107
108 if ($not) {
109 $allow = !$allow;
110 }
111 }
112
113 return $allow;
114 }
115
116 /**
117 * Returns a more readable keyword corresponding to a completion state.
118 *
119 * Used to make lang strings easier to read.
120 *
121 * @param int $completionstate COMPLETION_xx constant
122 * @return string Readable keyword
123 */
124 protected static function get_lang_string_keyword($completionstate) {
125 switch($completionstate) {
126 case COMPLETION_INCOMPLETE:
127 return 'incomplete';
128 case COMPLETION_COMPLETE:
129 return 'complete';
130 case COMPLETION_COMPLETE_PASS:
131 return 'complete_pass';
132 case COMPLETION_COMPLETE_FAIL:
133 return 'complete_fail';
134 default:
135 throw new \coding_exception('Unexpected completion state: ' . $completionstate);
136 }
137 }
138
139 public function get_description($full, $not, \core_availability\info $info) {
140 // Get name for module.
141 $modinfo = $info->get_modinfo();
142 if (!array_key_exists($this->cmid, $modinfo->cms)) {
143 $modname = get_string('missing', 'availability_completion');
144 } else {
145 $modname = '<AVAILABILITY_CMNAME_' . $modinfo->cms[$this->cmid]->id . '/>';
146 }
147
148 // Work out which lang string to use.
149 if ($not) {
150 // Convert NOT strings to use the equivalent where possible.
151 switch ($this->expectedcompletion) {
152 case COMPLETION_INCOMPLETE:
153 $str = 'requires_' . self::get_lang_string_keyword(COMPLETION_COMPLETE);
154 break;
155 case COMPLETION_COMPLETE:
156 $str = 'requires_' . self::get_lang_string_keyword(COMPLETION_INCOMPLETE);
157 break;
158 default:
159 // The other two cases do not have direct opposites.
160 $str = 'requires_not_' . self::get_lang_string_keyword($this->expectedcompletion);
161 break;
162 }
163 } else {
164 $str = 'requires_' . self::get_lang_string_keyword($this->expectedcompletion);
165 }
166
167 return get_string($str, 'availability_completion', $modname);
168 }
169
170 protected function get_debug_string() {
171 switch ($this->expectedcompletion) {
172 case COMPLETION_COMPLETE :
173 $type = 'COMPLETE';
174 break;
175 case COMPLETION_INCOMPLETE :
176 $type = 'INCOMPLETE';
177 break;
178 case COMPLETION_COMPLETE_PASS:
179 $type = 'COMPLETE_PASS';
180 break;
181 case COMPLETION_COMPLETE_FAIL:
182 $type = 'COMPLETE_FAIL';
183 break;
184 default:
185 throw new \coding_exception('Unexpected expected completion');
186 }
187 return 'cm' . $this->cmid . ' ' . $type;
188 }
189
190 public function update_after_restore($restoreid, $courseid, \base_logger $logger, $name) {
191 global $DB;
192 $rec = \restore_dbops::get_backup_ids_record($restoreid, 'course_module', $this->cmid);
193 if (!$rec || !$rec->newitemid) {
194 // If we are on the same course (e.g. duplicate) then we can just
195 // use the existing one.
196 if ($DB->record_exists('course_modules',
197 array('id' => $this->cmid, 'course' => $courseid))) {
198 return false;
199 }
200 // Otherwise it's a warning.
201 $this->cmid = 0;
202 $logger->process('Restored item (' . $name .
203 ') has availability condition on module that was not restored',
204 \backup::LOG_WARNING);
205 } else {
206 $this->cmid = (int)$rec->newitemid;
207 }
208 return true;
209 }
210
211 /**
212 * Used in course/lib.php because we need to disable the completion JS if
213 * a completion value affects a conditional activity.
214 *
215 * @param stdClass $course Moodle course object
216 * @param int $cmid Course-module id
217 * @return bool True if this is used in a condition, false otherwise
218 */
219 public static function completion_value_used($course, $cmid) {
220 // Have we already worked out a list of required completion values
221 // for this course? If so just use that.
222 if (!array_key_exists($course->id, self::$modsusedincondition)) {
223 // We don't have data for this course, build it.
224 $modinfo = get_fast_modinfo($course);
225 self::$modsusedincondition[$course->id] = array();
226
227 // Activities.
228 foreach ($modinfo->cms as $othercm) {
229 if (is_null($othercm->availability)) {
230 continue;
231 }
232 $ci = new \core_availability\info_module($othercm);
233 $tree = $ci->get_availability_tree();
234 foreach ($tree->get_all_children('availability_completion\condition') as $cond) {
235 self::$modsusedincondition[$course->id][$cond->cmid] = true;
236 }
237 }
238
239 // Sections.
240 foreach ($modinfo->get_section_info_all() as $section) {
241 if (is_null($section->availability)) {
242 continue;
243 }
244 $ci = new \core_availability\info_section($section);
245 $tree = $ci->get_availability_tree();
246 foreach ($tree->get_all_children('availability_completion\condition') as $cond) {
247 self::$modsusedincondition[$course->id][$cond->cmid] = true;
248 }
249 }
250 }
251 return array_key_exists($cmid, self::$modsusedincondition[$course->id]);
252 }
253
254 /**
255 * Wipes the static cache of modules used in a condition (for unit testing).
256 */
257 public static function wipe_static_cache() {
258 self::$modsusedincondition = array();
259 }
260
261 public function update_dependency_id($table, $oldid, $newid) {
262 if ($table === 'course_modules' && (int)$this->cmid === (int)$oldid) {
263 $this->cmid = $newid;
264 return true;
265 } else {
266 return false;
267 }
268 }
269}