PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/mod/feedback/classes/complete_form.php

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