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 * This file contains the marking guide editor element 19 * 20 * @package gradingform_guide 21 * @copyright 2012 Dan Marsden <dan@danmarsden.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 require_once("HTML/QuickForm/input.php"); 28 29 /** 30 * The editor for the marking guide advanced grading plugin. 31 * 32 * @package gradingform_guide 33 * @copyright 2012 Dan Marsden <dan@danmarsden.com> 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36 class moodlequickform_guideeditor extends HTML_QuickForm_input { 37 /** @var string help message */ 38 public $_helpbutton = ''; 39 /** @var null|false|string stores the result of the last validation: null - undefined, false - no errors, 40 * string - error(s) text */ 41 protected $validationerrors = null; 42 /** @var bool if element has already been validated **/ 43 protected $wasvalidated = false; 44 /** @var null|bool If non-submit (JS) button was pressed: null - unknown, true/false - button was/wasn't pressed */ 45 protected $nonjsbuttonpressed = false; 46 /** @var string|false Message to display in front of the editor (that there exist grades on this guide being edited) */ 47 protected $regradeconfirmation = false; 48 49 /** 50 * Constructor 51 * 52 * @param string $elementname 53 * @param string $elementlabel 54 * @param array $attributes 55 */ 56 public function __construct($elementname=null, $elementlabel=null, $attributes=null) { 57 parent::__construct($elementname, $elementlabel, $attributes); 58 } 59 60 /** 61 * Old syntax of class constructor. Deprecated in PHP7. 62 * 63 * @deprecated since Moodle 3.1 64 */ 65 public function moodlequickform_guideeditor($elementname=null, $elementlabel=null, $attributes=null) { 66 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 67 self::__construct($elementname, $elementlabel, $attributes); 68 } 69 70 /** 71 * get html for help button 72 * 73 * @return string html for help button 74 */ 75 public function getHelpButton() { 76 return $this->_helpbutton; 77 } 78 79 /** 80 * The renderer will take care itself about different display in normal and frozen states 81 * 82 * @return string 83 */ 84 public function getElementTemplateType() { 85 return 'default'; 86 } 87 88 /** 89 * Specifies that confirmation about re-grading needs to be added to this rubric editor. 90 * $changelevel is saved in $this->regradeconfirmation and retrieved in toHtml() 91 * 92 * @see gradingform_rubric_controller::update_or_check_rubric() 93 * @param int $changelevel 94 */ 95 public function add_regrade_confirmation($changelevel) { 96 $this->regradeconfirmation = $changelevel; 97 } 98 99 /** 100 * Returns html string to display this element 101 * 102 * @return string 103 */ 104 public function toHtml() { 105 global $PAGE; 106 $html = $this->_getTabs(); 107 $renderer = $PAGE->get_renderer('gradingform_guide'); 108 $data = $this->prepare_data(null, $this->wasvalidated); 109 if (!$this->_flagFrozen) { 110 $mode = gradingform_guide_controller::DISPLAY_EDIT_FULL; 111 $module = array('name'=>'gradingform_guideeditor', 112 'fullpath'=>'/grade/grading/form/guide/js/guideeditor.js', 113 'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'), 114 'strings' => array( 115 array('confirmdeletecriterion', 'gradingform_guide'), 116 array('clicktoedit', 'gradingform_guide'), 117 array('clicktoeditname', 'gradingform_guide') 118 )); 119 $PAGE->requires->js_init_call('M.gradingform_guideeditor.init', array( 120 array('name' => $this->getName(), 121 'criteriontemplate' => $renderer->criterion_template($mode, $data['options'], $this->getName()), 122 'commenttemplate' => $renderer->comment_template($mode, $this->getName()) 123 )), 124 true, $module); 125 } else { 126 // Guide is frozen, no javascript needed. 127 if ($this->_persistantFreeze) { 128 $mode = gradingform_guide_controller::DISPLAY_EDIT_FROZEN; 129 } else { 130 $mode = gradingform_guide_controller::DISPLAY_PREVIEW; 131 } 132 } 133 if ($this->regradeconfirmation) { 134 if (!isset($data['regrade'])) { 135 $data['regrade'] = 1; 136 } 137 $html .= $renderer->display_regrade_confirmation($this->getName(), $this->regradeconfirmation, $data['regrade']); 138 } 139 if ($this->validationerrors) { 140 $html .= html_writer::div($renderer->notification($this->validationerrors)); 141 } 142 $html .= $renderer->display_guide($data['criteria'], $data['comments'], $data['options'], $mode, $this->getName()); 143 return $html; 144 } 145 /** 146 * Prepares the data passed in $_POST: 147 * - processes the pressed buttons 'addlevel', 'addcriterion', 'moveup', 'movedown', 'delete' (when JavaScript is disabled) 148 * sets $this->nonjsbuttonpressed to true/false if such button was pressed 149 * - if options not passed (i.e. we create a new guide) fills the options array with the default values 150 * - if options are passed completes the options array with unchecked checkboxes 151 * - if $withvalidation is set, adds 'error_xxx' attributes to elements that contain errors and creates an error string 152 * and stores it in $this->validationerrors 153 * 154 * @param array $value 155 * @param boolean $withvalidation whether to enable data validation 156 * @return array 157 */ 158 protected function prepare_data($value = null, $withvalidation = false) { 159 if (null === $value) { 160 $value = $this->getValue(); 161 } 162 if ($this->nonjsbuttonpressed === null) { 163 $this->nonjsbuttonpressed = false; 164 } 165 166 $errors = array(); 167 $return = array('criteria' => array(), 'options' => gradingform_guide_controller::get_default_options(), 168 'comments' => array()); 169 if (!isset($value['criteria'])) { 170 $value['criteria'] = array(); 171 $errors['err_nocriteria'] = 1; 172 } 173 // If options are present in $value, replace default values with submitted values. 174 if (!empty($value['options'])) { 175 foreach (array_keys($return['options']) as $option) { 176 // Special treatment for checkboxes. 177 if (!empty($value['options'][$option])) { 178 $return['options'][$option] = $value['options'][$option]; 179 } else { 180 $return['options'][$option] = null; 181 } 182 183 } 184 } 185 186 if (is_array($value)) { 187 // For other array keys of $value no special treatmeant neeeded, copy them to return value as is. 188 foreach (array_keys($value) as $key) { 189 if ($key != 'options' && $key != 'criteria' && $key != 'comments') { 190 $return[$key] = $value[$key]; 191 } 192 } 193 } 194 195 // Iterate through criteria. 196 $lastaction = null; 197 $lastid = null; 198 foreach ($value['criteria'] as $id => $criterion) { 199 if ($id == 'addcriterion') { 200 $id = $this->get_next_id(array_keys($value['criteria'])); 201 $criterion = array('description' => ''); 202 $this->nonjsbuttonpressed = true; 203 } 204 205 if ($withvalidation && !array_key_exists('delete', $criterion)) { 206 if (!strlen(trim($criterion['shortname']))) { 207 $errors['err_noshortname'] = 1; 208 $criterion['error_description'] = true; 209 } 210 if (strlen(trim($criterion['shortname'])) > 255) { 211 $errors['err_shortnametoolong'] = 1; 212 $criterion['error_description'] = true; 213 } 214 if (!strlen(trim($criterion['maxscore']))) { 215 $errors['err_nomaxscore'] = 1; 216 $criterion['error_description'] = true; 217 } else if (!is_numeric($criterion['maxscore'])) { 218 $errors['err_maxscorenotnumeric'] = 1; 219 $criterion['error_description'] = true; 220 } else if ($criterion['maxscore'] < 0) { 221 $errors['err_maxscoreisnegative'] = 1; 222 $criterion['error_description'] = true; 223 } 224 } 225 if (array_key_exists('moveup', $criterion) || $lastaction == 'movedown') { 226 unset($criterion['moveup']); 227 if ($lastid !== null) { 228 $lastcriterion = $return['criteria'][$lastid]; 229 unset($return['criteria'][$lastid]); 230 $return['criteria'][$id] = $criterion; 231 $return['criteria'][$lastid] = $lastcriterion; 232 } else { 233 $return['criteria'][$id] = $criterion; 234 } 235 $lastaction = null; 236 $lastid = $id; 237 $this->nonjsbuttonpressed = true; 238 } else if (array_key_exists('delete', $criterion)) { 239 $this->nonjsbuttonpressed = true; 240 } else { 241 if (array_key_exists('movedown', $criterion)) { 242 unset($criterion['movedown']); 243 $lastaction = 'movedown'; 244 $this->nonjsbuttonpressed = true; 245 } 246 $return['criteria'][$id] = $criterion; 247 $lastid = $id; 248 } 249 } 250 251 // Add sort order field to criteria. 252 $csortorder = 1; 253 foreach (array_keys($return['criteria']) as $id) { 254 $return['criteria'][$id]['sortorder'] = $csortorder++; 255 } 256 257 // Iterate through comments. 258 $lastaction = null; 259 $lastid = null; 260 if (!empty($value['comments'])) { 261 foreach ($value['comments'] as $id => $comment) { 262 if ($id == 'addcomment') { 263 $id = $this->get_next_id(array_keys($value['comments'])); 264 $comment = array('description' => ''); 265 $this->nonjsbuttonpressed = true; 266 } 267 268 if (array_key_exists('moveup', $comment) || $lastaction == 'movedown') { 269 unset($comment['moveup']); 270 if ($lastid !== null) { 271 $lastcomment = $return['comments'][$lastid]; 272 unset($return['comments'][$lastid]); 273 $return['comments'][$id] = $comment; 274 $return['comments'][$lastid] = $lastcomment; 275 } else { 276 $return['comments'][$id] = $comment; 277 } 278 $lastaction = null; 279 $lastid = $id; 280 $this->nonjsbuttonpressed = true; 281 } else if (array_key_exists('delete', $comment)) { 282 $this->nonjsbuttonpressed = true; 283 } else { 284 if (array_key_exists('movedown', $comment)) { 285 unset($comment['movedown']); 286 $lastaction = 'movedown'; 287 $this->nonjsbuttonpressed = true; 288 } 289 $return['comments'][$id] = $comment; 290 $lastid = $id; 291 } 292 } 293 // Add sort order field to comments. 294 $csortorder = 1; 295 foreach (array_keys($return['comments']) as $id) { 296 $return['comments'][$id]['sortorder'] = $csortorder++; 297 } 298 } 299 // Create validation error string (if needed). 300 if ($withvalidation) { 301 if (count($errors)) { 302 $rv = array(); 303 foreach ($errors as $error => $v) { 304 $rv[] = get_string($error, 'gradingform_guide'); 305 } 306 $this->validationerrors = join('<br/ >', $rv); 307 } else { 308 $this->validationerrors = false; 309 } 310 $this->wasvalidated = true; 311 } 312 return $return; 313 314 } 315 316 /** 317 * Scans array $ids to find the biggest element ! NEWID*, increments it by 1 and returns 318 * 319 * @param array $ids 320 * @return string 321 */ 322 protected function get_next_id($ids) { 323 $maxid = 0; 324 foreach ($ids as $id) { 325 if (preg_match('/^NEWID(\d+)$/', $id, $matches) && ((int)$matches[1]) > $maxid) { 326 $maxid = (int)$matches[1]; 327 } 328 } 329 return 'NEWID'.($maxid+1); 330 } 331 332 /** 333 * Checks if a submit button was pressed which is supposed to be processed on client side by JS 334 * but user seem to have disabled JS in the browser. 335 * (buttons 'add criteria', 'add level', 'move up', 'move down', 'add comment') 336 * In this case the form containing this element is prevented from being submitted 337 * 338 * @param array $value 339 * @return boolean true if non-submit button was pressed and not processed by JS 340 */ 341 public function non_js_button_pressed($value) { 342 if ($this->nonjsbuttonpressed === null) { 343 $this->prepare_data($value); 344 } 345 return $this->nonjsbuttonpressed; 346 } 347 348 /** 349 * Validates that guide has at least one criterion, filled definitions and all criteria 350 * have filled descriptions 351 * 352 * @param array $value 353 * @return string|false error text or false if no errors found 354 */ 355 public function validate($value) { 356 if (!$this->wasvalidated) { 357 $this->prepare_data($value, true); 358 } 359 return $this->validationerrors; 360 } 361 362 /** 363 * Prepares the data for saving 364 * @see prepare_data() 365 * 366 * @param array $submitvalues 367 * @param boolean $assoc 368 * @return array 369 */ 370 public function exportValue(&$submitvalues, $assoc = false) { 371 $value = $this->prepare_data($this->_findValue($submitvalues)); 372 return $this->_prepareValue($value, $assoc); 373 } 374 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body