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_customfield\data_controller;
20
use core_customfield\field_controller;
21
use tool_courserating\external\stars_exporter;
22
 
23
/**
24
 * Additional helper functions
25
 *
26
 * @package     tool_courserating
27
 * @copyright   2022 Marina Glancy <marina.glancy@gmail.com>
28
 * @license     https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29
 */
30
class helper {
31
    /**
32
     * Temporary function
33
     *
34
     * @return void
35
     */
36
    private function wordings() {
37
        // @codingStandardsIgnoreStart
38
        // Udemy.
39
        'You\'ve finished the last lesson in this course! Would you like to leave a review?';
40
        [
41
            1 => 'Awful, not what I was expecting at all',
42
            1.5 => 'Awful / Poor',
43
            2 => 'Poor, pretty disappointed',
44
            2.5 => 'Poor / Average',
45
            3 => 'Average, could be better',
46
            3.5 => 'Average / Good',
47
            4 => 'Good, what I expected',
48
            4.5 => 'Good / Amazing',
49
            5 => 'Amazing, above expectations',
50
        ];
51
        'Tell us about your own personal experience taking this course. Was it a good match for you?';
52
 
53
        'Report'; 'Report abuse';
54
        'Flagged content is reviewed by Udemy staff to determine whether it violates Terms of Service or Community Guidelines. If you have a question or technical issue, please contact our Support team here.';
55
        'Issue type';
56
        [
57
            'Inappropriate Course Content',
58
            'Inappropriate Behavior',
59
            'Udemy Policy Violation',
60
            'Spammy Content',
61
            'Other',
62
        ];
63
        'Issue details';
64
        // @codingStandardsIgnoreEnd
65
    }
66
 
67
    /**
68
     * Checks if we are on a main course page
69
     *
70
     * @return int
71
     */
72
    public static function is_course_page(): int {
73
        global $PAGE, $CFG;
74
        if ($PAGE->course && $PAGE->url->out_omit_querystring() === $CFG->wwwroot . '/course/view.php') {
75
            return $PAGE->course->id;
76
        }
77
        return 0;
78
    }
79
 
80
    /**
81
     * Checks if we are on a main page of single-activity course
82
     *
83
     * @return int
84
     */
85
    public static function is_single_activity_course_page(): int {
86
        global $PAGE, $CFG;
87
        if ($PAGE->context->contextlevel == CONTEXT_MODULE && $PAGE->course->format === 'singleactivity' &&
88
            $PAGE->url->out_omit_querystring() === $CFG->wwwroot . '/mod/' . $PAGE->cm->modname . '/view.php') {
89
            return $PAGE->course->id;
90
        }
91
        return 0;
92
    }
93
 
94
    /**
95
     * Checks if we are on a course edit page
96
     *
97
     * @return int
98
     */
99
    public static function is_course_edit_page(): int {
100
        global $PAGE, $CFG;
101
        if ($PAGE->course && $PAGE->url->out_omit_querystring() === $CFG->wwwroot . '/course/edit.php') {
102
            return $PAGE->course->id;
103
        }
104
        return 0;
105
    }
106
 
107
    /**
108
     * Are course ratings enabled (or could be enabled) in any courses? Do we need to have a course rating field
109
     *
110
     * @return bool
111
     */
112
    public static function course_ratings_enabled_anywhere(): bool {
113
        if (self::get_setting(constants::SETTING_RATINGMODE) == constants::RATEBY_NOONE &&
114
                !self::get_setting(constants::SETTING_PERCOURSE)) {
115
            return false;
116
        }
117
        return true;
118
    }
119
 
120
    /**
121
     * Options for the review editor form element
122
     *
123
     * @param \context $context
124
     * @return array
125
     */
126
    public static function review_editor_options(\context $context) {
127
        global $CFG;
128
        require_once($CFG->dirroot.'/lib/formslib.php');
129
        return [
130
            'subdirs' => 0,
131
            'maxbytes' => $CFG->maxbytes,
132
            'maxfiles' => EDITOR_UNLIMITED_FILES,
133
            'changeformat' => 0,
134
            'context' => $context,
135
        ];
136
    }
137
 
138
    /**
139
     * Retrieve and clean plugin setting
140
     *
141
     * @param string $name
142
     * @return bool|mixed|object|string
143
     */
144
    public static function get_setting(string $name) {
145
        $value = get_config('tool_courserating', $name);
146
        static $defaults = [
147
            constants::SETTING_STARCOLOR => constants::SETTING_STARCOLOR_DEFAULT,
148
            constants::SETTING_RATINGCOLOR => constants::SETTING_RATINGCOLOR_DEFAULT,
149
            constants::SETTING_DISPLAYEMPTY => false,
150
            constants::SETTING_PERCOURSE => false,
151
            constants::SETTING_RATINGMODE => constants::RATEBY_ANYTIME,
152
            constants::SETTING_USEHTML => false,
153
        ];
154
        if (!isset($value) && array_key_exists($name, $defaults)) {
155
            // Can only happen if there is unfinished upgrade.
156
            return $defaults[$name];
157
        }
158
 
159
        if ($name === constants::SETTING_DISPLAYEMPTY || $name === constants::SETTING_PERCOURSE
160
                || $name === constants::SETTING_USEHTML) {
161
            return !empty($value);
162
        }
163
        if ($name === constants::SETTING_STARCOLOR || $name === constants::SETTING_RATINGCOLOR) {
164
            $color = strtolower($value ?? '');
165
            return (preg_match('/^#[a-f0-9]{6}$/', $color)) ? $color : $defaults[$name];
166
        }
167
        if ($name === constants::SETTING_RATINGMODE) {
168
            static $available = [constants::RATEBY_NOONE, constants::RATEBY_ANYTIME, constants::RATEBY_COMPLETED];
169
            return in_array($value, $available) ? $value : $defaults[$name];
170
        }
171
        return $value;
172
    }
173
 
174
    /**
175
     * CSS for the stars colors to be added to the page
176
     *
177
     * @return string
178
     */
179
    public static function get_rating_colour_css() {
180
        return '.tool_courserating-stars { color: '.self::get_setting(constants::SETTING_STARCOLOR).'; }'."\n".
181
            '.tool_courserating-ratingcolor { color: '.self::get_setting(constants::SETTING_RATINGCOLOR).';}'."\n".
182
            '.tool_courserating-norating .tool_courserating-stars { color: '.constants::COLOR_GRAY.';}'."\n".
183
            '.tool_courserating-barcolor { background-color: '.self::get_setting(constants::SETTING_STARCOLOR).';}'."\n";
184
    }
185
 
186
    /**
187
     * Finds a field by its shortname
188
     *
189
     * @param string $shortname
190
     * @return field_controller|null
191
     */
192
    protected static function find_custom_field_by_shortname(string $shortname): ?field_controller {
193
        $handler = \core_course\customfield\course_handler::create();
194
        $categories = $handler->get_categories_with_fields();
195
        foreach ($categories as $category) {
196
            foreach ($category->get_fields() as $field) {
197
                if ($field->get('shortname') === $shortname) {
198
                    return $field;
199
                }
200
            }
201
        }
202
        return null;
203
    }
204
 
205
    /**
206
     * Create a custom course field if it does not exist
207
     *
208
     * @param string $shortname
209
     * @param string $type i.e. 'textarea', 'select', 'text
210
     * @param null|\lang_string $displayname
211
     * @param array $config additional field configuration, for example, options for 'select' element
212
     * @param string $description
213
     * @return field_controller|null
214
     */
215
    protected static function create_custom_field(string $shortname, string $type = 'text', ?\lang_string $displayname = null,
216
                                               array $config = [], string $description = ''): ?field_controller {
217
        $handler = \core_course\customfield\course_handler::create();
218
        $categories = $handler->get_categories_with_fields();
219
        if (empty($categories)) {
220
            $categoryid = $handler->create_category();
221
            $category = \core_customfield\category_controller::create($categoryid);
222
        } else {
223
            $category = reset($categories);
224
        }
225
 
226
        $config += [
227
            'defaultvalue' => '',
228
            'defaultvalueformat' => 1,
229
            'visibility' => \core_course\customfield\course_handler::VISIBLETOALL,
230
            'required' => 0,
231
            'uniquevalues' => 0,
232
            'locked' => 0,
233
        ];
234
        $record = (object)[
235
            'type' => $type,
236
            'shortname' => $shortname,
237
            'name' => $displayname ? (string)$displayname : $shortname,
238
            'descriptionformat' => FORMAT_HTML,
239
            'description' => $description,
240
            'configdata' => json_encode($config),
241
        ];
242
 
243
        try {
244
            $field = \core_customfield\field_controller::create(0, $record, $category);
245
        } catch (\moodle_exception $e) {
246
            return null;
247
        }
248
 
249
        $handler->save_field_configuration($field, $record);
250
 
251
        // Fetch the field again because the categories cache was rebuilt.
252
        return self::find_custom_field_by_shortname($shortname);
253
    }
254
 
255
    /**
256
     * Retrieve course custom field responsible for storing course ratings, create if not found
257
     *
258
     * @return field_controller|null
259
     */
260
    public static function get_course_rating_field(): ?field_controller {
261
        $shortname = constants::CFIELD_RATING;
262
        $field = self::find_custom_field_by_shortname($shortname);
263
 
264
        if (!self::course_ratings_enabled_anywhere()) {
265
            if ($field) {
266
                $field->get_handler()->delete_field_configuration($field);
267
            }
268
            return null;
269
        }
270
 
271
        return $field ?? self::create_custom_field($shortname,
272
            'textarea',
273
            new \lang_string('ratinglabel', 'tool_courserating'),
274
            ['locked' => 1],
275
            get_string('cfielddescription', 'tool_courserating'));
276
    }
277
 
278
    /**
279
     * Retrieve course custom field responsible for configuring per-course course rating mode, create if needed
280
     *
281
     * @return field_controller|null
282
     */
283
    public static function get_course_rating_mode_field(): ?field_controller {
284
        $shortname = constants::CFIELD_RATINGMODE;
285
        $field = self::find_custom_field_by_shortname($shortname);
286
        if (!self::get_setting(constants::SETTING_PERCOURSE)) {
287
            if ($field) {
288
                $field->get_handler()->delete_field_configuration($field);
289
            }
290
            return null;
291
        }
292
 
293
        $options = constants::rated_courses_options();
294
        $description = get_string('ratebydefault', 'tool_courserating',
295
            $options[self::get_setting(constants::SETTING_RATINGMODE)]);
296
        $field = $field ?? self::create_custom_field($shortname,
297
            'select',
298
            new \lang_string('ratingmode', 'tool_courserating'),
299
            [
300
                'visibility' => \core_course\customfield\course_handler::NOTVISIBLE,
301
                'options' => join("\n", $options),
302
            ],
303
            $description);
304
        if ($field && $field->get('description') !== $description) {
305
            $field->set('description', $description);
306
            $field->save();
307
        }
308
        return $field;
309
    }
310
 
311
    /**
312
     * Delete all course custom fields created by this plugin (on uninstall)
313
     *
314
     * @return void
315
     */
316
    public static function delete_all_custom_fields() {
317
        $shortname = constants::CFIELD_RATINGMODE;
318
        if ($field = self::find_custom_field_by_shortname($shortname)) {
319
            $field->get_handler()->delete_field_configuration($field);
320
        }
321
        $shortname = constants::CFIELD_RATING;
322
        if ($field = self::find_custom_field_by_shortname($shortname)) {
323
            $field->get_handler()->delete_field_configuration($field);
324
        }
325
    }
326
 
327
    /**
328
     * Retireve data stored in a course custom field
329
     *
330
     * @param int $courseid
331
     * @param string $shortname
332
     * @return data_controller|null
333
     */
334
    protected static function get_custom_field_data(int $courseid, string $shortname): ?data_controller {
335
        if ($f = self::find_custom_field_by_shortname($shortname)) {
336
            $fields = \core_customfield\api::get_instance_fields_data([$f->get('id') => $f], $courseid);
337
            foreach ($fields as $data) {
338
                if (!$data->get('id')) {
339
                    $data->set('contextid', \context_course::instance($courseid)->id);
340
                }
341
                return $data;
342
            }
343
        }
344
        return null;
345
    }
346
 
347
    /**
348
     * Retrieve data stored in a course rating course custom field
349
     *
350
     * @param int $courseid
351
     * @return data_controller|null
352
     */
353
    public static function get_course_rating_data_in_cfield(int $courseid): ?data_controller {
354
        return self::get_custom_field_data($courseid, constants::CFIELD_RATING);
355
    }
356
 
357
    /**
358
     * Retireve data stored in a rating mode custom course field
359
     *
360
     * @param int $courseid
361
     * @return data_controller|null
362
     */
363
    public static function get_course_rating_enabled_data_in_cfield(int $courseid): ?data_controller {
364
        return self::get_custom_field_data($courseid, constants::CFIELD_RATINGMODE);
365
    }
366
 
367
    /**
368
     * Calculate the rating mode for a specific course
369
     *
370
     * @param int $courseid
371
     * @return int
372
     */
373
    public static function get_course_rating_mode(int $courseid): int {
374
        $mode = self::get_setting(constants::SETTING_RATINGMODE);
375
        if (self::get_setting(constants::SETTING_PERCOURSE)) {
376
            if ($data = self::get_course_rating_enabled_data_in_cfield($courseid)) {
377
                $modecourse = (int)$data->get('intvalue');
378
                if (array_key_exists($modecourse, constants::rated_courses_options())) {
379
                    // Value is overridden for this course.
380
                    return $modecourse;
381
                }
382
            }
383
        }
384
        return $mode;
385
    }
386
 
387
    /**
388
     * Formatter for average rating
389
     *
390
     * @param float|null $avgrating
391
     * @param string $default
392
     * @return string
393
     */
394
    public static function format_avgrating(?float $avgrating, string $default = ''): string {
395
        return $avgrating ? sprintf("%.1f", $avgrating) : $default;
396
    }
397
 
398
    /**
399
     * Formatter for stars
400
     *
401
     * @param float|null $avgrating
402
     * @param \renderer_base|null $output
403
     * @return string
404
     */
405
    public static function stars(?float $avgrating, ?\renderer_base $output = null): string {
406
        global $PAGE;
407
        if (!$avgrating) {
408
            return '';
409
        }
410
        $output = $output ?? $PAGE->get_renderer('tool_courserating');
411
        return $output->render_from_template('tool_courserating/stars',
412
            (new stars_exporter($avgrating))->export($output));
413
    }
414
 
415
    /**
416
     * Formatter for date
417
     * @param int $value
418
     * @return string
419
     */
420
    public static function format_date($value): string {
421
        return $value ? userdate($value, get_string('strftimedatetimeshort', 'core_langconfig')) : '';
422
    }
423
 
424
    /**
425
     * Formatter for review
426
     *
427
     * @param string $value
428
     * @param \stdClass $row
429
     * @return string
430
     */
431
    public static function format_review($value, \stdClass $row): string {
432
        if (empty($row->id) || !strlen($row->review ?? '')) {
433
            return '';
434
        }
435
        $context = !empty($row->courseid) ? \context_course::instance($row->courseid) : \context_system::instance();
436
        $formatparams = [
437
            'options' => [],
438
            'striplinks' => true,
439
            'component' => 'tool_courserating',
440
            'filearea' => 'review',
441
            'itemid' => $row->id,
442
            'context' => $context,
443
        ];
444
        if (self::get_setting(constants::SETTING_USEHTML)) {
445
            list($text, $format) = external_format_text($row->review, FORMAT_HTML, $formatparams['context'],
446
                $formatparams['component'], $formatparams['filearea'], $formatparams['itemid'], $formatparams['options']);
447
            return $text;
448
        } else {
449
            return format_text(clean_param($row->review, PARAM_TEXT), FORMAT_MOODLE, ['context' => $context]);
450
        }
451
    }
452
 
453
    /**
454
     * Actions column
455
     *
456
     * @param int $id
457
     * @param \stdClass $row
458
     * @return string
459
     */
460
    public static function format_actions($id, $row): string {
461
        if (!$id || !permission::can_delete_rating($id, $row->courseid)) {
462
            return '';
463
        }
464
        return "<span data-for=\"tool_courserating-rbcell\" data-ratingid=\"$id\">".
465
            "<a href=\"#\" data-action=\"tool_courserating-delete-rating\" data-ratingid=\"$id\">".
466
            get_string('deleterating', 'tool_courserating')."</a></span>";
467
    }
468
 
469
    /**
470
     * Format individual student rating in the course report
471
     *
472
     * @param int $rating
473
     * @param \stdClass $row
474
     * @return string
475
     */
476
    public static function format_rating_in_course_report($rating, $row): string {
477
        if (!$rating) {
478
            return '';
479
        }
480
        return \html_writer::span(
481
            self::stars((float)$rating).
482
            \html_writer::span($rating, 'tool_courserating-ratingcolor ml-2'),
483
            'tool_courserating-reportrating');
484
    }
485
 
486
    /**
487
     * Format flags count in course report
488
     *
489
     * @param int|null $nofflags
490
     * @param \stdClass $row
491
     * @return string
492
     */
493
    public static function format_flags_in_course_report(?int $nofflags, \stdClass $row): string {
494
        return $nofflags ? "<span class=\"badge badge-warning\">$nofflags</span>" : '';
495
    }
496
}