Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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).'-&gt;'.$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  }