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 tool_courserating;
18
 
19
use core\output\inplace_editable;
20
use tool_courserating\event\flag_created;
21
use tool_courserating\event\flag_deleted;
22
use tool_courserating\event\rating_created;
23
use tool_courserating\event\rating_deleted;
24
use tool_courserating\event\rating_updated;
25
use tool_courserating\external\summary_exporter;
26
use tool_courserating\local\models\flag;
27
use tool_courserating\local\models\rating;
28
use tool_courserating\local\models\summary;
29
 
30
/**
31
 * Methods to add/remove ratings
32
 *
33
 * @package     tool_courserating
34
 * @copyright   2022 Marina Glancy <marina.glancy@gmail.com>
35
 * @license     https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 */
37
class api {
38
 
39
    /**
40
     * Add or update user rating
41
     *
42
     * @param int $courseid
43
     * @param \stdClass $data
44
     * @param int $userid - only for phpunit and behat tests
45
     * @return rating
46
     */
47
    public static function set_rating(int $courseid, \stdClass $data, int $userid = 0): rating {
48
        global $USER;
49
        // TODO $userid can only be used in phpunit and behat.
50
        $userid = $userid ?: $USER->id;
51
        // TODO validate rating is within limits, trim/crop review.
52
        $rating = $data->rating;
53
        $ratingold = rating::get_record(['userid' => $userid, 'courseid' => $courseid]);
54
        if ($ratingold) {
55
            $oldrecord = $ratingold->to_record();
56
            $r = $ratingold;
57
            $review = self::prepare_review($r, $data);
58
            $r->set('rating', $rating);
59
            $r->set('review', $review);
60
            $r->save();
61
            $summary = summary::update_rating($courseid, $r, $oldrecord);
62
        } else {
63
            $r = new rating(0, (object)[
64
                'userid' => $userid,
65
                'courseid' => $courseid,
66
                'rating' => $rating,
67
            ]);
68
            $r->set('review', self::prepare_review(null, $data));
69
            $r->save();
70
            $review = self::prepare_review($r, $data);
71
            if ($review !== $r->get('review')) {
72
                $r->set('review', $review);
73
                $r->save();
74
            }
75
            $summary = summary::add_rating($courseid, $r);
76
        }
77
        self::update_course_rating_in_custom_field($summary);
78
 
79
        if ($ratingold) {
80
            rating_updated::create_from_rating($r, $oldrecord)->trigger();
81
        } else {
82
            rating_created::create_from_rating($r)->trigger();
83
        }
84
 
85
        return $r;
86
    }
87
 
88
    /**
89
     * Delete rating and review made by somebody else
90
     *
91
     * @param int $ratingid
92
     * @param string|null $reason
93
     * @return rating|null
94
     */
95
    public static function delete_rating(int $ratingid, string $reason = ''): ?rating {
96
        global $DB;
97
        if (!$rating = rating::get_record(['id' => $ratingid])) {
98
            return null;
99
        }
100
        $flagcount = $DB->count_records(flag::TABLE, ['ratingid' => $ratingid]);
101
        $record = $rating->to_record();
102
        $rating->delete();
103
        if ($context = \context_course::instance($record->courseid, IGNORE_MISSING)) {
104
            // Sometimes it might be called after course is deleted.
105
            get_file_storage()->delete_area_files($context->id, 'tool_courserating', 'review', $record->id);
106
        }
107
        if ($flagcount) {
108
            $DB->delete_records(flag::TABLE, ['ratingid' => $record->id]);
109
        }
110
        $summary = summary::delete_rating($record);
111
        self::update_course_rating_in_custom_field($summary);
112
 
113
        rating_deleted::create_from_rating($record, $flagcount, $reason)->trigger();
114
 
115
        return $rating;
116
    }
117
 
118
    /**
119
     * Update content of the course custom field that displays the rating
120
     *
121
     * @param summary|null $summary
122
     * @return void
123
     */
124
    protected static function update_course_rating_in_custom_field(?summary $summary) {
125
        global $PAGE;
126
        if (!$summary || !helper::get_course_rating_field()) {
127
            return;
128
        }
129
        $courseid = $summary->get('courseid');
130
 
131
        if ($summary->get('ratingmode') == constants::RATEBY_NOONE) {
132
            $ratingstr = '';
133
        } else {
134
            /** @var \tool_courserating\output\renderer $output */
135
            $output = $PAGE->get_renderer('tool_courserating');
136
            $data = (new summary_exporter(0, $summary))->export($output);
137
            $ratingstr = $output->render_from_template('tool_courserating/summary_for_cfield', $data);
138
        }
139
 
140
        if ($data = helper::get_course_rating_data_in_cfield($courseid)) {
141
            $data->instance_form_save((object)[
142
                'id' => $courseid,
143
                $data->get_form_element_name() => ['text' => $ratingstr, 'format' => FORMAT_HTML],
144
            ]);
145
        }
146
    }
147
 
148
    /**
149
     * Prepare review for storing (store files, convert to html)
150
     *
151
     * @param rating|null $rating
152
     * @param \stdClass $data
153
     * @return string
154
     */
155
    protected static function prepare_review(?rating $rating, \stdClass $data): string {
156
        $usehtml = helper::get_setting(constants::SETTING_USEHTML);
157
        if ($rating && $usehtml && !rating::review_is_empty($data->review_editor['text'] ?? '')) {
158
            $context = \context_course::instance($rating->get('courseid'));
159
            $data = file_postupdate_standard_editor($data, 'review', helper::review_editor_options($context), $context,
160
                'tool_courserating', 'review', $rating->get('id'));
161
            if ($data->reviewformat != FORMAT_HTML) {
162
                // We always store reviews as HTML, we don't even store the reviewformat field.
163
                // Do not apply filters now, they will be applied during display.
164
                return format_text($data->review, $data->reviewformat, ['filter' => false, 'context' => $context]);
165
            }
166
            return $data->review;
167
        } else if (!$usehtml && !rating::review_is_empty($data->review ?? '')) {
168
            return $data->review;
169
        } else {
170
            return '';
171
        }
172
    }
173
 
174
    /**
175
     * Prepare review to be displayed in a form (copy files to draft area)
176
     *
177
     * @param int $courseid
178
     * @return array|array[]
179
     */
180
    public static function prepare_rating_for_form(int $courseid): array {
181
        global $USER;
182
        $rv = [
183
            'review_editor' => ['text' => '', 'format' => FORMAT_HTML],
184
            'review' => '',
185
        ];
186
        if ($rating = rating::get_record(['userid' => $USER->id, 'courseid' => $courseid])) {
187
            $data = $rating->to_record();
188
            $rv['rating'] = $data->rating;
189
            if (helper::get_setting(constants::SETTING_USEHTML)) {
190
                $data->reviewformat = FORMAT_HTML;
191
                $context = \context_course::instance($courseid);
192
                $data = file_prepare_standard_editor($data, 'review', helper::review_editor_options($context), $context,
193
                    'tool_courserating', 'review', $data->id);
194
                $rv['review_editor'] = $data->review_editor;
195
            } else {
196
                $rv['review'] = clean_param($data->review, PARAM_TEXT);
197
            }
198
            return $rv;
199
        }
200
        return $rv;
201
    }
202
 
203
    /**
204
     * Flag somebody else's review
205
     *
206
     * @param int $ratingid
207
     * @return flag|null
208
     */
209
    public static function flag_review(int $ratingid): ?flag {
210
        global $USER;
211
        $flag = flag::get_records(['ratingid' => $ratingid, 'userid' => $USER->id]);
212
        if ($flag) {
213
            return null;
214
        }
215
        $rating = new rating($ratingid);
216
        $flag = new flag(0, (object)['userid' => $USER->id, 'ratingid' => $ratingid]);
217
        $flag->save();
218
 
219
        flag_created::create_from_flag($flag, $rating)->trigger();
220
        return $flag;
221
    }
222
 
223
    /**
224
     * Revoke a flag on somebody else's review
225
     *
226
     * @param int $ratingid
227
     * @return flag|null
228
     */
229
    public static function revoke_review_flag(int $ratingid): ?flag {
230
        global $USER;
231
        $flags = flag::get_records(['ratingid' => $ratingid, 'userid' => $USER->id]);
232
        $flag = reset($flags);
233
        if (!$flag) {
234
            return null;
235
        }
236
        $rating = new rating($ratingid);
237
        $oldrecord = $flag->to_record();
238
        $flag->delete();
239
        flag_deleted::create_from_flag($oldrecord, $rating)->trigger();
240
        return $flag;
241
    }
242
 
243
    /**
244
     * Get the flag
245
     *
246
     * @param int $ratingid
247
     * @param bool|null $hasflag
248
     * @return inplace_editable
249
     */
250
    public static function get_flag_inplace_editable(int $ratingid, ?bool $hasflag = null): inplace_editable {
251
        global $USER;
252
 
253
        if (!permission::can_flag_rating($ratingid)) {
254
            return new inplace_editable('tool_courserating', 'flag', $ratingid, false, '', 0, '');
255
        }
256
 
257
        if ($hasflag === null) {
258
            $hasflag = flag::count_records(['ratingid' => $ratingid, 'userid' => $USER->id]) > 0;
259
        }
260
        $displayvalue = $hasflag ? get_string('revokeratingflag', 'tool_courserating') :
261
            get_string('flagrating', 'tool_courserating');
262
        $edithint = $displayvalue;
263
        $r = new inplace_editable('tool_courserating', 'flag', $ratingid, true, $displayvalue,
264
            $hasflag ? 1 : 0, $edithint);
265
        $r->set_type_toggle([0, 1]);
266
        return $r;
267
    }
268
 
269
    /**
270
     * Re-index all courses, update ratings in the summary table and custom fields
271
     *
272
     * @param int $courseid
273
     * @return void
274
     */
275
    public static function reindex(int $courseid = 0) {
276
        global $DB, $SITE;
277
 
278
        $percourse = helper::get_setting(constants::SETTING_PERCOURSE);
279
        $ratingfield = helper::get_course_rating_field();
280
        $ratingmodefield = helper::get_course_rating_mode_field();
281
 
282
        if (!$ratingfield) {
283
            return;
284
        }
285
 
286
        $fields = 'c.id as courseid, d.value as cfield, s.cntall as summarycntall, s.ratingmode as summaryratingmode,
287
               (select count(1) from {tool_courserating_rating} r where r.courseid=c.id) as actualcntall ';
288
        $join = 'from {course} c
289
            left join {tool_courserating_summary} s on s.courseid = c.id
290
            left join {customfield_field} f on f.shortname = :field1
291
            left join {customfield_data} d on d.fieldid = f.id and d.instanceid = c.id ';
292
        $params = [
293
            'field1' => $ratingfield->get('shortname'),
294
            'siteid' => $SITE->id ?? SITEID,
295
        ];
296
 
297
        if ($percourse && $ratingmodefield) {
298
            // Each course may override whether course ratings are enabled.
299
            $fields .= ', dr.intvalue as rateby';
300
            $join .= ' left join {customfield_field} fr on fr.shortname = :field2
301
            left join {customfield_data} dr on dr.fieldid = fr.id and dr.instanceid = c.id';
302
            $params['field2'] = $ratingmodefield->get('shortname');
303
        }
304
 
305
        $sql = "SELECT $fields $join WHERE c.id <> :siteid ";
306
        if ($courseid) {
307
            $sql .= " AND c.id = :courseid ";
308
            $params['courseid'] = $courseid;
309
        } else {
310
            $sql .= " ORDER BY c.id DESC";
311
        }
312
 
313
        $records = $DB->get_records_sql($sql, $params);
314
        foreach ($records as $record) {
315
            $record->actualratingmode = helper::get_setting(constants::SETTING_RATINGMODE);
316
            if ($percourse && $record->rateby && array_key_exists($record->rateby, constants::rated_courses_options())) {
317
                $record->actualratingmode = $record->rateby;
318
            }
319
            self::reindex_course($record);
320
        }
321
    }
322
 
323
    /**
324
     * Re-index individual course
325
     *
326
     * @param \stdClass $data contains fields: courseid, cfield, summarycntall, actualcntall
327
     *     where cfield is the actual value stored in the "course rating" custom course field,
328
     *     summarycntall - the field tool_courserating_summary.cntall that corresponds to this course,
329
     *     summaryratingmode - the field tool_courserating_summary.ratingmode that corresponds to this course,
330
     *     actualcntall - the actual count of ratings for this course (count(*) from tool_courserating_rating)
331
     *     actualratingmode - what actually must be the rating mode of this course
332
     */
333
    protected static function reindex_course(\stdClass $data) {
334
        $mustbeempty = $data->actualratingmode == constants::RATEBY_NOONE
335
            || (!$data->actualcntall && !helper::get_setting(constants::SETTING_DISPLAYEMPTY));
336
 
337
        if ($data->summaryratingmode != $data->actualratingmode) {
338
            // Rating mode for this course has changed.
339
            $summary = summary::get_for_course($data->courseid);
340
            $summary->set('ratingmode', $data->actualratingmode);
341
            if ($data->actualratingmode == constants::RATEBY_NOONE) {
342
                $summary->reset_all_counters();
343
            }
344
            $summary->save();
345
        }
346
 
347
        if ($mustbeempty) {
348
            // Course rating should not be displayed at all.
349
            if (!empty($data->cfield)) {
350
                $summary = $summary ?? summary::get_for_course($data->courseid);
351
                self::update_course_rating_in_custom_field($summary);
352
            }
353
        } else {
354
            // Update summary and cfield with the data.
355
            $summary = $summary ?? summary::get_for_course($data->courseid);
356
            $summary->recalculate();
357
            self::update_course_rating_in_custom_field($summary);
358
        }
359
    }
360
 
361
    /**
362
     * Completely delete all data related to a course (i.e. when course is deleted)
363
     *
364
     * @param int $courseid
365
     * @return void
366
     */
367
    public static function delete_all_data_for_course(int $courseid) {
368
        global $DB;
369
        $DB->execute('DELETE from {'.flag::TABLE.'} WHERE ratingid IN (SELECT id FROM {'.
370
            rating::TABLE.'} WHERE courseid = ?)', [$courseid]);
371
        $DB->delete_records(rating::TABLE, ['courseid' => $courseid]);
372
        $DB->delete_records(summary::TABLE, ['courseid' => $courseid]);
373
    }
374
}