Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

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