Proyectos de Subversion Moodle

Rev

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