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
namespace mod_questionnaire\feedback;
18
 
19
use invalid_parameter_exception;
20
use coding_exception;
21
 
22
/**
23
 * Class for describing a feedback section.
24
 *
25
 * @package    mod_questionnaire
26
 * @copyright  2018 onward Mike Churchward (mike.churchward@poetopensource.org)
27
 * @author     Mike Churchward
28
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29
 */
30
class section {
31
 
32
    /** @var int */
33
    public $id = 0;
34
    /** @var int */
35
    public $surveyid = 0;
36
    /** @var int */
37
    public $section = 1;
38
    /** @var array */
39
    public $scorecalculation = [];
40
    /** @var string */
41
    public $sectionlabel = '';
42
    /** @var string */
43
    public $sectionheading = '';
44
    /** @var string */
45
    public $sectionheadingformat = FORMAT_HTML;
46
    /** @var array */
47
    public $sectionfeedback = [];
48
    /** @var array */
49
    public $questions = [];
50
 
51
    /** The table name. */
52
    const TABLE = 'questionnaire_fb_sections';
53
    /** Represents the "no score" setting. */
54
    const NOSCORE = -1;
55
 
56
    /**
57
     * Section constructor.
58
     * if $params is provided, loads the entire feedback section record from the specified parameters. Parameters can be:
59
     *  'id' - the id field of the fb_sections table (required if no 'surveyid' field),
60
     *  'surveyid' - the surveyid field of the fb_sections table (required if no 'id' field),
61
     *  'sectionnum' - the section field of the fb_sections table (ignored if 'id' is present; defaults to 1).
62
     *
63
     * @param array $questions Array of mod_questionnaire\question objects.
64
     * @param array $params As above
65
     * @throws \dml_exception
66
     * @throws coding_exception
67
     * @throws invalid_parameter_exception
68
     */
69
    public function __construct($questions, $params = []) {
70
 
71
        if (!is_array($params) || !is_array($questions)) {
72
            throw new coding_exception('Invalid data provided.');
73
        }
74
 
75
        $this->questions = $questions;
76
 
77
        // Return a new section based on the data parameters if present.
78
        if (isset($params['id']) || isset($params['surveyid'])) {
79
            $this->load_section($params);
80
        }
81
    }
82
 
83
    /**
84
     * Factory method to create a new, empty section and return an instance.
85
     * @param int $surveyid
86
     * @param string $sectionlabel
87
     * @return section
88
     */
89
    public static function new_section($surveyid, $sectionlabel = '') {
90
        global $DB;
91
 
92
        $newsection = new self([], []);
93
        if (empty($sectionlabel)) {
94
            $sectionlabel = get_string('feedbackdefaultlabel', 'questionnaire');
95
        }
96
        $maxsection = $DB->get_field(self::TABLE, 'MAX(section)', ['surveyid' => $surveyid]);
97
        $newsection->surveyid = $surveyid;
98
        $newsection->section = $maxsection + 1;
99
        $newsection->sectionlabel = $sectionlabel;
100
        $newsection->scorecalculation = $newsection->encode_scorecalculation([]);
101
        $newsecid = $DB->insert_record(self::TABLE, $newsection);
102
        $newsection->id = $newsecid;
103
        $newsection->scorecalculation = [];
104
        return $newsection;
105
    }
106
 
107
    /**
108
     * Loads the entire feedback section record from the specified parameters. Parameters can be:
109
     *  'id' - the id field of the fb_sections table (required if no 'surveyid' field),
110
     *  'surveyid' - the surveyid field of the fb_sections table (required if no 'id' field),
111
     *  'sectionnum' - the section field of the fb_sections table (ignored if 'id' is present; defaults to 1).
112
     *
113
     * @param array $params
114
     * @throws \dml_exception
115
     * @throws coding_exception
116
     * @throws invalid_parameter_exception
117
     */
118
    public function load_section($params) {
119
        global $DB;
120
 
121
        if (!is_array($params)) {
122
            throw new coding_exception('Invalid data provided.');
123
        } else if (isset($params['id'])) {
124
            $where = 'WHERE fs.id = :id ';
125
        } else if (isset($params['surveyid'])) {
126
            $where = 'WHERE fs.surveyid = :surveyid AND fs.section = :sectionnum ';
127
            if (!isset($params['sectionnum'])) {
128
                $params['sectionnum'] = 1;
129
            }
130
        } else {
131
            throw new coding_exception('No valid data parameters provided.');
132
        }
133
 
134
        $select = 'SELECT f.id as fbid, fs.*, f.feedbacklabel, f.feedbacktext, f.feedbacktextformat, f.minscore, f.maxscore ';
135
        $from = 'FROM {' . self::TABLE . '} fs LEFT JOIN {' . sectionfeedback::TABLE . '} f ON fs.id = f.sectionid ';
136
        $order = 'ORDER BY minscore DESC';
137
 
138
        if (!($feedbackrecs = $DB->get_records_sql($select . $from . $where . $order, $params))) {
139
            throw new invalid_parameter_exception('No feedback sections exists for that data.');
140
        }
141
        foreach ($feedbackrecs as $fbid => $feedbackrec) {
142
            if (empty($this->id)) {
143
                $this->id = $feedbackrec->id;
144
                $this->surveyid = $feedbackrec->surveyid;
145
                $this->section = $feedbackrec->section;
146
                $this->scorecalculation = $this->get_valid_scorecalculation($feedbackrec->scorecalculation);
147
                $this->sectionlabel = $feedbackrec->sectionlabel;
148
                $this->sectionheading = $feedbackrec->sectionheading;
149
                $this->sectionheadingformat = $feedbackrec->sectionheadingformat;
150
            }
151
            if (!empty($fbid)) {
152
                $feedbackrec->id = $fbid;
153
                $this->sectionfeedback[$fbid] = new sectionfeedback(0, $feedbackrec);
154
            }
155
        }
156
    }
157
 
158
    /**
159
     * Loads the section feedback record into the proper array location.
160
     *
161
     * @param \stdClass $feedbackrec
162
     * @return int The id of the section feedback record.
163
     */
164
    public function load_sectionfeedback($feedbackrec) {
165
        if (!isset($feedbackrec->id) || empty($feedbackrec->id)) {
166
            $sectionfeedback = sectionfeedback::new_sectionfeedback($feedbackrec);
167
            $this->sectionfeedback[$sectionfeedback->id] = $sectionfeedback;
168
            return $sectionfeedback->id;
169
        } else {
170
            $this->sectionfeedback[$feedbackrec->id] = new sectionfeedback(0, $feedbackrec);
171
            return $feedbackrec->id;
172
        }
173
    }
174
 
175
    /**
176
     * Updates the object and data record with a new scorecalculation. If no new score provided, uses what's in the object.
177
     *
178
     * @param array $scorecalculation
179
     * @throws coding_exception
180
     */
181
    public function set_new_scorecalculation($scorecalculation = null) {
182
        global $DB;
183
 
184
        if ($scorecalculation == null) {
185
            $scorecalculation = $this->scorecalculation;
186
        }
187
 
188
        if (is_array($scorecalculation)) {
189
            $newscore = $this->encode_scorecalculation($scorecalculation);
190
            $DB->set_field(self::TABLE, 'scorecalculation', $newscore, ['id' => $this->id]);
191
            $this->scorecalculation = $scorecalculation;
192
        } else {
193
            throw new coding_exception('Invalid scorecalculation format.');
194
        }
195
    }
196
 
197
    /**
198
     * Removes the question from this section and updates the database.
199
     *
200
     * @param int $qid The question id and index.
201
     * @throws \dml_exception
202
     * @throws coding_exception
203
     */
204
    public function remove_question($qid) {
205
        if (isset($this->scorecalculation[$qid])) {
206
            unset($this->scorecalculation[$qid]);
207
            $this->set_new_scorecalculation();
208
        }
209
    }
210
 
211
    /**
212
     * Deletes this section from the database. Object is invalid after that.
213
     * This will also adjust the section numbers so that they are sequential and begin at 1.
214
     */
215
    public function delete() {
216
        global $DB;
217
 
218
        $this->delete_sectionfeedback();
219
        $DB->delete_records(self::TABLE, ['id' => $this->id]);
220
 
221
        // Resequence the section numbers as necessary.
222
        if ($allsections = $DB->get_records(self::TABLE, ['surveyid' => $this->surveyid], 'section ASC')) {
223
            $count = 1;
224
            foreach ($allsections as $id => $section) {
225
                if ($section->section != $count) {
226
                    $DB->set_field(self::TABLE, 'section', $count, ['id' => $id]);
227
                }
228
                $count++;
229
            }
230
        }
231
    }
232
 
233
    /**
234
     * Deletes the section feedback records from the database and clears the object array.
235
     *
236
     * @throws \dml_exception
237
     */
238
    public function delete_sectionfeedback() {
239
        global $DB;
240
 
241
        // It's quicker to delete all of the records at once then to go through the array and delete each object.
242
        $DB->delete_records(sectionfeedback::TABLE, ['sectionid' => $this->id]);
243
        $this->sectionfeedback = [];
244
    }
245
 
246
    /**
247
     * Updates the data record with what is currently in the object instance.
248
     *
249
     * @throws \dml_exception
250
     * @throws coding_exception
251
     */
252
    public function update() {
253
        global $DB;
254
 
255
        $this->scorecalculation = $this->encode_scorecalculation($this->scorecalculation);
256
        $DB->update_record(self::TABLE, $this);
257
        $this->scorecalculation = $this->get_valid_scorecalculation($this->scorecalculation);
258
 
259
        foreach ($this->sectionfeedback as $sectionfeedback) {
260
            $sectionfeedback->update();
261
        }
262
    }
263
 
264
    /**
265
     * Decode and ensure scorecalculation is what we expect.
266
     * @param string|null $codedstring
267
     * @return array
268
     * @throws coding_exception
269
     */
270
    public static function decode_scorecalculation(?string $codedstring): array {
271
        // Expect a serialized data string.
272
        if (($codedstring == null)) {
273
            $codedstring = '';
274
        }
275
        if (!is_string($codedstring)) {
276
            throw new coding_exception('Invalid scorecalculation format.');
277
        }
278
        if (!empty($codedstring)) {
279
            $scorecalculation = unserialize_array($codedstring) ?: [];
280
        } else {
281
            $scorecalculation = [];
282
        }
283
 
284
        if (!is_array($scorecalculation)) {
285
            throw new coding_exception('Invalid scorecalculation format.');
286
        }
287
 
288
        foreach ($scorecalculation as $score) {
289
            if (!empty($score) && !is_numeric($score)) {
290
                throw new coding_exception('Invalid scorecalculation format.');
291
            }
292
        }
293
 
294
        return $scorecalculation;
295
    }
296
 
297
    /**
298
     * Return the decoded and validated calculation array.
299
     * @param string $codedstring
300
     * @return mixed
301
     * @throws coding_exception
302
     */
303
    protected function get_valid_scorecalculation($codedstring) {
304
        $scorecalculation = static::decode_scorecalculation($codedstring);
305
 
306
        // Check for deleted questions and questions that don't support scores.
307
        foreach ($scorecalculation as $qid => $score) {
308
            if (!isset($this->questions[$qid])) {
309
                unset($scorecalculation[$qid]);
310
            } else if (!$this->questions[$qid]->supports_feedback_scores()) {
311
                $scorecalculation[$qid] = self::NOSCORE;
312
            }
313
        }
314
 
315
        return $scorecalculation;
316
    }
317
 
318
    /**
319
     * Return the encoded score array as a serialized string.
320
     * @param string $scorearray
321
     * @return mixed
322
     * @throws coding_exception
323
     */
324
    protected function encode_scorecalculation($scorearray) {
325
        // Expect an array.
326
        if (!is_array($scorearray)) {
327
            throw new coding_exception('Invalid scorearray format.');
328
        }
329
 
330
        $scorecalculation = serialize($scorearray);
331
 
332
        return $scorecalculation;
333
    }
334
}