See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [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 * Contains class mod_feedback_complete_form 19 * 20 * @package mod_feedback 21 * @copyright 2016 Marina Glancy 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 /** 28 * Class mod_feedback_complete_form 29 * 30 * @package mod_feedback 31 * @copyright 2016 Marina Glancy 32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 */ 34 class mod_feedback_complete_form extends moodleform { 35 36 /** @var int */ 37 const MODE_COMPLETE = 1; 38 /** @var int */ 39 const MODE_PRINT = 2; 40 /** @var int */ 41 const MODE_EDIT = 3; 42 /** @var int */ 43 const MODE_VIEW_RESPONSE = 4; 44 /** @var int */ 45 const MODE_VIEW_TEMPLATE = 5; 46 47 /** @var int */ 48 protected $mode; 49 /** @var mod_feedback_structure|mod_feedback_completion */ 50 protected $structure; 51 /** @var mod_feedback_completion */ 52 protected $completion; 53 /** @var int */ 54 protected $gopage; 55 /** @var bool */ 56 protected $hasrequired = false; 57 58 /** 59 * Constructor 60 * 61 * @param int $mode 62 * @param mod_feedback_structure $structure 63 * @param string $formid CSS id attribute of the form 64 * @param array $customdata 65 */ 66 public function __construct($mode, mod_feedback_structure $structure, $formid, $customdata = null) { 67 $this->mode = $mode; 68 $this->structure = $structure; 69 $this->gopage = isset($customdata['gopage']) ? $customdata['gopage'] : 0; 70 $isanonymous = $this->structure->is_anonymous() ? ' ianonymous' : ''; 71 parent::__construct(null, $customdata, 'POST', '', 72 array('id' => $formid, 'class' => 'feedback_form' . $isanonymous), true); 73 $this->set_display_vertical(); 74 } 75 76 /** 77 * Form definition 78 */ 79 public function definition() { 80 $mform = $this->_form; 81 $mform->addElement('hidden', 'id', $this->get_cm()->id); 82 $mform->setType('id', PARAM_INT); 83 $mform->addElement('hidden', 'courseid', $this->get_current_course_id()); 84 $mform->setType('courseid', PARAM_INT); 85 $mform->addElement('hidden', 'gopage'); 86 $mform->setType('gopage', PARAM_INT); 87 $mform->addElement('hidden', 'lastpage'); 88 $mform->setType('lastpage', PARAM_INT); 89 $mform->addElement('hidden', 'startitempos'); 90 $mform->setType('startitempos', PARAM_INT); 91 $mform->addElement('hidden', 'lastitempos'); 92 $mform->setType('lastitempos', PARAM_INT); 93 94 if (isloggedin() && !isguestuser() && $this->mode != self::MODE_EDIT && $this->mode != self::MODE_VIEW_TEMPLATE && 95 $this->mode != self::MODE_VIEW_RESPONSE) { 96 // Output information about the current mode (anonymous or not) in some modes. 97 if ($this->structure->is_anonymous()) { 98 $anonymousmodeinfo = get_string('anonymous', 'feedback'); 99 } else { 100 $anonymousmodeinfo = get_string('non_anonymous', 'feedback'); 101 } 102 $element = $mform->addElement('static', 'anonymousmode', '', 103 get_string('mode', 'feedback') . ': ' . $anonymousmodeinfo); 104 $element->setAttributes($element->getAttributes() + ['class' => 'feedback_mode']); 105 } 106 107 // Add buttons to go to previous/next pages and submit the feedback. 108 if ($this->mode == self::MODE_COMPLETE) { 109 $buttonarray = array(); 110 $buttonarray[] = &$mform->createElement('submit', 'gopreviouspage', get_string('previous_page', 'feedback')); 111 $buttonarray[] = &$mform->createElement('submit', 'gonextpage', get_string('next_page', 'feedback'), 112 array('class' => 'form-submit')); 113 $buttonarray[] = &$mform->createElement('submit', 'savevalues', get_string('save_entries', 'feedback'), 114 array('class' => 'form-submit')); 115 $buttonarray[] = &$mform->createElement('cancel'); 116 $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false); 117 $mform->closeHeaderBefore('buttonar'); 118 } 119 120 if ($this->mode == self::MODE_COMPLETE) { 121 $this->definition_complete(); 122 } else { 123 $this->definition_preview(); 124 } 125 126 // Set data. 127 $this->set_data(array('gopage' => $this->gopage)); 128 } 129 130 /** 131 * Called from definition_after_data() in the completion mode 132 * 133 * This will add only items from a current page to the feedback and adjust the buttons 134 */ 135 protected function definition_complete() { 136 if (!$this->structure instanceof mod_feedback_completion) { 137 // We should not really be here but just in case. 138 return; 139 } 140 $pages = $this->structure->get_pages(); 141 $gopage = $this->gopage; 142 $pageitems = $pages[$gopage]; 143 $hasnextpage = $gopage < count($pages) - 1; // Until we complete this page we can not trust get_next_page(). 144 $hasprevpage = $gopage && ($this->structure->get_previous_page($gopage, false) !== null); 145 146 // Add elements. 147 foreach ($pageitems as $item) { 148 $itemobj = feedback_get_item_class($item->typ); 149 $itemobj->complete_form_element($item, $this); 150 } 151 152 // Remove invalid buttons (for example, no "previous page" if we are on the first page). 153 if (!$hasprevpage) { 154 $this->remove_button('gopreviouspage'); 155 } 156 if (!$hasnextpage) { 157 $this->remove_button('gonextpage'); 158 } 159 if ($hasnextpage) { 160 $this->remove_button('savevalues'); 161 } 162 } 163 164 /** 165 * Called from definition_after_data() in all modes except for completion 166 * 167 * This will add all items to the form, including pagebreaks as horizontal rules. 168 */ 169 protected function definition_preview() { 170 foreach ($this->structure->get_items() as $feedbackitem) { 171 $itemobj = feedback_get_item_class($feedbackitem->typ); 172 $itemobj->complete_form_element($feedbackitem, $this); 173 } 174 } 175 176 /** 177 * Removes the button that is not applicable for the current page 178 * 179 * @param string $buttonname 180 */ 181 private function remove_button($buttonname) { 182 $el = $this->_form->getElement('buttonar'); 183 foreach ($el->_elements as $idx => $button) { 184 if ($button instanceof MoodleQuickForm_submit && $button->getName() === $buttonname) { 185 unset($el->_elements[$idx]); 186 return; 187 } 188 } 189 } 190 191 /** 192 * Returns value for this element that is already stored in temporary or permanent table, 193 * usually only available when user clicked "Previous page". Null means no value is stored. 194 * 195 * @param stdClass $item 196 * @return string 197 */ 198 public function get_item_value($item) { 199 if ($this->structure instanceof mod_feedback_completion) { 200 return $this->structure->get_item_value($item); 201 } 202 return null; 203 } 204 205 /** 206 * Can be used by the items to get the course id for which feedback is taken 207 * 208 * This function returns 0 for feedbacks that are located inside the courses. 209 * $this->get_feedback()->course will return the course where feedback is located. 210 * $this->get_current_course_id() will return the course where user was before taking the feedback 211 * 212 * @return int 213 */ 214 public function get_course_id() { 215 return $this->structure->get_courseid(); 216 } 217 218 /** 219 * Record from 'feedback' table corresponding to the current feedback 220 * @return stdClass 221 */ 222 public function get_feedback() { 223 return $this->structure->get_feedback(); 224 } 225 226 /** 227 * Current feedback mode, see constants on the top of this class 228 * @return int 229 */ 230 public function get_mode() { 231 return $this->mode; 232 } 233 234 /** 235 * Returns whether the form is frozen, some items may prefer to change the element 236 * type in case of frozen form. For example, text or textarea element does not look 237 * nice when frozen 238 * 239 * @return bool 240 */ 241 public function is_frozen() { 242 return $this->mode == self::MODE_VIEW_RESPONSE; 243 } 244 245 /** 246 * Returns the current course module 247 * @return cm_info 248 */ 249 public function get_cm() { 250 return $this->structure->get_cm(); 251 } 252 253 /** 254 * Returns the course where user was before taking the feedback. 255 * 256 * For feedbacks inside the course it will be the same as $this->get_feedback()->course. 257 * For feedbacks on the frontpage it will be the same as $this->get_course_id() 258 * 259 * @return int 260 */ 261 public function get_current_course_id() { 262 return $this->structure->get_courseid() ?: $this->get_feedback()->course; 263 } 264 265 /** 266 * CSS class for the item 267 * @param stdClass $item 268 * @return string 269 */ 270 protected function get_suggested_class($item) { 271 $class = "feedback_itemlist feedback-item-{$item->typ}"; 272 if ($item->dependitem) { 273 $class .= " feedback_is_dependent"; 274 } 275 if ($item->typ !== 'pagebreak') { 276 $itemobj = feedback_get_item_class($item->typ); 277 if ($itemobj->get_hasvalue()) { 278 $class .= " feedback_hasvalue"; 279 } 280 } 281 return $class; 282 } 283 284 /** 285 * Adds an element to this form - to be used by items in their complete_form_element() method 286 * 287 * @param stdClass $item 288 * @param HTML_QuickForm_element|array $element either completed form element or an array that 289 * can be passed as arguments to $this->_form->createElement() function 290 * @param bool $addrequiredrule automatically add 'required' rule 291 * @param bool $setdefaultvalue automatically set default value for element 292 * @return HTML_QuickForm_element 293 */ 294 public function add_form_element($item, $element, $addrequiredrule = true, $setdefaultvalue = true) { 295 global $OUTPUT; 296 297 if (is_array($element) && $element[0] == 'group') { 298 // For groups, use the mforms addGroup API. 299 // $element looks like: ['group', $groupinputname, $name, $objects, $separator, $appendname], 300 $element = $this->_form->addGroup($element[3], $element[1], $element[2], $element[4], $element[5]); 301 } else { 302 // Add non-group element to the form. 303 if (is_array($element)) { 304 if ($this->is_frozen() && $element[0] === 'text') { 305 // Convert 'text' element to 'static' when freezing for better display. 306 $element = ['static', $element[1], $element[2]]; 307 } 308 $element = call_user_func_array(array($this->_form, 'createElement'), $element); 309 } 310 $element = $this->_form->addElement($element); 311 } 312 313 // Prepend standard CSS classes to the element classes. 314 $attributes = $element->getAttributes(); 315 $class = !empty($attributes['class']) ? ' ' . $attributes['class'] : ''; 316 $attributes['class'] = $this->get_suggested_class($item) . $class; 317 $element->setAttributes($attributes); 318 319 // Add required rule. 320 if ($item->required && $addrequiredrule) { 321 $this->_form->addRule($element->getName(), get_string('required'), 'required', null, 'client'); 322 } 323 324 // Set default value. 325 if ($setdefaultvalue && ($tmpvalue = $this->get_item_value($item))) { 326 $this->_form->setDefault($element->getName(), s($tmpvalue)); 327 } 328 329 // Freeze if needed. 330 if ($this->is_frozen()) { 331 $element->freeze(); 332 } 333 334 // Add red asterisks on required fields. 335 if ($item->required) { 336 $required = $OUTPUT->pix_icon('req', get_string('requiredelement', 'form')); 337 $element->setLabel($element->getLabel() . $required); 338 $this->hasrequired = true; 339 } 340 341 // Add different useful stuff to the question name. 342 $this->add_item_label($item, $element); 343 $this->add_item_dependencies($item, $element); 344 $this->add_item_number($item, $element); 345 346 if ($this->mode == self::MODE_EDIT) { 347 $this->enhance_name_for_edit($item, $element); 348 } 349 350 return $element; 351 } 352 353 /** 354 * Adds a group element to this form - to be used by items in their complete_form_element() method 355 * 356 * @param stdClass $item 357 * @param string $groupinputname name for the form element 358 * @param string $name question text 359 * @param array $elements array of arrays that can be passed to $this->_form->createElement() 360 * @param string $separator separator between group elements 361 * @param string $class additional CSS classes for the form element 362 * @return HTML_QuickForm_element 363 */ 364 public function add_form_group_element($item, $groupinputname, $name, $elements, $separator, 365 $class = '') { 366 $objects = array(); 367 foreach ($elements as $element) { 368 $object = call_user_func_array(array($this->_form, 'createElement'), $element); 369 $objects[] = $object; 370 } 371 $element = $this->add_form_element($item, 372 ['group', $groupinputname, $name, $objects, $separator, false], 373 false, 374 false); 375 if ($class !== '') { 376 $attributes = $element->getAttributes(); 377 $attributes['class'] .= ' ' . $class; 378 $element->setAttributes($attributes); 379 } 380 return $element; 381 } 382 383 /** 384 * Adds an item number to the question name (if feedback autonumbering is on) 385 * @param stdClass $item 386 * @param HTML_QuickForm_element $element 387 */ 388 protected function add_item_number($item, $element) { 389 if ($this->get_feedback()->autonumbering && !empty($item->itemnr)) { 390 $name = $element->getLabel(); 391 $element->setLabel(html_writer::span($item->itemnr. '.', 'itemnr') . ' ' . $name); 392 } 393 } 394 395 /** 396 * Adds an item label to the question name 397 * @param stdClass $item 398 * @param HTML_QuickForm_element $element 399 */ 400 protected function add_item_label($item, $element) { 401 if (strlen($item->label) && ($this->mode == self::MODE_EDIT || $this->mode == self::MODE_VIEW_TEMPLATE)) { 402 $name = get_string('nameandlabelformat', 'mod_feedback', 403 (object)['label' => format_string($item->label), 'name' => $element->getLabel()]); 404 $element->setLabel($name); 405 } 406 } 407 408 /** 409 * Adds a dependency description to the question name 410 * @param stdClass $item 411 * @param HTML_QuickForm_element $element 412 */ 413 protected function add_item_dependencies($item, $element) { 414 $allitems = $this->structure->get_items(); 415 if ($item->dependitem && ($this->mode == self::MODE_EDIT || $this->mode == self::MODE_VIEW_TEMPLATE)) { 416 if (isset($allitems[$item->dependitem])) { 417 $dependitem = $allitems[$item->dependitem]; 418 $name = $element->getLabel(); 419 $name .= html_writer::span(' ('.format_string($dependitem->label).'->'.$item->dependvalue.')', 420 'feedback_depend'); 421 $element->setLabel($name); 422 } 423 } 424 } 425 426 /** 427 * Returns the CSS id attribute that will be assigned by moodleform later to this element 428 * @param stdClass $item 429 * @param HTML_QuickForm_element $element 430 */ 431 protected function guess_element_id($item, $element) { 432 if (!$id = $element->getAttribute('id')) { 433 $attributes = $element->getAttributes(); 434 $id = $attributes['id'] = 'feedback_item_' . $item->id; 435 $element->setAttributes($attributes); 436 } 437 if ($element->getType() === 'group') { 438 return 'fgroup_' . $id; 439 } 440 return 'fitem_' . $id; 441 } 442 443 /** 444 * Adds editing actions to the question name in the edit mode 445 * @param stdClass $item 446 * @param HTML_QuickForm_element $element 447 */ 448 protected function enhance_name_for_edit($item, $element) { 449 global $OUTPUT; 450 $menu = new action_menu(); 451 $menu->set_owner_selector('#' . $this->guess_element_id($item, $element)); 452 $menu->set_constraint('.feedback_form'); 453 $menu->set_menu_trigger(get_string('edit')); 454 $menu->prioritise = true; 455 456 $itemobj = feedback_get_item_class($item->typ); 457 $actions = $itemobj->edit_actions($item, $this->get_feedback(), $this->get_cm()); 458 foreach ($actions as $action) { 459 $menu->add($action); 460 } 461 $editmenu = $OUTPUT->render($menu); 462 463 $name = $element->getLabel(); 464 465 $name = html_writer::span('', 'itemdd', array('id' => 'feedback_item_box_' . $item->id)) . 466 html_writer::span($name, 'itemname') . 467 html_writer::span($editmenu, 'itemactions'); 468 $element->setLabel(html_writer::span($name, 'itemtitle')); 469 } 470 471 /** 472 * Sets the default value for form element - alias to $this->_form->setDefault() 473 * @param HTML_QuickForm_element|string $element 474 * @param mixed $defaultvalue 475 */ 476 public function set_element_default($element, $defaultvalue) { 477 if ($element instanceof HTML_QuickForm_element) { 478 $element = $element->getName(); 479 } 480 $this->_form->setDefault($element, $defaultvalue); 481 } 482 483 484 /** 485 * Sets the default value for form element - wrapper to $this->_form->setType() 486 * @param HTML_QuickForm_element|string $element 487 * @param int $type 488 */ 489 public function set_element_type($element, $type) { 490 if ($element instanceof HTML_QuickForm_element) { 491 $element = $element->getName(); 492 } 493 $this->_form->setType($element, $type); 494 } 495 496 /** 497 * Adds a validation rule for the given field - wrapper for $this->_form->addRule() 498 * 499 * Do not use for 'required' rule! 500 * Required * will be added automatically, if additional validation is needed 501 * use method {@link self::add_validation_rule()} 502 * 503 * @param string $element Form element name 504 * @param string $message Message to display for invalid data 505 * @param string $type Rule type, use getRegisteredRules() to get types 506 * @param string $format (optional)Required for extra rule data 507 * @param string $validation (optional)Where to perform validation: "server", "client" 508 * @param bool $reset Client-side validation: reset the form element to its original value if there is an error? 509 * @param bool $force Force the rule to be applied, even if the target form element does not exist 510 */ 511 public function add_element_rule($element, $message, $type, $format = null, $validation = 'server', 512 $reset = false, $force = false) { 513 if ($element instanceof HTML_QuickForm_element) { 514 $element = $element->getName(); 515 } 516 $this->_form->addRule($element, $message, $type, $format, $validation, $reset, $force); 517 } 518 519 /** 520 * Adds a validation rule to the form 521 * 522 * @param callable $callback with arguments ($values, $files) 523 */ 524 public function add_validation_rule(callable $callback) { 525 if ($this->mode == self::MODE_COMPLETE) { 526 $this->_form->addFormRule($callback); 527 } 528 } 529 530 /** 531 * Returns a reference to the element - wrapper for function $this->_form->getElement() 532 * 533 * @param string $elementname Element name 534 * @return HTML_QuickForm_element reference to element 535 */ 536 public function get_form_element($elementname) { 537 return $this->_form->getElement($elementname); 538 } 539 540 /** 541 * Displays the form 542 */ 543 public function display() { 544 global $OUTPUT, $PAGE; 545 // Finalize the form definition if not yet done. 546 if (!$this->_definition_finalized) { 547 $this->_definition_finalized = true; 548 $this->definition_after_data(); 549 } 550 551 $mform = $this->_form; 552 553 // Add "This form has required fields" text in the bottom of the form. 554 if (($mform->_required || $this->hasrequired) && 555 ($this->mode == self::MODE_COMPLETE || $this->mode == self::MODE_PRINT || $this->mode == self::MODE_VIEW_TEMPLATE)) { 556 $element = $mform->addElement('static', 'requiredfields', '', 557 get_string('somefieldsrequired', 'form', 558 $OUTPUT->pix_icon('req', get_string('requiredelement', 'form')))); 559 $element->setAttributes($element->getAttributes() + ['class' => 'requirednote']); 560 } 561 562 // Reset _required array so the default red * are not displayed. 563 $mform->_required = array(); 564 565 // Move buttons to the end of the form. 566 if ($this->mode == self::MODE_COMPLETE) { 567 $mform->addElement('hidden', '__dummyelement'); 568 $buttons = $mform->removeElement('buttonar', false); 569 $mform->insertElementBefore($buttons, '__dummyelement'); 570 $mform->removeElement('__dummyelement'); 571 } 572 573 $this->_form->display(); 574 575 if ($this->mode == self::MODE_EDIT) { 576 $PAGE->requires->js_call_amd('mod_feedback/edit', 'setup'); 577 } 578 } 579 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body