See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
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 * Badge award criteria 19 * 20 * @package core 21 * @subpackage badges 22 * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 * @author Yuliya Bozhko <yuliya.bozhko@totaralms.com> 25 */ 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 /* 30 * Role completion criteria type 31 * Criteria type constant, primarily for storing criteria type in the database. 32 */ 33 define('BADGE_CRITERIA_TYPE_OVERALL', 0); 34 35 /* 36 * Activity completion criteria type 37 * Criteria type constant, primarily for storing criteria type in the database. 38 */ 39 define('BADGE_CRITERIA_TYPE_ACTIVITY', 1); 40 41 /* 42 * Duration completion criteria type 43 * Criteria type constant, primarily for storing criteria type in the database. 44 */ 45 define('BADGE_CRITERIA_TYPE_MANUAL', 2); 46 47 /* 48 * Grade completion criteria type 49 * Criteria type constant, primarily for storing criteria type in the database. 50 */ 51 define('BADGE_CRITERIA_TYPE_SOCIAL', 3); 52 53 /* 54 * Course completion criteria type 55 * Criteria type constant, primarily for storing criteria type in the database. 56 */ 57 define('BADGE_CRITERIA_TYPE_COURSE', 4); 58 59 /* 60 * Courseset completion criteria type 61 * Criteria type constant, primarily for storing criteria type in the database. 62 */ 63 define('BADGE_CRITERIA_TYPE_COURSESET', 5); 64 65 /* 66 * Course completion criteria type 67 * Criteria type constant, primarily for storing criteria type in the database. 68 */ 69 define('BADGE_CRITERIA_TYPE_PROFILE', 6); 70 71 /* 72 * Badge completion criteria type 73 * Criteria type constant, primarily for storing criteria type in the database. 74 */ 75 define('BADGE_CRITERIA_TYPE_BADGE', 7); 76 77 /* 78 * Cohort criteria type 79 * Criteria type constant, primarily for storing criteria type in the database. 80 */ 81 define('BADGE_CRITERIA_TYPE_COHORT', 8); 82 83 /* 84 * Competency criteria type 85 * Criteria type constant, primarily for storing criteria type in the database. 86 */ 87 define('BADGE_CRITERIA_TYPE_COMPETENCY', 9); 88 89 /** 90 * Award criteria abstract definition 91 * 92 */ 93 abstract class award_criteria { 94 95 /** 96 * ID of the criterion. 97 * @var integer 98 */ 99 public $id; 100 101 /** 102 * Aggregation method [BADGE_CRITERIA_AGGREGATION_ANY, BADGE_CRITERIA_AGGREGATION_ALL]. 103 * @var integer 104 */ 105 public $method; 106 107 /** 108 * ID of a badge this criterion belongs to. 109 * @var integer 110 */ 111 public $badgeid; 112 113 /** 114 * Criterion HTML/plain text description. 115 * @var string 116 */ 117 public $description; 118 119 /** 120 * Format of the criterion description. 121 * @var integer 122 */ 123 public $descriptionformat; 124 125 /** 126 * Any additional parameters. 127 * @var array 128 */ 129 public $params = array(); 130 131 /** 132 * The base constructor 133 * 134 * @param array $params 135 */ 136 public function __construct($params) { 137 $this->id = isset($params['id']) ? $params['id'] : 0; 138 $this->method = isset($params['method']) ? $params['method'] : BADGE_CRITERIA_AGGREGATION_ANY; 139 $this->badgeid = $params['badgeid']; 140 $this->description = isset($params['description']) ? $params['description'] : ''; 141 $this->descriptionformat = isset($params['descriptionformat']) ? $params['descriptionformat'] : FORMAT_HTML; 142 if (isset($params['id'])) { 143 $this->params = $this->get_params($params['id']); 144 } 145 } 146 147 /** 148 * Factory method for creating criteria class object 149 * 150 * @param array $params associative arrays varname => value 151 * @return award_criteria 152 */ 153 public static function build($params) { 154 global $CFG; 155 156 require_once($CFG->libdir . '/badgeslib.php'); 157 158 $types = badges_list_criteria(false); 159 160 if (!isset($params['criteriatype']) || !isset($types[$params['criteriatype']])) { 161 throw new \moodle_exception('error:invalidcriteriatype', 'badges'); 162 } 163 164 $class = 'award_criteria_' . $types[$params['criteriatype']]; 165 require_once($CFG->dirroot . '/badges/criteria/' . $class . '.php'); 166 167 return new $class($params); 168 } 169 170 /** 171 * Return criteria title 172 * 173 * @return string 174 */ 175 public function get_title() { 176 return get_string('criteria_' . $this->criteriatype, 'badges'); 177 } 178 179 /** 180 * Get criteria details for displaying to users 181 * 182 * @param string $short Print short version of criteria 183 * @return string 184 */ 185 abstract public function get_details($short = ''); 186 187 /** 188 * Add appropriate criteria options to the form 189 * 190 */ 191 abstract public function get_options(&$mform); 192 193 /** 194 * Add appropriate parameter elements to the criteria form 195 * 196 */ 197 public function config_options(&$mform, $param) { 198 global $OUTPUT; 199 $prefix = $this->required_param . '_'; 200 201 if ($param['error']) { 202 $parameter[] =& $mform->createElement('advcheckbox', $prefix . $param['id'], '', 203 $OUTPUT->error_text($param['name']), null, array(0, $param['id'])); 204 $mform->addGroup($parameter, 'param_' . $prefix . $param['id'], '', array(' '), false); 205 } else { 206 $parameter[] =& $mform->createElement('advcheckbox', $prefix . $param['id'], '', $param['name'], null, array(0, $param['id'])); 207 $parameter[] =& $mform->createElement('static', 'break_start_' . $param['id'], null, 208 '<div class="ml-3 mt-1 w-100 align-items-center">'); 209 210 if (in_array('grade', $this->optional_params)) { 211 $parameter[] =& $mform->createElement('static', 'mgrade_' . $param['id'], null, get_string('mingrade', 'badges')); 212 $parameter[] =& $mform->createElement('text', 'grade_' . $param['id'], '', array('size' => '5')); 213 $mform->setType('grade_' . $param['id'], PARAM_INT); 214 } 215 216 if (in_array('bydate', $this->optional_params)) { 217 $parameter[] =& $mform->createElement('static', 'complby_' . $param['id'], null, get_string('bydate', 'badges')); 218 $parameter[] =& $mform->createElement('date_selector', 'bydate_' . $param['id'], "", array('optional' => true)); 219 } 220 221 $parameter[] =& $mform->createElement('static', 'break_end_' . $param['id'], null, '</div>'); 222 $mform->addGroup($parameter, 'param_' . $prefix . $param['id'], '', array(' '), false); 223 if (in_array('grade', $this->optional_params)) { 224 $mform->addGroupRule('param_' . $prefix . $param['id'], array( 225 'grade_' . $param['id'] => array(array(get_string('err_numeric', 'form'), 'numeric', '', 'client')))); 226 } 227 $mform->disabledIf('bydate_' . $param['id'] . '[day]', 'bydate_' . $param['id'] . '[enabled]', 'notchecked'); 228 $mform->disabledIf('bydate_' . $param['id'] . '[month]', 'bydate_' . $param['id'] . '[enabled]', 'notchecked'); 229 $mform->disabledIf('bydate_' . $param['id'] . '[year]', 'bydate_' . $param['id'] . '[enabled]', 'notchecked'); 230 $mform->disabledIf('param_' . $prefix . $param['id'], $prefix . $param['id'], 'notchecked'); 231 } 232 233 // Set default values. 234 $mform->setDefault($prefix . $param['id'], $param['checked']); 235 if (isset($param['bydate'])) { 236 $mform->setDefault('bydate_' . $param['id'], $param['bydate']); 237 } 238 if (isset($param['grade'])) { 239 $mform->setDefault('grade_' . $param['id'], $param['grade']); 240 } 241 } 242 243 /** 244 * Add appropriate criteria elements 245 * 246 * @param stdClass $data details of various criteria 247 */ 248 public function config_form_criteria($data) { 249 global $OUTPUT; 250 $agg = $data->get_aggregation_methods(); 251 252 $editurl = new moodle_url('/badges/criteria_settings.php', 253 array('badgeid' => $this->badgeid, 'edit' => true, 'type' => $this->criteriatype, 'crit' => $this->id)); 254 $deleteurl = new moodle_url('/badges/criteria_action.php', 255 array('badgeid' => $this->badgeid, 'delete' => true, 'type' => $this->criteriatype)); 256 $editaction = $OUTPUT->action_icon($editurl, new pix_icon('t/edit', get_string('edit')), null, array('class' => 'criteria-action')); 257 $deleteaction = $OUTPUT->action_icon($deleteurl, new pix_icon('t/delete', get_string('delete')), null, array('class' => 'criteria-action')); 258 259 echo $OUTPUT->box_start(); 260 if (!$data->is_locked() && !$data->is_active()) { 261 echo $OUTPUT->box($deleteaction . $editaction, array('criteria-header')); 262 } 263 echo $OUTPUT->heading($this->get_title() . $OUTPUT->help_icon('criteria_' . $this->criteriatype, 'badges'), 3, 'main help'); 264 265 if (!empty($this->description)) { 266 $badge = new badge($this->badgeid); 267 echo $OUTPUT->box( 268 format_text($this->description, $this->descriptionformat, array('context' => $badge->get_context())), 269 'criteria-description' 270 ); 271 } 272 273 if (!empty($this->params)) { 274 if (count($this->params) > 1) { 275 echo $OUTPUT->box(get_string('criteria_descr_' . $this->criteriatype, 'badges', 276 core_text::strtoupper($agg[$data->get_aggregation_method($this->criteriatype)])), array('clearfix')); 277 } else { 278 echo $OUTPUT->box(get_string('criteria_descr_single_' . $this->criteriatype , 'badges'), array('clearfix')); 279 } 280 echo $OUTPUT->box($this->get_details(), array('clearfix')); 281 } 282 echo $OUTPUT->box_end(); 283 } 284 285 /** 286 * Review this criteria and decide if the user has completed 287 * 288 * @param int $userid User whose criteria completion needs to be reviewed. 289 * @param bool $filtered An additional parameter indicating that user list 290 * has been reduced and some expensive checks can be skipped. 291 * 292 * @return bool Whether criteria is complete 293 */ 294 abstract public function review($userid, $filtered = false); 295 296 /** 297 * Returns array with sql code and parameters returning all ids 298 * of users who meet this particular criterion. 299 * 300 * @return array list($join, $where, $params) 301 */ 302 abstract public function get_completed_criteria_sql(); 303 304 /** 305 * Mark this criteria as complete for a user 306 * 307 * @param int $userid User whose criteria is completed. 308 */ 309 public function mark_complete($userid) { 310 global $DB; 311 $obj = array(); 312 $obj['critid'] = $this->id; 313 $obj['userid'] = $userid; 314 $obj['datemet'] = time(); 315 if (!$DB->record_exists('badge_criteria_met', array('critid' => $this->id, 'userid' => $userid))) { 316 $DB->insert_record('badge_criteria_met', $obj); 317 } 318 } 319 320 /** 321 * Return criteria parameters 322 * 323 * @param int $critid Criterion ID 324 * @return array 325 */ 326 public function get_params($cid) { 327 global $DB; 328 $params = array(); 329 330 $records = $DB->get_records('badge_criteria_param', array('critid' => $cid)); 331 foreach ($records as $rec) { 332 $arr = explode('_', $rec->name); 333 $params[$arr[1]][$arr[0]] = $rec->value; 334 } 335 336 return $params; 337 } 338 339 /** 340 * Delete this criterion 341 * 342 */ 343 public function delete() { 344 global $DB, $PAGE; 345 346 // Remove any records if it has already been met. 347 $DB->delete_records('badge_criteria_met', array('critid' => $this->id)); 348 349 // Remove all parameters records. 350 $DB->delete_records('badge_criteria_param', array('critid' => $this->id)); 351 352 // Finally remove criterion itself. 353 $DB->delete_records('badge_criteria', array('id' => $this->id)); 354 355 // Trigger event, badge criteria deleted. 356 $eventparams = array('objectid' => $this->id, 357 'context' => $PAGE->context, 358 'other' => array('badgeid' => $this->badgeid)); 359 $event = \core\event\badge_criteria_deleted::create($eventparams); 360 $event->trigger(); 361 } 362 363 /** 364 * Saves intial criteria records with required parameters set up. 365 * 366 * @param array $params Values from the form or any other array. 367 */ 368 public function save($params = array()) { 369 global $DB, $PAGE; 370 371 // Figure out criteria description. 372 // If it is coming from the form editor, it is an array(text, format). 373 $description = ''; 374 $descriptionformat = FORMAT_HTML; 375 if (isset($params['description']['text'])) { 376 $description = $params['description']['text']; 377 $descriptionformat = $params['description']['format']; 378 } else if (isset($params['description'])) { 379 $description = $params['description']; 380 } 381 382 $fordb = new stdClass(); 383 $fordb->criteriatype = $this->criteriatype; 384 $fordb->method = isset($params['agg']) ? $params['agg'] : BADGE_CRITERIA_AGGREGATION_ALL; 385 $fordb->badgeid = $this->badgeid; 386 $fordb->description = $description; 387 $fordb->descriptionformat = $descriptionformat; 388 $t = $DB->start_delegated_transaction(); 389 390 // Pick only params that are required by this criterion. 391 // Filter out empty values first. 392 $params = array_filter($params); 393 // Find out which param matches optional and required ones. 394 $match = array_merge($this->optional_params, array($this->required_param)); 395 $regex = implode('|', array_map(function($a) { 396 return $a . "_"; 397 }, $match)); 398 $requiredkeys = preg_grep('/^(' . $regex . ').*$/', array_keys($params)); 399 400 if ($this->id !== 0) { 401 $cid = $this->id; 402 403 // Update criteria before doing anything with parameters. 404 $fordb->id = $cid; 405 $DB->update_record('badge_criteria', $fordb, true); 406 407 // Trigger event: badge_criteria_updated. 408 $eventparams = array('objectid' => $this->id, 409 'context' => $PAGE->context, 410 'other' => array('badgeid' => $this->badgeid)); 411 $event = \core\event\badge_criteria_updated::create($eventparams); 412 $event->trigger(); 413 414 $existing = $DB->get_fieldset_select('badge_criteria_param', 'name', 'critid = ?', array($cid)); 415 $todelete = array_diff($existing, $requiredkeys); 416 417 if (!empty($todelete)) { 418 // A workaround to add some disabled elements that are still being submitted from the form. 419 foreach ($todelete as $del) { 420 $name = explode('_', $del); 421 if ($name[0] == $this->required_param) { 422 foreach ($this->optional_params as $opt) { 423 $todelete[] = $opt . '_' . $name[1]; 424 } 425 } 426 } 427 $todelete = array_unique($todelete); 428 list($sql, $sqlparams) = $DB->get_in_or_equal($todelete, SQL_PARAMS_NAMED, 'd', true); 429 $sqlparams = array_merge(array('critid' => $cid), $sqlparams); 430 $DB->delete_records_select('badge_criteria_param', 'critid = :critid AND name ' . $sql, $sqlparams); 431 } 432 433 foreach ($requiredkeys as $key) { 434 if (in_array($key, $existing)) { 435 $updp = $DB->get_record('badge_criteria_param', array('name' => $key, 'critid' => $cid)); 436 $updp->value = $params[$key]; 437 $DB->update_record('badge_criteria_param', $updp, true); 438 } else { 439 $newp = new stdClass(); 440 $newp->critid = $cid; 441 $newp->name = $key; 442 $newp->value = $params[$key]; 443 $DB->insert_record('badge_criteria_param', $newp); 444 } 445 } 446 } else { 447 $cid = $DB->insert_record('badge_criteria', $fordb, true); 448 if ($cid) { 449 foreach ($requiredkeys as $key) { 450 $newp = new stdClass(); 451 $newp->critid = $cid; 452 $newp->name = $key; 453 $newp->value = $params[$key]; 454 $DB->insert_record('badge_criteria_param', $newp, false, true); 455 } 456 } 457 // Trigger event: badge_criteria_created. 458 $eventparams = array('objectid' => $this->id, 459 'context' => $PAGE->context, 460 'other' => array('badgeid' => $this->badgeid)); 461 $event = \core\event\badge_criteria_created::create($eventparams); 462 $event->trigger(); 463 } 464 $t->allow_commit(); 465 } 466 467 /** 468 * Saves intial criteria records with required parameters set up. 469 */ 470 public function make_clone($newbadgeid) { 471 global $DB; 472 473 $fordb = new stdClass(); 474 $fordb->criteriatype = $this->criteriatype; 475 $fordb->method = $this->method; 476 $fordb->badgeid = $newbadgeid; 477 $fordb->description = $this->description; 478 $fordb->descriptionformat = $this->descriptionformat; 479 if (($newcrit = $DB->insert_record('badge_criteria', $fordb, true)) && isset($this->params)) { 480 foreach ($this->params as $k => $param) { 481 foreach ($param as $key => $value) { 482 $paramdb = new stdClass(); 483 $paramdb->critid = $newcrit; 484 $paramdb->name = $key . '_' . $k; 485 $paramdb->value = $value; 486 $DB->insert_record('badge_criteria_param', $paramdb); 487 } 488 } 489 } 490 } 491 492 /** 493 * Allow some specific criteria types to be disabled based on config. 494 * 495 * @return boolean 496 */ 497 public static function is_enabled() { 498 return true; 499 } 500 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body