├── .gitattributes ├── pix ├── star-o.png ├── star.png ├── star-half.png ├── star.svg ├── star-half.svg └── star-o.svg ├── README.md ├── db ├── uninstall.php ├── install.php ├── events.php ├── services.php ├── hooks.php ├── access.php ├── upgrade.php └── install.xml ├── version.php ├── templates ├── stars.mustache ├── rating_flag.mustache ├── course_ratings_popup_reviews.mustache ├── rating.mustache ├── course_rating_block.mustache ├── course_ratings_popup.mustache ├── summary_for_cfield.mustache ├── star.mustache └── course_ratings_summary.mustache ├── tests ├── helper_test.php ├── generator │ ├── behat_tool_courserating_generator.php │ └── lib.php ├── behat │ ├── behat_tool_courserating.php │ ├── courseoverrides.feature │ ├── teacher.feature │ └── student.feature ├── observer_test.php ├── reportbuilder │ └── datasource │ │ └── courseratings_test.php └── privacy │ └── provider_test.php ├── CHANGELOG.md ├── classes ├── local │ ├── models │ │ ├── flag.php │ │ └── rating.php │ └── hooks │ │ └── output │ │ ├── before_standard_head_html_generation.php │ │ ├── before_footer_html_generation.php │ │ └── before_http_headers.php ├── observer.php ├── task │ └── reindex.php ├── event │ ├── rating_created.php │ ├── rating_updated.php │ ├── flag_deleted.php │ ├── flag_created.php │ └── rating_deleted.php ├── external │ ├── stars_exporter.php │ ├── course_rating_popup.php │ ├── ratings_list_exporter.php │ ├── rating_exporter.php │ └── summary_exporter.php ├── constants.php ├── form │ ├── deleterating.php │ └── addrating.php ├── output │ └── renderer.php ├── reportbuilder │ ├── local │ │ └── systemreports │ │ │ └── course_ratings_report.php │ └── datasource │ │ └── courseratings.php └── permission.php ├── index.php ├── .github └── workflows │ ├── moodle-release.yml │ └── moodle.yml ├── styles.css ├── settings.php ├── lang └── en │ └── tool_courserating.php └── amd └── build └── rating.min.js /.gitattributes: -------------------------------------------------------------------------------- 1 | **/yui/build/** -diff 2 | **/amd/build/** -diff 3 | -------------------------------------------------------------------------------- /pix/star-o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marinaglancy/moodle-tool_courserating/master/pix/star-o.png -------------------------------------------------------------------------------- /pix/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marinaglancy/moodle-tool_courserating/master/pix/star.png -------------------------------------------------------------------------------- /pix/star-half.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marinaglancy/moodle-tool_courserating/master/pix/star-half.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Course ratings # 2 | 3 | "Course ratings" plugin allows students to add ratings and reviews to the courses, 4 | and teachers and other users to read them. 5 | 6 | The custom course field that stores the average course rating is created automatically. 7 | The course ratings will be displayed in any course listing that displays custom course 8 | fields, for example, "All courses" page and site home. 9 | 10 | The current course rating is also automatically added to each course home page. 11 | 12 | Managers are able to permanently delete course reviews that were flagged by users. 13 | 14 | By default every student enrolled into a course can leave a rating. It can be configured that only students who completed the course can do it. 15 | There is also an option to allow to enable or disable course ratings for individual courses. 16 | 17 | This plugin can be found in Moodle plugins directory: 18 | https://moodle.org/plugins/tool_courserating -------------------------------------------------------------------------------- /db/uninstall.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Course rating plugin uninstallation. 19 | * 20 | * @package tool_courserating 21 | * @category upgrade 22 | * @copyright 2022 Marina Glancy 23 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | /** 27 | * Executed on uninstall 28 | * 29 | * @return bool 30 | */ 31 | function xmldb_tool_courserating_uninstall() { 32 | 33 | \tool_courserating\helper::delete_all_custom_fields(); 34 | 35 | return true; 36 | } 37 | -------------------------------------------------------------------------------- /version.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Plugin version and other meta-data are defined here. 19 | * 20 | * @package tool_courserating 21 | * @copyright 2022 Marina Glancy 22 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | $plugin->component = 'tool_courserating'; 28 | $plugin->release = '4.1.0'; 29 | $plugin->version = 2025110600; 30 | $plugin->requires = 2022112800; 31 | $plugin->maturity = MATURITY_STABLE; 32 | $plugin->supported = [401, 501]; 33 | -------------------------------------------------------------------------------- /db/install.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Code to be executed after the plugin's database scheme has been installed is defined here. 19 | * 20 | * @package tool_courserating 21 | * @category upgrade 22 | * @copyright 2022 Marina Glancy 23 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | /** 27 | * Custom code to be run on installing the plugin. 28 | */ 29 | function xmldb_tool_courserating_install() { 30 | 31 | if (!defined('PHPUNIT_TEST') || !PHPUNIT_TEST) { 32 | \tool_courserating\task\reindex::schedule(); 33 | } 34 | return true; 35 | } 36 | -------------------------------------------------------------------------------- /pix/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /pix/star-half.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /templates/stars.mustache: -------------------------------------------------------------------------------- 1 | {{! 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 . 16 | }} 17 | {{! 18 | @template tool_courserating/stars 19 | 20 | Example context (json): 21 | { 22 | "staricons": [ 23 | {"key": "star", "component": "tool_courserating", "title": ""}, 24 | {"key": "star", "component": "tool_courserating", "title": ""}, 25 | {"key": "star-half", "component": "tool_courserating", "title": ""}, 26 | {"key": "star-o", "component": "tool_courserating", "title": ""}, 27 | {"key": "star-o", "component": "tool_courserating", "title": ""} 28 | ] 29 | } 30 | 31 | }} 32 | {{#staricons}}{{#pix}}{{key}}, {{component}}, {{title}}{{/pix}}{{/staricons}} -------------------------------------------------------------------------------- /pix/star-o.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /db/events.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Event handler definition. 19 | * 20 | * @package tool_courserating 21 | * @copyright 2022 Marina Glancy 22 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | $observers = [ 28 | [ 29 | 'eventname' => '\core\event\course_updated', 30 | 'callback' => '\tool_courserating\observer::course_updated', 31 | ], 32 | [ 33 | 'eventname' => '\core\event\course_created', 34 | 'callback' => '\tool_courserating\observer::course_created', 35 | ], 36 | [ 37 | 'eventname' => '\core\event\course_deleted', 38 | 'callback' => '\tool_courserating\observer::course_deleted', 39 | ], 40 | ]; 41 | -------------------------------------------------------------------------------- /db/services.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * External functions and service declaration for Course ratings 19 | * 20 | * Documentation: {@link https://moodledev.io/docs/apis/subsystems/external/description} 21 | * 22 | * @package tool_courserating 23 | * @category webservice 24 | * @copyright 2023 Marina Glancy 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | $functions = [ 31 | 32 | 'tool_courserating_course_rating_popup' => [ 33 | 'classname' => tool_courserating\external\course_rating_popup::class, 34 | 'description' => 'Course rating popup', 35 | 'type' => 'read', 36 | 'ajax' => true, 37 | 'loginrequired' => false, 38 | ], 39 | ]; 40 | 41 | $services = [ 42 | ]; 43 | -------------------------------------------------------------------------------- /tests/helper_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating; 18 | 19 | /** 20 | * Tests for helper class 21 | * 22 | * @package tool_courserating 23 | * @covers \tool_courserating\helper 24 | * @copyright 2022 Marina Glancy 25 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | final class helper_test extends \advanced_testcase { 28 | /** 29 | * Set up 30 | */ 31 | public function setUp(): void { 32 | parent::setUp(); 33 | $this->resetAfterTest(); 34 | set_config( 35 | \tool_courserating\constants::SETTING_RATINGMODE, 36 | \tool_courserating\constants::RATEBY_ANYTIME, 37 | 'tool_courserating' 38 | ); 39 | } 40 | 41 | public function test_coursefield(): void { 42 | $this->assertNotEmpty(helper::get_course_rating_field()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /templates/rating_flag.mustache: -------------------------------------------------------------------------------- 1 | {{! 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 . 16 | }} 17 | {{! 18 | @template tool_courserating/rating_flag 19 | 20 | Example context (json): 21 | { 22 | } 23 | 24 | }} 25 |
26 |
27 | {{#flagged}} 28 | {{#str}}youflagged, tool_courserating{{/str}} 29 | {{/flagged}} 30 | {{#toggleflag}} 31 | {{>core/inplace_editable}} 32 | {{/toggleflag}} 33 |
34 | {{#flags}} 35 |
36 | {{#str}}usersflagged, tool_courserating, {{.}}{{/str}} 37 | {{#candelete}} 38 | {{#str}}deleterating, tool_courserating{{/str}} 39 | {{/candelete}} 40 |
41 | {{/flags}} 42 |
43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [4.1.0] - 2025110600 5 | ### Added 6 | - Ability to completely disable text reviews or make them visible to teachers only #9 7 | 8 | ### Changed 9 | - Raised minimum supported Moodle version to 4.1, remove legacy code 10 | 11 | ## [1.9] - 2025110400 12 | ### Fixed 13 | - Call to undefined function tool_courserating\external_format_text #20, #22 14 | 15 | ## [1.8] - 2025100700 16 | ### Added 17 | - Support for Moodle 5.0 and 5.1 18 | 19 | ## [1.7] - 2024100500 20 | ### Added 21 | - Support for Moodle 4.5 22 | 23 | ## [1.6] - 2024052100 24 | ### Added 25 | - Support for Moodle 4.4 26 | ### Fixed 27 | - Coding style fixes 28 | - Failing core unittests, #12 29 | 30 | ## [1.5] - 2023112400 31 | ### Added 32 | - Display course ratings details to non-logged in users instead of redirecting to login page 33 | - Add testing on Moodle 4.3 34 | 35 | ## [1.4] - 2023072800 36 | ### Added 37 | - Compatibility with Moodle 4.1 and 4.2 38 | 39 | ## [1.3] - 2022111300 40 | ### Added 41 | - Course report with all individual ratings and reviews 42 | - For Moodle 4.0 - data source for the report builder for all courses ratings and reviews 43 | 44 | ### Changed 45 | - Hide "Course rating" field from the course edit form 46 | 47 | ## [1.2] - 2022070800 48 | ### Changed 49 | - Prevent JS from loading in 'embedded' page layout (fixes bug when used with filter_embedquestion plugin). 50 | 51 | ## [1.1] - 2022070500 52 | ### Added 53 | - New setting **tool_courserating/parentcss** allows to specify custom position of the 54 | course rating on the course page (useful for custom site themes). 55 | 56 | ## [1.0] - 2022061900 57 | First release 58 | -------------------------------------------------------------------------------- /templates/course_ratings_popup_reviews.mustache: -------------------------------------------------------------------------------- 1 | {{! 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 . 16 | }} 17 | {{! 18 | @template tool_courserating/course_ratings_popup_reviews 19 | 20 | Example context (json): 21 | { 22 | "courseid": 12, 23 | "ratings": [ 24 | ] 25 | } 26 | 27 | }} 28 | {{^offset}} 29 | {{#withrating}} 30 |

{{#str}}showallreviewsforrating, tool_courserating, {{withrating}}{{/str}} 31 | {{#str}}showallratings, tool_courserating{{/str}}

32 | {{/withrating}} 33 | {{/offset}} 34 | {{#ratings}} 35 | {{> tool_courserating/rating}} 36 | {{/ratings}} 37 | {{#hasmore}} 38 |
39 | 41 |
42 | {{/hasmore}} 43 | -------------------------------------------------------------------------------- /db/hooks.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Hook callbacks for Course ratings 19 | * 20 | * @package tool_courserating 21 | * @copyright 2024 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 | $callbacks = [ 28 | 29 | [ 30 | 'hook' => core\hook\output\before_standard_head_html_generation::class, 31 | 'callback' => 'tool_courserating\local\hooks\output\before_standard_head_html_generation::callback', 32 | 'priority' => 0, 33 | ], 34 | 35 | [ 36 | 'hook' => core\hook\output\before_footer_html_generation::class, 37 | 'callback' => 'tool_courserating\local\hooks\output\before_footer_html_generation::callback', 38 | 'priority' => 0, 39 | ], 40 | 41 | [ 42 | 'hook' => core\hook\output\before_http_headers::class, 43 | 'callback' => 'tool_courserating\local\hooks\output\before_http_headers::callback', 44 | 'priority' => 0, 45 | ], 46 | ]; 47 | -------------------------------------------------------------------------------- /classes/local/models/flag.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\local\models; 18 | 19 | /** 20 | * Model for flag table 21 | * 22 | * @package tool_courserating 23 | * @copyright 2022 Marina Glancy 24 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | class flag extends \core\persistent { 27 | /** @var string Table name */ 28 | public const TABLE = 'tool_courserating_flag'; 29 | 30 | /** 31 | * Return the definition of the properties of this model. 32 | * 33 | * @return array 34 | */ 35 | protected static function define_properties(): array { 36 | return [ 37 | 'ratingid' => [ 38 | 'type' => PARAM_INT, 39 | ], 40 | 'userid' => [ 41 | 'type' => PARAM_INT, 42 | ], 43 | 'reasoncode' => [ 44 | 'type' => PARAM_INT, 45 | 'default' => 0, 46 | ], 47 | 'reason' => [ 48 | 'type' => PARAM_TEXT, 49 | 'default' => '', 50 | ], 51 | ]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /templates/rating.mustache: -------------------------------------------------------------------------------- 1 | {{! 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 . 16 | }} 17 | {{! 18 | @template tool_courserating/rating 19 | 20 | Example context (json): 21 | { 22 | } 23 | 24 | }} 25 |
26 |
27 | {{#user}} 28 | 29 | 30 | 31 | {{fullname}} 32 | {{#hasidentity}} 33 | {{identity}} 34 | {{/hasidentity}} 35 | {{/user}} 36 |
37 |
38 | {{#reviewstars}} 39 | {{> tool_courserating/stars }} 40 | {{/reviewstars}} 41 | 42 | {{{reviewdate}}} 43 | 44 |
45 |
46 | {{{reviewtext}}} 47 |
48 | {{#ratingflag}} 49 | {{> tool_courserating/rating_flag }} 50 | {{/ratingflag}} 51 |
52 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Course ratings report for the course 19 | * 20 | * @package tool_courserating 21 | * @copyright 2022 Marina Glancy 22 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | require('../../../config.php'); 26 | 27 | $courseid = required_param('id', PARAM_INT); 28 | 29 | require_course_login($courseid); 30 | \tool_courserating\permission::require_can_view_reports($courseid); 31 | $PAGE->set_url(new moodle_url('/admin/tool/courserating/index.php', ['id' => $courseid])); 32 | $PAGE->set_title($COURSE->shortname . ': ' . get_string('pluginname', 'tool_courserating')); 33 | $PAGE->set_heading($COURSE->fullname); 34 | 35 | echo $OUTPUT->header(); 36 | echo $OUTPUT->heading(get_string('pluginname', 'tool_courserating')); 37 | 38 | $report = \core_reportbuilder\system_report_factory::create( 39 | \tool_courserating\reportbuilder\local\systemreports\course_ratings_report::class, 40 | context_course::instance($courseid), 41 | '', 42 | '', 43 | 0, 44 | ['courseid' => $courseid] 45 | ); 46 | 47 | echo $report->output(); 48 | 49 | echo $OUTPUT->footer(); 50 | -------------------------------------------------------------------------------- /db/access.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Capability definitions for this plugin. 19 | * 20 | * @package tool_courserating 21 | * @copyright 2022 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 | $capabilities = [ 28 | 29 | 'tool/courserating:rate' => [ 30 | 'captype' => 'write', 31 | 'contextlevel' => CONTEXT_COURSE, 32 | 'archetypes' => [ 33 | 'student' => CAP_ALLOW, 34 | ], 35 | ], 36 | 37 | 'tool/courserating:delete' => [ 38 | 'riskbitmap' => RISK_DATALOSS, 39 | 'captype' => 'write', 40 | 'contextlevel' => CONTEXT_COURSE, 41 | 'archetypes' => [ 42 | 'manager' => CAP_ALLOW, 43 | ], 44 | ], 45 | 46 | 'tool/courserating:reports' => [ 47 | 'riskbitmap' => RISK_PERSONAL, 48 | 'captype' => 'write', 49 | 'contextlevel' => CONTEXT_COURSE, 50 | 'archetypes' => [ 51 | 'manager' => CAP_ALLOW, 52 | 'teacher' => CAP_ALLOW, 53 | 'editingteacher' => CAP_ALLOW, 54 | ], 55 | ], 56 | ]; 57 | -------------------------------------------------------------------------------- /classes/local/hooks/output/before_standard_head_html_generation.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\local\hooks\output; 18 | 19 | /** 20 | * Hook callbacks for tool_courserating 21 | * 22 | * @package tool_courserating 23 | * @copyright 2024 Marina Glancy 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | class before_standard_head_html_generation { 27 | /** 28 | * Callback allowing to add to of the page 29 | * 30 | * @param \core\hook\output\before_standard_head_html_generation $hook 31 | */ 32 | public static function callback(\core\hook\output\before_standard_head_html_generation $hook): void { 33 | 34 | if (during_initial_install() || isset($CFG->upgraderunning)) { 35 | // Do nothing during installation or upgrade. 36 | return; 37 | } 38 | 39 | if (\tool_courserating\helper::course_ratings_enabled_anywhere()) { 40 | // Add CSS to all pages, the course ratings can be displayed on any page (for example course listings). 41 | $res = ''; 42 | $hook->add_html($res); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /templates/course_rating_block.mustache: -------------------------------------------------------------------------------- 1 | {{! 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 . 16 | }} 17 | {{! 18 | @template tool_courserating/course_rating_block 19 | 20 | Example context (json): 21 | { 22 | } 23 | 24 | }} 25 | 42 | {{#parentelement}} 43 | {{#js}} 44 | /* Move the widget to the header area */ 45 | const widget = document.querySelector(".tool_courserating-widget"); 46 | const parents = "{{parentelement}}".split(','); 47 | for (let i in parents) { 48 | const header = document.querySelector(parents[i]); 49 | if (header) { 50 | header.appendChild(widget); 51 | break; 52 | } 53 | } 54 | {{/js}} 55 | {{/parentelement}} 56 | -------------------------------------------------------------------------------- /classes/local/hooks/output/before_footer_html_generation.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\local\hooks\output; 18 | 19 | /** 20 | * Hook callbacks for tool_courserating 21 | * 22 | * @package tool_courserating 23 | * @copyright 2024 Marina Glancy 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | class before_footer_html_generation { 27 | /** 28 | * Callback allowing to add contetnt inside the region-main, in the very end 29 | * 30 | * @param \core\hook\output\before_footer_html_generation $hook 31 | */ 32 | public static function callback(\core\hook\output\before_footer_html_generation $hook): void { 33 | 34 | if (during_initial_install() || isset($CFG->upgraderunning)) { 35 | // Do nothing during installation or upgrade. 36 | return; 37 | } 38 | 39 | global $PAGE; 40 | $res = ''; 41 | if (\tool_courserating\helper::course_ratings_enabled_anywhere()) { 42 | /** @var \tool_courserating\output\renderer $output */ 43 | $output = $PAGE->get_renderer('tool_courserating'); 44 | if ( 45 | ($courseid = \tool_courserating\helper::is_course_page()) || 46 | ($courseid = \tool_courserating\helper::is_single_activity_course_page()) 47 | ) { 48 | $res .= $output->course_rating_block($courseid); 49 | } 50 | } 51 | $hook->add_html($res); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /templates/course_ratings_popup.mustache: -------------------------------------------------------------------------------- 1 | {{! 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 . 16 | }} 17 | {{! 18 | @template tool_courserating/course_ratings_popup 19 | 20 | Example context (json): 21 | { 22 | "courseid": 12, 23 | "canviewreviews": true, 24 | "canrate": true, 25 | "ratings": [ 26 | ] 27 | } 28 | 29 | }} 30 |
31 | {{#canrate}} 32 |

33 | 34 | {{#hasrating}} 35 | {{#str}}editrating, tool_courserating{{/str}} 36 | {{/hasrating}} 37 | {{^hasrating}} 38 | {{#str}}addrating, tool_courserating{{/str}} 39 | {{/hasrating}} 40 | 41 |

42 | {{/canrate}} 43 | 44 |
45 | {{>tool_courserating/course_ratings_summary}} 46 |
47 | 48 | {{#canviewreviews}} 49 | {{#hiddenreviewsnotification}} 50 | {{> core/notification_warning}} 51 | {{/hiddenreviewsnotification}} 52 |
55 | {{>tool_courserating/course_ratings_popup_reviews}} 56 |
57 | {{/canviewreviews}} 58 |
59 | -------------------------------------------------------------------------------- /classes/observer.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating; 18 | 19 | use core\event\course_created; 20 | use core\event\course_deleted; 21 | use core\event\course_updated; 22 | use tool_courserating\local\models\summary; 23 | 24 | /** 25 | * Events ovserver 26 | * 27 | * @package tool_courserating 28 | * @copyright 2022 Marina Glancy 29 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 | */ 31 | class observer { 32 | /** 33 | * Observer for course_updated event 34 | * 35 | * @param course_updated $event 36 | */ 37 | public static function course_updated(course_updated $event) { 38 | if (helper::get_setting(constants::SETTING_PERCOURSE)) { 39 | $summary = summary::get_for_course($event->courseid); 40 | if ($summary->get('ratingmode') != helper::get_course_rating_mode($event->courseid)) { 41 | // Rating mode has changed for this course. 42 | api::reindex($event->courseid); 43 | } 44 | } 45 | } 46 | 47 | /** 48 | * Observer for course_deleted event 49 | * 50 | * @param course_deleted $event 51 | */ 52 | public static function course_deleted(course_deleted $event) { 53 | api::delete_all_data_for_course($event->courseid); 54 | } 55 | 56 | /** 57 | * Observer for course_created event 58 | * 59 | * @param course_created $event 60 | */ 61 | public static function course_created(course_created $event) { 62 | api::reindex($event->courseid); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /classes/task/reindex.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\task; 18 | 19 | use tool_courserating\api; 20 | 21 | /** 22 | * Task to recalculate ratings on all courses 23 | * 24 | * @package tool_courserating 25 | * @copyright 2022 Marina Glancy 26 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 | */ 28 | class reindex extends \core\task\adhoc_task { 29 | /** 30 | * Name of the task 31 | * 32 | * @return \lang_string|string 33 | */ 34 | public function get_name() { 35 | return get_string('reindextask', 'tool_courserating'); 36 | } 37 | 38 | /** 39 | * Execute the task 40 | * 41 | * @return void 42 | */ 43 | public function execute() { 44 | $courseid = $this->get_custom_data()->courseid ?? 0; 45 | try { 46 | api::reindex($courseid); 47 | } catch (\Throwable $t) { 48 | debugging($t->getMessage() . "\n\n" . $t->getTraceAsString()); 49 | } 50 | } 51 | 52 | /** 53 | * Schedule the task to run on the next cron 54 | */ 55 | public static function schedule() { 56 | self::schedule_course(0); 57 | } 58 | 59 | /** 60 | * Schedule the task to run on the next cron, for individual course 61 | * 62 | * @param int $courseid 63 | */ 64 | public static function schedule_course(int $courseid = 0) { 65 | global $USER; 66 | 67 | $task = new static(); 68 | $task->set_userid($USER->id); 69 | $task->set_custom_data(['courseid' => $courseid]); 70 | 71 | \core\task\manager::queue_adhoc_task($task, true); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/generator/behat_tool_courserating_generator.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | use tool_courserating\api; 18 | use tool_courserating\helper; 19 | 20 | /** 21 | * Behat data generator for tool_courserating 22 | * 23 | * @package tool_courserating 24 | * @copyright 2022 Marina Glancy 25 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | class behat_tool_courserating_generator extends behat_generator_base { 28 | /** 29 | * @var tool_courserating_generator 30 | */ 31 | protected $componentdatagenerator; 32 | 33 | /** 34 | * Get a list of the entities that can be created for this component. 35 | * 36 | * See {@see behat_core_generator::get_creatable_entities} for an example. 37 | * 38 | * @return array entity name => information about how to generate. 39 | */ 40 | protected function get_creatable_entities(): array { 41 | return [ 42 | 'ratings' => [ 43 | 'singular' => 'rating', 44 | 'datagenerator' => 'rating', 45 | 'required' => ['user', 'course', 'rating'], 46 | 'switchids' => ['user' => 'userid', 'course' => 'courseid'], 47 | ], 48 | ]; 49 | } 50 | 51 | /** 52 | * Adapter to enrol_user() data generator. 53 | * 54 | * @throws Exception 55 | * @param array $data 56 | * @return void 57 | */ 58 | protected function process_rating($data) { 59 | $this->componentdatagenerator->create_rating( 60 | (int)$data['userid'], 61 | (int)$data['courseid'], 62 | (int)$data['rating'], 63 | $data['review'] ?? '' 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /classes/local/hooks/output/before_http_headers.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\local\hooks\output; 18 | 19 | /** 20 | * Hook callbacks for tool_courserating 21 | * 22 | * @package tool_courserating 23 | * @copyright 2024 Marina Glancy 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | class before_http_headers { 27 | /** 28 | * Callback allowing to add js to $PAGE->requires 29 | * 30 | * @param \core\hook\output\before_http_headers $hook 31 | */ 32 | public static function callback(\core\hook\output\before_http_headers $hook): void { 33 | global $PAGE, $CFG; 34 | 35 | if (during_initial_install() || isset($CFG->upgraderunning)) { 36 | // Do nothing during installation or upgrade. 37 | return; 38 | } 39 | 40 | if ( 41 | \tool_courserating\helper::course_ratings_enabled_anywhere() && 42 | !in_array($PAGE->pagelayout, ['redirect', 'embedded']) 43 | ) { 44 | // Add JS to all pages, the course ratings can be displayed on any page (for example course listings). 45 | $PAGE->requires->js_call_amd( 46 | 'tool_courserating/rating', 47 | 'init', 48 | [\context_system::instance()->id] 49 | ); 50 | if (\tool_courserating\helper::is_course_edit_page()) { 51 | $field = \tool_courserating\helper::get_course_rating_field(); 52 | $PAGE->requires->js_call_amd( 53 | 'tool_courserating/rating', 54 | 'hideEditField', 55 | [$field->get('shortname')] 56 | ); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/behat/behat_tool_courserating.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | use Behat\Gherkin\Node\TableNode, 18 | Behat\Mink\Exception\ExpectationException; 19 | 20 | require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php'); 21 | 22 | /** 23 | * Behat steps for tool_courserating 24 | * 25 | * @package tool_courserating 26 | * @copyright 2022 Marina Glancy 27 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 | */ 29 | class behat_tool_courserating extends behat_base { 30 | /** 31 | * Return the list of partial named selectors. 32 | * 33 | * @return array 34 | */ 35 | public static function get_partial_named_selectors(): array { 36 | return [ 37 | new behat_component_named_selector('Review', [ 38 | <<. 16 | }} 17 | {{! 18 | @template tool_courserating/summary_for_cfield 19 | 20 | This template is used to generate content for the custom course profile field that shows course rating. 21 | Because the content of the custom course profile field is purified, it is not allowed to use any data- 22 | attributes. The course id is included as a part of a 'class' attribute. 23 | 24 | Example context (json): 25 | { 26 | "avgrating": "3.6", 27 | "stars": { 28 | "staricons": [ 29 | {"key": "star", "component": "tool_courserating", "title": ""}, 30 | {"key": "star", "component": "tool_courserating", "title": ""}, 31 | {"key": "star", "component": "tool_courserating", "title": ""}, 32 | {"key": "star-half", "component": "tool_courserating", "title": ""}, 33 | {"key": "star-o", "component": "tool_courserating", "title": ""} 34 | ] 35 | }, 36 | "courseid": "2", 37 | "cntall": "15" 38 | } 39 | 40 | }} 41 | {{#cntall}} 42 | {{#stars}}{{> tool_courserating/stars }}{{/stars}}{{avgrating}}({{cntall}}) 48 | {{/cntall}} 49 | {{#displayempty}} 50 | {{^cntall}} 51 | {{#stars}}{{> tool_courserating/stars }}{{/stars}} 54 | {{/cntall}} 55 | {{/displayempty}} 56 | -------------------------------------------------------------------------------- /classes/event/rating_created.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\event; 18 | 19 | use tool_courserating\local\models\rating; 20 | 21 | /** 22 | * Event rating created 23 | * 24 | * @package tool_courserating 25 | * @copyright 2022 Marina Glancy 26 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 | */ 28 | class rating_created extends \core\event\base { 29 | /** 30 | * Init 31 | */ 32 | protected function init() { 33 | $this->data['crud'] = 'c'; 34 | $this->data['edulevel'] = self::LEVEL_PARTICIPATING; 35 | $this->data['objecttable'] = rating::TABLE; 36 | } 37 | 38 | /** 39 | * Event name 40 | * 41 | * @return string 42 | */ 43 | public static function get_name(): string { 44 | return get_string('event:rating_created', 'tool_courserating'); 45 | } 46 | 47 | /** 48 | * Event description 49 | * 50 | * @return string 51 | */ 52 | public function get_description(): string { 53 | return "User {$this->relateduserid} has rated the course with " . $this->other['rating'] . " stars"; 54 | } 55 | 56 | /** 57 | * Shortcut to create an instance of event 58 | * 59 | * @param rating $object 60 | * @return self 61 | */ 62 | public static function create_from_rating(rating $object): self { 63 | /** @var self $event */ 64 | $event = static::create([ 65 | 'objectid' => $object->get('id'), 66 | 'courseid' => $object->get('courseid'), 67 | 'relateduserid' => $object->get('userid'), 68 | 'context' => \context_course::instance($object->get('courseid')), 69 | 'other' => ['rating' => $object->get('rating')], 70 | ]); 71 | $event->add_record_snapshot($event->data['objecttable'], $object->to_record()); 72 | return $event; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.github/workflows/moodle-release.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Whenever a new tag starting with "v" is pushed, add the tagged version 3 | # to the Moodle Plugins directory at https://moodle.org/plugins 4 | # 5 | # revision: 2021070201 6 | # 7 | name: Releasing in the Plugins directory 8 | 9 | on: 10 | push: 11 | tags: 12 | - v* 13 | 14 | workflow_dispatch: 15 | inputs: 16 | tag: 17 | description: 'Tag to be released' 18 | required: true 19 | 20 | defaults: 21 | run: 22 | shell: bash 23 | 24 | jobs: 25 | release-at-moodle-org: 26 | runs-on: ubuntu-latest 27 | env: 28 | PLUGIN: tool_courserating 29 | CURL: curl -s 30 | ENDPOINT: https://moodle.org/webservice/rest/server.php 31 | TOKEN: ${{ secrets.MOODLE_ORG_TOKEN }} 32 | FUNCTION: local_plugins_add_version 33 | 34 | steps: 35 | - name: Call the service function 36 | id: add-version 37 | run: | 38 | if [[ ! -z "${{ github.event.inputs.tag }}" ]]; then 39 | TAGNAME="${{ github.event.inputs.tag }}" 40 | elif [[ $GITHUB_REF = refs/tags/* ]]; then 41 | TAGNAME="${GITHUB_REF##*/}" 42 | fi 43 | if [[ -z "${TAGNAME}" ]]; then 44 | echo "No tag name has been provided!" 45 | exit 1 46 | fi 47 | ZIPURL="https://api.github.com/repos/${{ github.repository }}/zipball/${TAGNAME}" 48 | RESPONSE=$(${CURL} ${ENDPOINT} --data-urlencode "wstoken=${TOKEN}" \ 49 | --data-urlencode "wsfunction=${FUNCTION}" \ 50 | --data-urlencode "moodlewsrestformat=json" \ 51 | --data-urlencode "frankenstyle=${PLUGIN}" \ 52 | --data-urlencode "zipurl=${ZIPURL}" \ 53 | --data-urlencode "vcssystem=git" \ 54 | --data-urlencode "vcsrepositoryurl=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \ 55 | --data-urlencode "vcstag=${TAGNAME}" \ 56 | --data-urlencode "changelogurl=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commits/${TAGNAME}" \ 57 | --data-urlencode "altdownloadurl=${ZIPURL}") 58 | echo "response=${RESPONSE}" >> $GITHUB_OUTPUT 59 | 60 | - name: Evaluate the response 61 | id: evaluate-response 62 | env: 63 | RESPONSE: ${{ steps.add-version.outputs.response }} 64 | run: | 65 | jq <<< ${RESPONSE} 66 | jq --exit-status ".id" <<< ${RESPONSE} > /dev/null 67 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* Styles for course ratings */ 2 | 3 | .tool_courserating-stars { 4 | white-space: nowrap; 5 | } 6 | .tool_courserating-stars .icon { 7 | margin-right: 1px; 8 | } 9 | .tool_courserating-cfield .course-average-value { 10 | font-weight: bold; 11 | } 12 | .tool_courserating-cfield .tool_courserating-ratings:hover { 13 | text-decoration: none; 14 | } 15 | .tool_courserating-reportrating { 16 | font-weight: 700; 17 | white-space: nowrap; 18 | } 19 | 20 | .tool_courserating-reviews-popup .course-average-value { 21 | font-weight: 700; 22 | letter-spacing: -.02rem; 23 | font-size: 4rem; 24 | } 25 | .tool_courserating-reviews-popup .course-average-caption { 26 | font-weight: 700; 27 | line-height: 1.4; 28 | font-size: 0.9rem; 29 | } 30 | .tool_courserating-reviews-popup .course-rating-percent { 31 | font-weight: 700; 32 | } 33 | .tool_courserating-reviews-popup .course-rating-bar { 34 | height: 10px; 35 | display: block; 36 | border-radius: 2px; 37 | box-shadow: inset 0 0 2px rgba(0, 0, 0, .25); 38 | position: relative; 39 | background-color: #eee; 40 | margin: 3px; 41 | } 42 | .tool_courserating-reviews-popup .course-rating-bar .course-rating-bar-rating { 43 | display: block; 44 | height: 10px; 45 | } 46 | .tool_courserating-reviews-popup .user-review { 47 | border-top: 1px solid #dee2e6; 48 | } 49 | 50 | .tool_courserating-form-stars-group label .icon { 51 | font-size: 2rem; 52 | } 53 | .tool_courserating-form-stars-group label.form-check-inline { 54 | margin-right: 0; 55 | padding-right: 1rem; 56 | } 57 | 58 | .tool_courserating-form-stars-group input[type='radio'] { 59 | display: none; 60 | } 61 | 62 | .tool_courserating-form-stars-group .stars-1 .star-on, 63 | .tool_courserating-form-stars-group.s-1 .stars-1 .star-off, 64 | .tool_courserating-form-stars-group .stars-2 .star-on, 65 | .tool_courserating-form-stars-group.s-2 .stars-2 .star-off, 66 | .tool_courserating-form-stars-group .stars-3 .star-on, 67 | .tool_courserating-form-stars-group.s-3 .stars-3 .star-off, 68 | .tool_courserating-form-stars-group .stars-4 .star-on, 69 | .tool_courserating-form-stars-group.s-4 .stars-4 .star-off, 70 | .tool_courserating-form-stars-group .stars-5 .star-on, 71 | .tool_courserating-form-stars-group.s-5 .stars-5 .star-off { 72 | display: none; 73 | } 74 | .tool_courserating-form-stars-group.s-1 .stars-1 .star-on, 75 | .tool_courserating-form-stars-group.s-2 .stars-2 .star-on, 76 | .tool_courserating-form-stars-group.s-3 .stars-3 .star-on, 77 | .tool_courserating-form-stars-group.s-4 .stars-4 .star-on, 78 | .tool_courserating-form-stars-group.s-5 .stars-5 .star-on { 79 | display: inline-block; 80 | } 81 | -------------------------------------------------------------------------------- /classes/event/rating_updated.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\event; 18 | 19 | use tool_courserating\local\models\rating; 20 | 21 | /** 22 | * Event rating updated 23 | * 24 | * @package tool_courserating 25 | * @copyright 2022 Marina Glancy 26 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 | */ 28 | class rating_updated extends \core\event\base { 29 | /** 30 | * Init 31 | */ 32 | protected function init() { 33 | $this->data['crud'] = 'u'; 34 | $this->data['edulevel'] = self::LEVEL_PARTICIPATING; 35 | $this->data['objecttable'] = rating::TABLE; 36 | } 37 | 38 | /** 39 | * Event name 40 | * 41 | * @return string 42 | */ 43 | public static function get_name(): string { 44 | return get_string('event:rating_updated', 'tool_courserating'); 45 | } 46 | 47 | /** 48 | * Event description 49 | * 50 | * @return string 51 | */ 52 | public function get_description(): string { 53 | return "User {$this->relateduserid} has changed the rating for the course from " . 54 | $this->other['oldrating'] . " to " . $this->other['rating']; 55 | } 56 | 57 | /** 58 | * Shortcut to create an instance of event 59 | * 60 | * @param rating $object 61 | * @param \stdClass $recordold 62 | * @return self 63 | */ 64 | public static function create_from_rating(rating $object, \stdClass $recordold): self { 65 | /** @var self $event */ 66 | $event = static::create([ 67 | 'objectid' => $object->get('id'), 68 | 'courseid' => $object->get('courseid'), 69 | 'relateduserid' => $recordold->userid, 70 | 'context' => \context_course::instance($object->get('courseid')), 71 | 'other' => ['rating' => $object->get('rating'), 'oldrating' => $recordold->rating], 72 | ]); 73 | $event->add_record_snapshot($event->data['objecttable'], $object->to_record()); 74 | return $event; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /classes/event/flag_deleted.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\event; 18 | 19 | use tool_courserating\local\models\flag; 20 | use tool_courserating\local\models\rating; 21 | 22 | /** 23 | * Event flag deleted 24 | * 25 | * @package tool_courserating 26 | * @copyright 2022 Marina Glancy 27 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 | */ 29 | class flag_deleted extends \core\event\base { 30 | /** 31 | * Init 32 | */ 33 | protected function init() { 34 | $this->data['crud'] = 'd'; 35 | $this->data['edulevel'] = self::LEVEL_PARTICIPATING; 36 | $this->data['objecttable'] = flag::TABLE; 37 | } 38 | 39 | /** 40 | * Event name 41 | * 42 | * @return string 43 | */ 44 | public static function get_name(): string { 45 | return get_string('event:flag_deleted', 'tool_courserating'); 46 | } 47 | 48 | /** 49 | * Event description 50 | * 51 | * @return string 52 | */ 53 | public function get_description(): string { 54 | return "User {$this->userid} has revoked their flag on the course rating made by the user with id {$this->relateduserid}"; 55 | } 56 | 57 | /** 58 | * Shortcut to create an instance of event 59 | * 60 | * @param \stdClass $object 61 | * @param rating $rating 62 | * @return self 63 | */ 64 | public static function create_from_flag(\stdClass $object, rating $rating): self { 65 | /** @var self $event */ 66 | $event = static::create([ 67 | 'objectid' => $object->id, 68 | 'courseid' => $rating->get('courseid'), 69 | 'relateduserid' => $rating->get('userid'), 70 | 'context' => \context_course::instance($rating->get('courseid')), 71 | 'other' => ['ratingid' => $object->id], 72 | ]); 73 | $event->add_record_snapshot($event->data['objecttable'], $object); 74 | $event->add_record_snapshot(rating::TABLE, $rating->to_record()); 75 | return $event; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /classes/event/flag_created.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\event; 18 | 19 | use tool_courserating\local\models\flag; 20 | use tool_courserating\local\models\rating; 21 | 22 | /** 23 | * Event flag created 24 | * 25 | * @package tool_courserating 26 | * @copyright 2022 Marina Glancy 27 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 | */ 29 | class flag_created extends \core\event\base { 30 | /** 31 | * Init 32 | */ 33 | protected function init() { 34 | $this->data['crud'] = 'c'; 35 | $this->data['edulevel'] = self::LEVEL_PARTICIPATING; 36 | $this->data['objecttable'] = flag::TABLE; 37 | } 38 | 39 | /** 40 | * Event name 41 | * 42 | * @return string 43 | */ 44 | public static function get_name(): string { 45 | return get_string('event:flag_created', 'tool_courserating'); 46 | } 47 | 48 | /** 49 | * Event description 50 | * 51 | * @return string 52 | */ 53 | public function get_description(): string { 54 | return "User {$this->userid} has flagged the course rating made by the user with id {$this->relateduserid}"; 55 | } 56 | 57 | /** 58 | * Shortcut to create an instance of event 59 | * 60 | * @param flag $object 61 | * @param rating $rating 62 | * @return self 63 | */ 64 | public static function create_from_flag(flag $object, rating $rating): self { 65 | /** @var self $event */ 66 | $event = static::create([ 67 | 'objectid' => $object->get('id'), 68 | 'courseid' => $rating->get('courseid'), 69 | 'relateduserid' => $rating->get('userid'), 70 | 'context' => \context_course::instance($rating->get('courseid')), 71 | 'other' => ['ratingid' => $object->get('ratingid')], 72 | ]); 73 | $event->add_record_snapshot($event->data['objecttable'], $object->to_record()); 74 | $event->add_record_snapshot(rating::TABLE, $rating->to_record()); 75 | return $event; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /templates/star.mustache: -------------------------------------------------------------------------------- 1 | {{! 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 . 16 | }} 17 | {{! 18 | @template tool_courserating/star 19 | 20 | TODO. This template is not currently used because it is not compatible with a custom field format. 21 | 22 | Example context (json): 23 | { 24 | "uniqueid": "abcdef", 25 | "fillcolor": "rgb(0, 118, 206)", 26 | "offcolor": "rgb(238, 238, 238)", 27 | "rating": "50.00" 28 | } 29 | 30 | }} 31 | 33 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /db/upgrade.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Function to upgrade tool_courserating. 19 | * 20 | * @package tool_courserating 21 | * @category upgrade 22 | * @copyright 2022 Marina Glancy 23 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | /** 27 | * Function to upgrade tool_courserating. 28 | * 29 | * @param int $oldversion the version we are upgrading from 30 | * @return bool result 31 | */ 32 | function xmldb_tool_courserating_upgrade($oldversion) { 33 | global $CFG, $DB; 34 | $dbman = $DB->get_manager(); 35 | 36 | if ($oldversion < 2022111300) { 37 | // Define index avgrating (not unique) to be dropped form tool_courserating_summary. 38 | $table = new xmldb_table('tool_courserating_summary'); 39 | $index = new xmldb_index('avgrating', XMLDB_INDEX_NOTUNIQUE, ['avgrating']); 40 | 41 | // Conditionally launch drop index avgrating. 42 | if ($dbman->index_exists($table, $index)) { 43 | $dbman->drop_index($table, $index); 44 | } 45 | 46 | // Changing nullability of field avgrating on table tool_courserating_summary to null. 47 | $table = new xmldb_table('tool_courserating_summary'); 48 | $field = new xmldb_field('avgrating', XMLDB_TYPE_NUMBER, '10, 2', null, null, null, null, 'cntall'); 49 | 50 | // Launch change of nullability for field avgrating. 51 | $dbman->change_field_notnull($table, $field); 52 | 53 | $DB->execute('UPDATE {tool_courserating_summary} SET avgrating = NULL WHERE cntall = 0'); 54 | 55 | // Define index avgrating (not unique) to be added to tool_courserating_summary. 56 | $table = new xmldb_table('tool_courserating_summary'); 57 | $index = new xmldb_index('avgrating', XMLDB_INDEX_NOTUNIQUE, ['avgrating']); 58 | 59 | // Conditionally launch add index avgrating. 60 | if (!$dbman->index_exists($table, $index)) { 61 | $dbman->add_index($table, $index); 62 | } 63 | 64 | // Courserating savepoint reached. 65 | upgrade_plugin_savepoint(true, 2022111300, 'tool', 'courserating'); 66 | } 67 | 68 | return true; 69 | } 70 | -------------------------------------------------------------------------------- /templates/course_ratings_summary.mustache: -------------------------------------------------------------------------------- 1 | {{! 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 . 16 | }} 17 | {{! 18 | @template tool_courserating/course_ratings_summary 19 | 20 | Example context (json): 21 | { 22 | } 23 | 24 | }} 25 |
26 |
27 |
{{ avgrating }} 29 |
30 |
{{#stars}}{{> tool_courserating/stars }}{{/stars}}
31 |

{{#str}}ratinglabel, tool_courserating{{/str}}

32 |
33 |
34 |
35 | {{#lines}} 36 |
37 |
38 |
39 | {{#canviewreviews}} 40 | 42 | 43 | 44 | {{/canviewreviews}} 45 | {{^canviewreviews}} 46 | 47 | {{/canviewreviews}} 48 |
49 |
50 |
{{#star}}{{> tool_courserating/stars }}{{/star}}{{{ percent }}}
53 |
54 | {{/lines}} 55 |
56 |
57 |
58 | -------------------------------------------------------------------------------- /classes/event/rating_deleted.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\event; 18 | 19 | use tool_courserating\local\models\rating; 20 | 21 | /** 22 | * Event rating deleted 23 | * 24 | * @package tool_courserating 25 | * @copyright 2022 Marina Glancy 26 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 | */ 28 | class rating_deleted extends \core\event\base { 29 | /** 30 | * Init 31 | */ 32 | protected function init() { 33 | $this->data['crud'] = 'd'; 34 | $this->data['edulevel'] = self::LEVEL_PARTICIPATING; 35 | $this->data['objecttable'] = rating::TABLE; 36 | } 37 | 38 | /** 39 | * Event name 40 | * 41 | * @return string 42 | */ 43 | public static function get_name(): string { 44 | return get_string('event:rating_deleted', 'tool_courserating'); 45 | } 46 | 47 | /** 48 | * Event description 49 | * 50 | * @return string 51 | */ 52 | public function get_description(): string { 53 | return "User {$this->userid} has deleted course rating made by the user with id {$this->relateduserid}. " . 54 | "The old rating was " . $this->other['oldrating'] . " and the review had been flagged " . 55 | $this->other['flagcount'] . " times. Reason provided: " . s($this->other['reason']); 56 | } 57 | 58 | /** 59 | * Shortcut to create an instance of event 60 | * 61 | * @param \stdClass $recordold 62 | * @param int $flagcount 63 | * @param string $reason 64 | * @return self 65 | */ 66 | public static function create_from_rating(\stdClass $recordold, int $flagcount, string $reason): self { 67 | /** @var self $event */ 68 | $event = static::create([ 69 | 'objectid' => $recordold->id, 70 | 'courseid' => $recordold->courseid, 71 | 'context' => \context_course::instance($recordold->courseid, IGNORE_MISSING) ?? \context_system::instance(), 72 | 'relateduserid' => $recordold->userid, 73 | 'other' => ['oldrating' => $recordold->rating, 'flagcount' => $flagcount, 'reason' => $reason], 74 | ]); 75 | $event->add_record_snapshot($event->data['objecttable'], $recordold); 76 | return $event; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /classes/local/models/rating.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\local\models; 18 | 19 | /** 20 | * Model for rating table 21 | * 22 | * @package tool_courserating 23 | * @copyright 2022 Marina Glancy 24 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | class rating extends \core\persistent { 27 | /** @var string Table name */ 28 | public const TABLE = 'tool_courserating_rating'; 29 | 30 | /** 31 | * Return the definition of the properties of this model. 32 | * 33 | * @return array 34 | */ 35 | protected static function define_properties(): array { 36 | return [ 37 | 'courseid' => [ 38 | 'type' => PARAM_INT, 39 | ], 40 | 'userid' => [ 41 | 'type' => PARAM_INT, 42 | ], 43 | 'rating' => [ 44 | 'type' => PARAM_INT, 45 | ], 46 | 'review' => [ 47 | 'type' => PARAM_RAW, 48 | 'default' => '', 49 | ], 50 | 'hasreview' => [ 51 | 'type' => PARAM_INT, 52 | 'default' => 0, 53 | ], 54 | ]; 55 | } 56 | 57 | /** 58 | * Context of this rating 59 | * 60 | * @return \context_course 61 | */ 62 | public function get_context(): \context_course { 63 | return \context_course::instance($this->get('courseid')); 64 | } 65 | 66 | /** 67 | * Checks if review is actually empty (i.e. empty

or just newlines is not considered a content) 68 | * 69 | * @param string $review 70 | * @return bool 71 | */ 72 | public static function review_is_empty(string $review): bool { 73 | $review = clean_text($review); 74 | $tagstostrip = ['p', 'span', 'font', 'br', 'div']; 75 | foreach ($tagstostrip as $tag) { 76 | $review = preg_replace("/<\\/?" . $tag . "\b(.|\\s)*?>/", '', $review); 77 | } 78 | return strlen(trim($review)) == 0; 79 | } 80 | 81 | /** 82 | * Magic method to set review 83 | * 84 | * @param string $value 85 | */ 86 | protected function set_review($value) { 87 | $this->raw_set('review', $value); 88 | $this->raw_set('hasreview', (int)(!self::review_is_empty(($value)))); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/generator/lib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | use tool_courserating\api; 18 | use tool_courserating\helper; 19 | use tool_courserating\local\models\rating; 20 | 21 | /** 22 | * Generator 23 | * 24 | * @package tool_courserating 25 | * @copyright 2022 Marina Glancy 26 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 | */ 28 | class tool_courserating_generator extends testing_module_generator { 29 | /** 30 | * Set course rating mode 31 | * 32 | * @param int $courseid 33 | * @param int $mode 34 | */ 35 | public function set_course_rating_mode(int $courseid, int $mode) { 36 | if ($data = helper::get_course_rating_enabled_data_in_cfield($courseid)) { 37 | $data->instance_form_save((object)[ 38 | 'id' => $courseid, 39 | $data->get_form_element_name() => $mode, 40 | ]); 41 | api::reindex($courseid); 42 | } 43 | } 44 | 45 | /** 46 | * Set courserating config and reindex 47 | * 48 | * @param string $name 49 | * @param mixed $value 50 | */ 51 | public function set_config(string $name, $value) { 52 | set_config($name, $value, 'tool_courserating'); 53 | api::reindex(); 54 | } 55 | 56 | /** 57 | * Clear custom field cache 58 | * 59 | * Unfortunately we can not call the proper method from behat: 60 | * \core_course\customfield\course_handler::reset_caches() 61 | * 62 | * @return void 63 | */ 64 | public function clear_course_custom_field_cache() { 65 | $reflection = new \ReflectionProperty(\core_course\customfield\course_handler::class, 'singleton'); 66 | $reflection->setAccessible(true); 67 | $reflection->setValue(null, null); 68 | } 69 | 70 | /** 71 | * Create rating 72 | * 73 | * @param int $userid 74 | * @param int $courseid 75 | * @param int $rating 76 | * @param string $review 77 | * @return rating 78 | */ 79 | public function create_rating(int $userid, int $courseid, int $rating, string $review = '') { 80 | $this->clear_course_custom_field_cache(); 81 | $formdata = (object)[ 82 | 'review_editor' => ['text' => $review ?? '', 'format' => FORMAT_HTML], 83 | 'review' => $review ?? '', 84 | 'rating' => $rating, 85 | ]; 86 | return api::set_rating($courseid, $formdata, $userid); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /classes/external/stars_exporter.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\external; 18 | 19 | use core\external\exporter; 20 | use renderer_base; 21 | 22 | /** 23 | * Exporter for 5-star rating 24 | * 25 | * @package tool_courserating 26 | * @copyright 2022 Marina Glancy 27 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 | */ 29 | class stars_exporter extends exporter { 30 | /** 31 | * Constructor. 32 | * 33 | * @param float|null $rating 34 | */ 35 | public function __construct(?float $rating) { 36 | parent::__construct([], ['rating' => $rating]); 37 | } 38 | 39 | /** 40 | * Return the list of properties. 41 | * 42 | * @return array 43 | */ 44 | protected static function define_related() { 45 | return [ 46 | 'rating' => PARAM_FLOAT, 47 | ]; 48 | } 49 | 50 | /** 51 | * Return the list of additional properties used only for display. 52 | * 53 | * @return array - Keys with their types. 54 | */ 55 | protected static function define_other_properties() { 56 | return [ 57 | 'staricons' => [ 58 | 'type' => [ 59 | 'key' => ['type' => PARAM_RAW], 60 | 'component' => ['type' => PARAM_RAW], 61 | 'title' => ['type' => PARAM_RAW], 62 | ], 63 | 'multiple' => true, 64 | ], 65 | ]; 66 | } 67 | 68 | /** 69 | * One star 70 | * 71 | * @param float $rating 72 | * @return array 73 | */ 74 | protected function star(float $rating) { 75 | if ($rating < 0.25) { 76 | $icon = 'star-o'; 77 | } else if ($rating >= 0.75) { 78 | $icon = 'star'; 79 | } else { 80 | $icon = 'star-half'; 81 | } 82 | return (new \pix_icon($icon, '', 'tool_courserating', [])) 83 | ->export_for_pix(); 84 | } 85 | 86 | /** 87 | * Get the additional values to inject while exporting. 88 | * 89 | * @param renderer_base $output The renderer. 90 | * @return array Keys are the property names, values are their values. 91 | */ 92 | protected function get_other_values(renderer_base $output) { 93 | $rating = $this->related['rating']; 94 | $rating = round($rating * 20) / 20; 95 | $icons = array_fill(0, floor($rating), $this->star(1)); 96 | $icons[] = $this->star($rating - floor($rating)); 97 | $icons = array_slice($icons, 0, 5); 98 | $icons = array_merge($icons, array_fill(0, 5 - count($icons), $this->star(0))); 99 | return [ 100 | 'staricons' => $icons, 101 | ]; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /classes/external/course_rating_popup.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Class implementing WS tool_courserating_course_rating_popup 19 | * 20 | * @package tool_courserating 21 | * @copyright 2023 Marina Glancy 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace tool_courserating\external; 26 | 27 | use external_function_parameters; 28 | use external_single_structure; 29 | use external_api; 30 | use external_value; 31 | 32 | defined('MOODLE_INTERNAL') || die; 33 | 34 | require_once($CFG->libdir . '/externallib.php'); 35 | 36 | /** 37 | * Implementation of web service tool_courserating_course_rating_popup 38 | * 39 | * @package tool_courserating 40 | * @copyright 2023 Marina Glancy 41 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 | */ 43 | class course_rating_popup extends external_api { 44 | /** 45 | * Describes the parameters for tool_courserating_course_rating_popup 46 | * 47 | * @return external_function_parameters 48 | */ 49 | public static function execute_parameters(): external_function_parameters { 50 | return new external_function_parameters([ 51 | 'courseid' => new external_value(PARAM_INT, 'Course id'), 52 | ]); 53 | } 54 | 55 | /** 56 | * Implementation of web service tool_courserating_course_rating_popup 57 | * 58 | * @param mixed $courseid 59 | */ 60 | public static function execute($courseid) { 61 | global $PAGE, $OUTPUT, $CFG; 62 | require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/courserating/lib.php'); 63 | 64 | // Basically copied from the core_get_fragment WS except for login check. 65 | 66 | // Hack alert: Set a default URL to stop the annoying debug. 67 | $PAGE->set_url('/'); 68 | // Hack alert: Forcing bootstrap_renderer to initiate moodle page. 69 | $OUTPUT->header(); 70 | 71 | // Overwriting page_requirements_manager with the fragment one so only JS included from 72 | // this point is returned to the user. 73 | $PAGE->start_collecting_javascript_requirements(); 74 | $data = tool_courserating_output_fragment_course_ratings_popup(['courseid' => $courseid]); 75 | $jsfooter = $PAGE->requires->get_end_code(); 76 | $output = ['html' => $data, 'javascript' => $jsfooter]; 77 | return $output; 78 | } 79 | 80 | /** 81 | * Describe the return structure for tool_courserating_course_rating_popup 82 | * 83 | * @return external_single_structure 84 | */ 85 | public static function execute_returns(): external_single_structure { 86 | return new external_single_structure( 87 | [ 88 | 'html' => new external_value(PARAM_RAW, 'HTML fragment.'), 89 | 'javascript' => new external_value(PARAM_RAW, 'JavaScript fragment'), 90 | ] 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/observer_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating; 18 | 19 | use tool_courserating\local\models\flag; 20 | use tool_courserating\local\models\rating; 21 | use tool_courserating\local\models\summary; 22 | 23 | /** 24 | * Tests for permission class 25 | * 26 | * @package tool_courserating 27 | * @covers \tool_courserating\permission 28 | * @copyright 2022 Marina Glancy 29 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 | */ 31 | final class observer_test extends \advanced_testcase { 32 | /** 33 | * Set up 34 | */ 35 | public function setUp(): void { 36 | parent::setUp(); 37 | $this->resetAfterTest(); 38 | set_config( 39 | \tool_courserating\constants::SETTING_RATINGMODE, 40 | \tool_courserating\constants::RATEBY_ANYTIME, 41 | 'tool_courserating' 42 | ); 43 | } 44 | 45 | /** 46 | * Generator 47 | * 48 | * @return \tool_courserating_generator 49 | */ 50 | protected function get_generator(): \tool_courserating_generator { 51 | /** @var \tool_courserating_generator $generator */ 52 | $generator = self::getDataGenerator()->get_plugin_generator('tool_courserating'); 53 | return $generator; 54 | } 55 | 56 | public function test_course_updated(): void { 57 | $course = $this->getDataGenerator()->create_course(); 58 | 59 | $this->get_generator()->set_config(constants::SETTING_PERCOURSE, 1); 60 | $this->get_generator()->set_config(constants::SETTING_RATINGMODE, constants::RATEBY_NOONE); 61 | 62 | $this->assertEquals(constants::RATEBY_NOONE, summary::get_for_course($course->id)->get('ratingmode')); 63 | update_course((object)['id' => $course->id, 'customfield_' . constants::CFIELD_RATINGMODE => constants::RATEBY_ANYTIME]); 64 | $this->assertEquals(constants::RATEBY_ANYTIME, summary::get_for_course($course->id)->get('ratingmode')); 65 | } 66 | 67 | public function test_course_deleted(): void { 68 | global $DB; 69 | $course = $this->getDataGenerator()->create_course(); 70 | $user1 = $this->getDataGenerator()->create_user(); 71 | $user2 = $this->getDataGenerator()->create_user(); 72 | 73 | $this->setUser($user1); 74 | $rating = api::set_rating($course->id, (object)['rating' => 5]); 75 | 76 | $this->setUser($user2); 77 | api::flag_review($rating->get('id')); 78 | 79 | delete_course($course->id, false); 80 | $this->assertEmpty($DB->get_records(summary::TABLE, ['courseid' => $course->id])); 81 | $this->assertEmpty($DB->get_records(rating::TABLE, [])); 82 | $this->assertEmpty($DB->get_records(flag::TABLE, [])); 83 | } 84 | 85 | public function test_course_created(): void { 86 | global $DB; 87 | $course = $this->getDataGenerator()->create_course(); 88 | $this->assertNotEmpty($DB->get_records(summary::TABLE, ['courseid' => $course->id])); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /classes/constants.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating; 18 | 19 | /** 20 | * Plugin constants 21 | * 22 | * @package tool_courserating 23 | * @copyright 2022 Marina Glancy 24 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | class constants { 27 | /** @var int */ 28 | const REVIEWS_PER_PAGE = 10; 29 | 30 | /** @var string */ 31 | const CFIELD_RATING = 'tool_courserating'; 32 | /** @var string */ 33 | const CFIELD_RATINGMODE = 'tool_courserating_mode'; 34 | /** @var string */ 35 | const CFIELD_ALLOWREVIEWS = 'tool_courserating_reviews'; 36 | 37 | /** @var int */ 38 | const RATEBY_NOONE = 1; 39 | /** @var int */ 40 | const RATEBY_ANYTIME = 2; 41 | /** @var int */ 42 | const RATEBY_COMPLETED = 3; 43 | 44 | /** @var string */ 45 | const SETTING_RATINGMODE = 'ratingmode'; 46 | /** @var int */ 47 | const ALLOWREVIEWS_NO = 1; 48 | 49 | /** @var int */ 50 | const ALLOWREVIEWS_HIDDEN = 2; 51 | 52 | /** @var int */ 53 | const ALLOWREVIEWS_VISIBLE = 3; 54 | 55 | /** @var string */ 56 | const SETTING_ALLOWREVIEWS = 'allowreviews'; 57 | /** @var string */ 58 | const SETTING_PERCOURSE = 'percourse'; 59 | /** @var string */ 60 | const SETTING_STARCOLOR = 'starcolor'; 61 | /** @var string */ 62 | const SETTING_RATINGCOLOR = 'ratingcolor'; 63 | /** @var string */ 64 | const SETTING_DISPLAYEMPTY = 'displayempty'; 65 | /** @var string */ 66 | const SETTING_USEHTML = 'usehtml'; 67 | /** @var string */ 68 | const SETTING_PARENTCSS = 'parentcss'; 69 | 70 | /** @var string */ 71 | const SETTING_STARCOLOR_DEFAULT = '#e59819'; 72 | /** @var string */ 73 | const SETTING_RATINGCOLOR_DEFAULT = '#b4690e'; 74 | 75 | /** @var string */ 76 | const COLOR_GRAY = '#a0a0a0'; 77 | 78 | /** 79 | * List of options for the 'ratingmode' selector 80 | * 81 | * @return \lang_string[] 82 | */ 83 | public static function rated_courses_options() { 84 | return [ 85 | self::RATEBY_NOONE => new \lang_string('ratebynoone', 'tool_courserating'), 86 | self::RATEBY_ANYTIME => new \lang_string('ratebyanybody', 'tool_courserating'), 87 | self::RATEBY_COMPLETED => new \lang_string('ratebycompleted', 'tool_courserating'), 88 | ]; 89 | } 90 | 91 | /** 92 | * List of options for the 'allowreviews' selector 93 | * 94 | * @return \lang_string[] 95 | */ 96 | public static function allow_reviews_options() { 97 | return [ 98 | self::ALLOWREVIEWS_NO => new \lang_string('allowreviewsno', 'tool_courserating'), 99 | self::ALLOWREVIEWS_HIDDEN => new \lang_string('allowreviewshidden', 'tool_courserating'), 100 | self::ALLOWREVIEWS_VISIBLE => new \lang_string('allowreviewsvisible', 'tool_courserating'), 101 | ]; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /classes/form/deleterating.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\form; 18 | 19 | use context; 20 | use core_form\dynamic_form; 21 | use moodle_url; 22 | use tool_courserating\api; 23 | use tool_courserating\local\models\rating; 24 | use tool_courserating\permission; 25 | 26 | /** 27 | * Form for deleting a rating (by manager) 28 | * 29 | * @package tool_courserating 30 | * @copyright 2022 Marina Glancy 31 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 32 | */ 33 | class deleterating extends dynamic_form { 34 | /** @var rating */ 35 | protected $rating; 36 | 37 | /** 38 | * Id of the current rating 39 | * 40 | * @return int 41 | */ 42 | protected function get_rating_id(): int { 43 | $id = $this->optional_param('ratingid', 0, PARAM_INT); 44 | if ($id <= 0) { 45 | throw new \moodle_exception('missingparam', '', '', 'ratingid'); 46 | } 47 | return $id; 48 | } 49 | 50 | /** 51 | * Current rating 52 | * 53 | * @return rating 54 | */ 55 | protected function get_rating(): rating { 56 | if (!$this->rating) { 57 | $this->rating = new rating($this->get_rating_id()); 58 | } 59 | return $this->rating; 60 | } 61 | 62 | /** 63 | * Current context 64 | * 65 | * @return \context 66 | */ 67 | protected function get_context_for_dynamic_submission(): context { 68 | return \context_course::instance($this->get_rating()->get('courseid')); 69 | } 70 | 71 | /** 72 | * Check access and throw exception if not allowed 73 | * 74 | * @return void 75 | * @throws \moodle_exception 76 | */ 77 | protected function check_access_for_dynamic_submission(): void { 78 | permission::require_can_delete_rating($this->get_rating_id(), $this->get_rating()->get('courseid')); 79 | } 80 | 81 | /** 82 | * Process submission 83 | * 84 | * @return mixed|void 85 | */ 86 | public function process_dynamic_submission() { 87 | $rv = ['ratingid' => $this->get_rating_id(), 'courseid' => $this->get_rating()->get('courseid')]; 88 | api::delete_rating($this->get_rating_id(), $this->get_data()->reason); 89 | return $rv; 90 | } 91 | 92 | /** 93 | * Load in existing data as form defaults 94 | */ 95 | public function set_data_for_dynamic_submission(): void { 96 | $this->set_data(['ratingid' => $this->get_rating_id()]); 97 | } 98 | 99 | /** 100 | * Fake URL for atto auto-save 101 | * 102 | * @return moodle_url 103 | */ 104 | protected function get_page_url_for_dynamic_submission(): moodle_url { 105 | return new moodle_url( 106 | '/course/view.php', 107 | ['id' => $this->get_rating()->get('courseid'), 'deleterating' => $this->get_rating_id()] 108 | ); 109 | } 110 | 111 | /** 112 | * Form definition 113 | */ 114 | protected function definition() { 115 | $mform = $this->_form; 116 | 117 | $mform->addElement('hidden', 'ratingid'); 118 | $mform->setType('ratingid', PARAM_INT); 119 | 120 | $mform->addElement('textarea', 'reason', get_string('deletereason', 'tool_courserating')); 121 | $mform->setType('reason', PARAM_TEXT); 122 | $mform->addRule('reason', get_string('required'), 'required', null, 'client'); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /tests/behat/courseoverrides.feature: -------------------------------------------------------------------------------- 1 | @tool @tool_courserating @javascript 2 | Feature: Testing course settings overrides in tool_courserating 3 | 4 | Background: 5 | Given the following "courses" exist: 6 | | fullname | shortname | numsections | 7 | | Course 1 | C1 | 1 | 8 | | Course 2 | C2 | 1 | 9 | | Course 3 | C3 | 1 | 10 | Given the following "users" exist: 11 | | username | firstname | lastname | email | 12 | | teacher1 | Teacher | 1 | teacher1@example.com | 13 | | student1 | Student | 1 | student1@example.com | 14 | | student2 | Student | 2 | student2@example.com | 15 | | student3 | Student | 3 | student3@example.com | 16 | | manager1 | Manager | 1 | manager1@example.com | 17 | And the following "course enrolments" exist: 18 | | user | course | role | 19 | | student1 | C1 | student | 20 | | student2 | C1 | student | 21 | | student3 | C1 | student | 22 | | teacher1 | C1 | editingteacher | 23 | And the following "tool_courserating > ratings" exist: 24 | | user | course | rating | review | 25 | | student1 | C1 | 3 | abcdef | 26 | | student2 | C1 | 4 | hello | 27 | | student3 | C1 | 1 | | 28 | And the following "role assigns" exist: 29 | | user | role | contextlevel | reference | 30 | | manager1 | manager | System | | 31 | 32 | Scenario: When admin enables course overrides, teacher can change settings per course 33 | Given I log in as "teacher1" 34 | And I am on "Course 1" course homepage 35 | When I navigate to "Settings" in current page administration 36 | And I expand all fieldsets 37 | And I should not see "When can courses be rated" 38 | And I should not see "Accompanying rating with a review" 39 | And I log out 40 | And I log in as "admin" 41 | And I navigate to "Courses > Course ratings" in site administration 42 | And I set the field "Course overrides" to "1" 43 | And I press "Save changes" 44 | And I run all adhoc tasks 45 | And I log out 46 | And I log in as "teacher1" 47 | And I am on "Course 1" course homepage 48 | And I navigate to "Settings" in current page administration 49 | And I wait "1" seconds 50 | And I expand all fieldsets 51 | And I should see "When can courses be rated" 52 | And I should see "Accompanying rating with a review" 53 | And I log out 54 | And I log in as "admin" 55 | And I navigate to "Courses > Course ratings" in site administration 56 | And I set the field "Course overrides" to "0" 57 | And I press "Save changes" 58 | And I run all adhoc tasks 59 | And I log out 60 | And I log in as "teacher1" 61 | And I am on "Course 1" course homepage 62 | And I navigate to "Settings" in current page administration 63 | And I wait "1" seconds 64 | And I expand all fieldsets 65 | And I should not see "When can courses be rated" 66 | And I should not see "Accompanying rating with a review" 67 | And I log out 68 | 69 | Scenario: Course settings overrule the site settings 70 | And I log in as "admin" 71 | And I navigate to "Courses > Course ratings" in site administration 72 | And I set the field "Course overrides" to "1" 73 | And I press "Save changes" 74 | And I run all adhoc tasks 75 | And I log out 76 | And I log in as "teacher1" 77 | And I am on "Course 1" course homepage 78 | And I navigate to "Settings" in current page administration 79 | And I wait "1" seconds 80 | And I expand all fieldsets 81 | And I set the field "Accompanying rating with a review" to "No text reviews allowed" 82 | And I press "Save and display" 83 | And I log out 84 | When I log in as "student1" 85 | And I am on "Course 1" course homepage 86 | And "2.7" "text" should exist in the "#page-header .tool_courserating-ratings" "css_element" 87 | And I follow "Edit your rating" 88 | And I click on ".tool_courserating-form-stars-group .stars-4" "css_element" 89 | And I should not see "Review" in the "Leave a rating" "dialogue" 90 | And I press "Save changes" 91 | And "3.0" "text" should exist in the "#page-header .tool_courserating-ratings" "css_element" 92 | -------------------------------------------------------------------------------- /classes/output/renderer.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\output; 18 | 19 | use plugin_renderer_base; 20 | use tool_courserating\api; 21 | use tool_courserating\constants; 22 | use tool_courserating\external\summary_exporter; 23 | use tool_courserating\external\ratings_list_exporter; 24 | use tool_courserating\helper; 25 | use tool_courserating\local\models\rating; 26 | use tool_courserating\local\models\summary; 27 | use tool_courserating\permission; 28 | 29 | /** 30 | * Renderer 31 | * 32 | * @package tool_courserating 33 | * @copyright 2022 Marina Glancy 34 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class renderer extends plugin_renderer_base { 37 | /** 38 | * Reads contents of a custom field and displays it 39 | * 40 | * @param int $courseid 41 | * @return string 42 | */ 43 | public function cfield(int $courseid): string { 44 | $summary = summary::get_for_course($courseid); 45 | $data = (new summary_exporter(0, $summary))->export($this); 46 | return $this->render_from_template('tool_courserating/summary_for_cfield', $data); 47 | } 48 | 49 | /** 50 | * Content of a course rating summary popup 51 | * 52 | * @param int $courseid 53 | * @return string 54 | */ 55 | public function course_ratings_popup(int $courseid): string { 56 | global $USER; 57 | $data1 = (new summary_exporter($courseid))->export($this); 58 | $canviewreviews = permission::can_view_reviews($courseid); 59 | $data2 = $canviewreviews ? (new ratings_list_exporter(['courseid' => $courseid]))->export($this) : []; 60 | $data = (array)$data1 + (array)$data2; 61 | $data['canviewreviews'] = $canviewreviews; 62 | if ($canviewreviews && helper::get_course_allow_reviews($courseid) == constants::ALLOWREVIEWS_HIDDEN) { 63 | $data['hiddenreviewsnotification'] = (new \core\output\notification( 64 | get_string('reviewsarehidden', 'tool_courserating'), 65 | \core\output\notification::NOTIFY_WARNING 66 | ))->export_for_template($this); 67 | } 68 | $data['canrate'] = permission::can_add_rating($courseid); 69 | $data['hasrating'] = $data['canrate'] && rating::get_record(['userid' => $USER->id, 'courseid' => $courseid]); 70 | $this->page->requires->js_call_amd('tool_courserating/rating', 'setupViewRatingsPopup', []); 71 | return $this->render_from_template('tool_courserating/course_ratings_popup', $data); 72 | } 73 | 74 | /** 75 | * Course review widget to be added to the course page 76 | * 77 | * @param int $courseid 78 | * @return string 79 | */ 80 | public function course_rating_block(int $courseid): string { 81 | global $CFG, $USER; 82 | if (!permission::can_view_ratings($courseid)) { 83 | return ''; 84 | } 85 | $summary = summary::get_for_course($courseid); 86 | $canrate = permission::can_add_rating($courseid); 87 | $data = (new summary_exporter(0, $summary, $canrate))->export($this); 88 | $data->canrate = $canrate; 89 | $data->hasrating = $canrate && rating::get_record(['userid' => $USER->id, 'courseid' => $courseid]); 90 | 91 | if ($parentcss = helper::get_setting(constants::SETTING_PARENTCSS)) { 92 | $data->parentelement = $parentcss; 93 | } else { 94 | $data->parentelement = '#page-header'; 95 | $data->extraclasses = 'pb-2'; 96 | } 97 | return $this->render_from_template('tool_courserating/course_rating_block', $data); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /classes/external/ratings_list_exporter.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\external; 18 | 19 | use core\external\exporter; 20 | use renderer_base; 21 | use tool_courserating\constants; 22 | use tool_courserating\local\models\rating; 23 | 24 | /** 25 | * Exporter for the list of ratings (used in the rating summary popup) 26 | * 27 | * @package tool_courserating 28 | * @copyright 2022 Marina Glancy 29 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 | */ 31 | class ratings_list_exporter extends exporter { 32 | /** 33 | * Constructor. 34 | * 35 | * @param array $related - related objects. 36 | */ 37 | public function __construct($related) { 38 | parent::__construct([], $related); 39 | } 40 | 41 | /** 42 | * Return the list of properties. 43 | * 44 | * @return array 45 | */ 46 | protected static function define_related() { 47 | return [ 48 | 'limit' => PARAM_INT . '?', 49 | 'offset' => PARAM_INT . '?', 50 | 'courseid' => PARAM_INT . '?', 51 | 'showempty' => PARAM_BOOL . '?', 52 | 'withrating' => PARAM_INT . '?', 53 | ]; 54 | } 55 | 56 | /** 57 | * Return the list of additional properties used only for display. 58 | * 59 | * @return array - Keys with their types. 60 | */ 61 | protected static function define_other_properties() { 62 | return [ 63 | 'ratings' => [ 64 | 'type' => rating_exporter::read_properties_definition(), 65 | 'multiple' => true, 66 | ], 67 | 'offset' => [ 68 | 'type' => PARAM_INT, 69 | ], 70 | 'hasmore' => [ 71 | 'type' => PARAM_BOOL, 72 | ], 73 | 'nextoffset' => [ 74 | 'type' => PARAM_INT, 75 | ], 76 | 'courseid' => [ 77 | 'type' => PARAM_INT, 78 | ], 79 | 'systemcontextid' => [ 80 | 'type' => PARAM_INT, 81 | ], 82 | 'withrating' => [ 83 | 'type' => PARAM_INT, 84 | ], 85 | ]; 86 | } 87 | 88 | /** 89 | * Get the additional values to inject while exporting. 90 | * 91 | * @param renderer_base $output The renderer. 92 | * @return array Keys are the property names, values are their values. 93 | */ 94 | protected function get_other_values(renderer_base $output) { 95 | $courseid = $this->related['courseid']; 96 | $offset = $this->related['offset'] ?: 0; 97 | $limit = $this->related['limit'] ?: constants::REVIEWS_PER_PAGE; 98 | $withrating = $this->related['withrating'] ?: 0; 99 | 100 | $reviews = rating::get_records_select( 101 | 'courseid = :courseid' . 102 | (empty($this->related['showempty']) ? ' AND hasreview = 1' : '') . 103 | ($withrating ? ' AND rating = :rating' : ''), 104 | ['courseid' => $courseid, 'rating' => $withrating], 105 | 'timemodified DESC, id DESC', 106 | '*', 107 | $offset, 108 | $limit + 1 109 | ); 110 | 111 | $data = [ 112 | 'ratings' => [], 113 | 'offset' => $offset, 114 | 'hasmore' => count($reviews) > $limit, 115 | 'nextoffset' => $offset + $limit, 116 | 'courseid' => $courseid, 117 | 'systemcontextid' => \context_system::instance()->id, 118 | 'withrating' => $withrating, 119 | ]; 120 | 121 | $reviews = array_slice(array_values($reviews), 0, $limit); 122 | foreach ($reviews as $review) { 123 | $data['ratings'][] = (new rating_exporter($review))->export($output); 124 | } 125 | 126 | return $data; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /classes/external/rating_exporter.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\external; 18 | 19 | use core\external\persistent_exporter; 20 | use core_user\external\user_summary_exporter; 21 | use tool_courserating\api; 22 | use tool_courserating\constants; 23 | use tool_courserating\helper; 24 | use tool_courserating\local\models\flag; 25 | use tool_courserating\local\models\rating; 26 | use tool_courserating\output\renderer; 27 | use tool_courserating\permission; 28 | use tool_dataprivacy\category; 29 | use tool_dataprivacy\context_instance; 30 | 31 | /** 32 | * Class for exporting field data. 33 | * 34 | * @package tool_courserating 35 | * @copyright 2022 Marina Glancy 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 | */ 38 | class rating_exporter extends persistent_exporter { 39 | /** 40 | * Defines the persistent class. 41 | * 42 | * @return string 43 | */ 44 | protected static function define_class() { 45 | return rating::class; 46 | } 47 | 48 | /** 49 | * Returns a list of objects that are related. 50 | * 51 | * @return array 52 | */ 53 | protected static function define_related() { 54 | return [ 55 | 'context' => 'context?', 56 | ]; 57 | } 58 | 59 | /** 60 | * Get the formatting parameters for the review field. 61 | * 62 | * @return array 63 | */ 64 | protected function get_format_parameters_for_review() { 65 | return [ 66 | 'component' => 'tool_courserating', 67 | 'filearea' => 'review', 68 | 'itemid' => $this->data->id, 69 | 'context' => \context_course::instance($this->data->courseid), 70 | ]; 71 | } 72 | 73 | /** 74 | * Return a list of additional properties used only for display 75 | * 76 | * @return array 77 | */ 78 | protected static function define_other_properties(): array { 79 | return [ 80 | 'user' => ['type' => user_summary_exporter::read_properties_definition(), 'optional' => true], 81 | 'reviewtext' => ['type' => PARAM_RAW], 82 | 'reviewstars' => ['type' => stars_exporter::read_properties_definition()], 83 | 'reviewdate' => ['type' => PARAM_RAW], 84 | 'ratingflag' => [ 85 | 'type' => 'array', 86 | 'optional' => true, 87 | ], 88 | ]; 89 | } 90 | 91 | /** 92 | * Get additional values to inject while exporting 93 | * 94 | * @param \renderer_base $output 95 | * @return array 96 | */ 97 | protected function get_other_values(\renderer_base $output): array { 98 | global $USER; 99 | $result = []; 100 | 101 | if ($user = \core_user::get_user($this->data->userid)) { 102 | $userexporter = new user_summary_exporter($user); 103 | $result['user'] = $userexporter->export($output); 104 | } else { 105 | $result['user'] = []; 106 | } 107 | 108 | $result['reviewtext'] = helper::format_review($this->data->review, $this->data); 109 | 110 | $result['reviewstars'] = (new stars_exporter($this->data->rating))->export($output); 111 | 112 | $result['reviewdate'] = helper::format_date($this->data->timemodified); 113 | 114 | if (permission::can_delete_rating($this->data->id, $this->data->courseid)) { 115 | $flags = flag::count_records(['ratingid' => $this->data->id]); 116 | } else { 117 | $flags = []; 118 | } 119 | $flagged = flag::get_records(['ratingid' => $this->data->id, 'userid' => $USER->id]) ? true : false; 120 | 121 | $result['ratingflag'] = (object)[ 122 | 'flagged' => $flagged, 123 | 'toggleflag' => api::get_flag_inplace_editable($this->data->id, $flagged)->export_for_template($output), 124 | 'flags' => $flags, 125 | 'candelete' => $flags > 0, 126 | 'ratingid' => $this->data->id, 127 | ]; 128 | 129 | return $result; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /classes/external/summary_exporter.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\external; 18 | 19 | use core\external\exporter; 20 | use renderer_base; 21 | use tool_courserating\constants; 22 | use tool_courserating\helper; 23 | use tool_courserating\local\models\summary; 24 | use tool_courserating\permission; 25 | 26 | /** 27 | * Exporter for rating summary (how many people gave 5 stars, etc) 28 | * 29 | * @package tool_courserating 30 | * @copyright 2022 Marina Glancy 31 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 32 | */ 33 | class summary_exporter extends exporter { 34 | /** @var summary */ 35 | protected $summary; 36 | 37 | /** 38 | * Constructor. 39 | * 40 | * @param int $courseid 41 | * @param summary|null $summary 42 | * @param bool $overridedisplayempty 43 | */ 44 | public function __construct(int $courseid, ?summary $summary = null, bool $overridedisplayempty = false) { 45 | if (!$summary) { 46 | $records = summary::get_records(['courseid' => $courseid]); 47 | if (count($records)) { 48 | $summary = reset($records); 49 | } else { 50 | $summary = new summary(0, (object)['courseid' => $courseid]); 51 | } 52 | } 53 | $this->summary = $summary; 54 | parent::__construct([], ['overridedisplayempty' => $overridedisplayempty]); 55 | } 56 | 57 | /** 58 | * Return the list of properties. 59 | * 60 | * @return array 61 | */ 62 | protected static function define_related() { 63 | return [ 64 | 'overridedisplayempty' => 'bool', 65 | ]; 66 | } 67 | 68 | /** 69 | * Return the list of additional properties used only for display. 70 | * 71 | * @return array - Keys with their types. 72 | */ 73 | protected static function define_other_properties() { 74 | return [ 75 | 'avgrating' => ['type' => PARAM_RAW], 76 | 'stars' => ['type' => stars_exporter::read_properties_definition()], 77 | 'lines' => [ 78 | 'type' => [ 79 | 'rating' => ['type' => PARAM_INT], 80 | 'star' => ['type' => stars_exporter::read_properties_definition()], 81 | 'percent' => ['type' => PARAM_RAW], 82 | ], 83 | 'multiple' => true, 84 | ], 85 | 'courseid' => ['type' => PARAM_INT], 86 | 'cntall' => ['type' => PARAM_INT], 87 | 'displayempty' => ['type' => PARAM_INT], 88 | ]; 89 | } 90 | 91 | /** 92 | * Get the additional values to inject while exporting. 93 | * 94 | * @param renderer_base $output The renderer. 95 | * @return array Keys are the property names, values are their values. 96 | */ 97 | protected function get_other_values(renderer_base $output) { 98 | $summary = $this->summary; 99 | $courseid = $summary->get('courseid'); 100 | $avgrating = $summary->get('cntall') ? $summary->get('avgrating') : 0; 101 | $data = [ 102 | 'avgrating' => $summary->get('cntall') ? sprintf("%.1f", $summary->get('avgrating')) : '-', 103 | 'cntall' => $summary->get('cntall'), 104 | 'stars' => (new stars_exporter($avgrating))->export($output), 105 | 'lines' => [], 106 | 'courseid' => $courseid, 107 | 'displayempty' => !empty($this->related['overridedisplayempty']) 108 | || helper::get_setting(constants::SETTING_DISPLAYEMPTY), 109 | ]; 110 | foreach ([5, 4, 3, 2, 1] as $line) { 111 | $percent = $summary->get('cntall') ? round(100 * $summary->get('cnt0' . $line) / $summary->get('cntall')) : 0; 112 | $data['lines'][] = [ 113 | 'rating' => $line, 114 | 'star' => (new stars_exporter($line))->export($output), 115 | 'percent' => $percent . '%', 116 | ]; 117 | } 118 | 119 | return $data; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /settings.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Plugin administration pages are defined here. 19 | * 20 | * @package tool_courserating 21 | * @category admin 22 | * @copyright 2022 Marina Glancy 23 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | if ($hassiteconfig) { 29 | // Disable ratings in unittests by default, otherwise it breaks core tests. 30 | $isunittest = defined('PHPUNIT_TEST') && PHPUNIT_TEST; 31 | 32 | $temp = new admin_settingpage('tool_courserating', new lang_string('pluginname', 'tool_courserating')); 33 | $el = new admin_setting_configselect( 34 | 'tool_courserating/' . \tool_courserating\constants::SETTING_RATINGMODE, 35 | new lang_string('ratingmode', 'tool_courserating'), 36 | new lang_string('ratingmodeconfig', 'tool_courserating'), 37 | $isunittest ? \tool_courserating\constants::RATEBY_NOONE : \tool_courserating\constants::RATEBY_ANYTIME, 38 | \tool_courserating\constants::rated_courses_options() 39 | ); 40 | $el->set_updatedcallback('tool_courserating\task\reindex::schedule'); 41 | $temp->add($el); 42 | 43 | $el = new admin_setting_configselect( 44 | 'tool_courserating/' . \tool_courserating\constants::SETTING_ALLOWREVIEWS, 45 | new lang_string('allowreviews', 'tool_courserating'), 46 | new lang_string('allowreviewsconfig', 'tool_courserating'), 47 | \tool_courserating\constants::ALLOWREVIEWS_VISIBLE, 48 | \tool_courserating\constants::allow_reviews_options() 49 | ); 50 | $temp->add($el); 51 | 52 | $el = new admin_setting_configcheckbox( 53 | 'tool_courserating/' . \tool_courserating\constants::SETTING_PERCOURSE, 54 | new lang_string('percourseoverride', 'tool_courserating'), 55 | new lang_string('percourseoverrideconfig', 'tool_courserating'), 56 | 0 57 | ); 58 | $el->set_updatedcallback('tool_courserating\task\reindex::schedule'); 59 | $temp->add($el); 60 | 61 | $el = new admin_setting_configcolourpicker( 62 | 'tool_courserating/' . \tool_courserating\constants::SETTING_STARCOLOR, 63 | new lang_string('colorstar', 'tool_courserating'), 64 | '', 65 | \tool_courserating\constants::SETTING_STARCOLOR_DEFAULT 66 | ); 67 | $el->set_updatedcallback('tool_courserating\task\reindex::schedule'); 68 | $temp->add($el); 69 | 70 | $el = new admin_setting_configcolourpicker( 71 | 'tool_courserating/' . \tool_courserating\constants::SETTING_RATINGCOLOR, 72 | new lang_string('colorrating', 'tool_courserating'), 73 | new lang_string('colorratingconfig', 'tool_courserating'), 74 | \tool_courserating\constants::SETTING_RATINGCOLOR_DEFAULT 75 | ); 76 | $el->set_updatedcallback('tool_courserating\task\reindex::schedule'); 77 | $temp->add($el); 78 | 79 | $el = new admin_setting_configcheckbox( 80 | 'tool_courserating/' . \tool_courserating\constants::SETTING_DISPLAYEMPTY, 81 | new lang_string('displayempty', 'tool_courserating'), 82 | new lang_string('displayemptyconfig', 'tool_courserating'), 83 | 0 84 | ); 85 | $el->set_updatedcallback('tool_courserating\task\reindex::schedule'); 86 | $temp->add($el); 87 | 88 | $el = new admin_setting_configcheckbox( 89 | 'tool_courserating/' . \tool_courserating\constants::SETTING_USEHTML, 90 | new lang_string('usehtml', 'tool_courserating'), 91 | new lang_string('usehtmlconfig', 'tool_courserating'), 92 | 0 93 | ); 94 | $temp->add($el); 95 | 96 | $el = new admin_setting_configtext( 97 | 'tool_courserating/' . \tool_courserating\constants::SETTING_PARENTCSS, 98 | new lang_string('parentcss', 'tool_courserating'), 99 | new lang_string('parentcssconfig', 'tool_courserating'), 100 | '' 101 | ); 102 | $temp->add($el); 103 | 104 | $temp->add(new admin_setting_description( 105 | 'tool_courserating/description', 106 | '', 107 | new lang_string('settingsdescription', 'tool_courserating') 108 | )); 109 | 110 | $ADMIN->add('courses', $temp); 111 | } 112 | -------------------------------------------------------------------------------- /classes/reportbuilder/local/systemreports/course_ratings_report.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\reportbuilder\local\systemreports; 18 | 19 | use context_system; 20 | use core_reportbuilder\local\entities\user; 21 | use core_reportbuilder\local\helpers\database; 22 | use core_reportbuilder\system_report; 23 | use stdClass; 24 | use tool_courserating\helper; 25 | use tool_courserating\permission; 26 | use tool_courserating\reportbuilder\local\entities\rating; 27 | 28 | /** 29 | * Config changes system report class implementation 30 | * 31 | * @package tool_courserating 32 | * @copyright 2022 Marina Glancy 33 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 | */ 35 | class course_ratings_report extends system_report { 36 | /** 37 | * Get course id 38 | * 39 | * @return mixed 40 | */ 41 | protected function get_course_id() { 42 | return $this->get_context()->instanceid; 43 | } 44 | 45 | /** 46 | * Initialise report, we need to set the main table, load our entities and set columns/filters 47 | */ 48 | protected function initialise(): void { 49 | // Our main entity, it contains all of the column definitions that we need. 50 | $ratingentity = new rating(); 51 | $ratingtablealias = $ratingentity->get_table_alias('tool_courserating_rating'); 52 | $paramcourseid = database::generate_param_name(); 53 | $this->add_base_condition_sql( 54 | "{$ratingtablealias}.courseid = :{$paramcourseid}", 55 | [$paramcourseid => $this->get_course_id()] 56 | ); 57 | 58 | $this->set_main_table('tool_courserating_rating', $ratingtablealias); 59 | $this->add_entity($ratingentity); 60 | 61 | // We can join the "user" entity to our "main" entity using standard SQL JOIN. 62 | $entityuser = new user(); 63 | $entityuseralias = $entityuser->get_table_alias('user'); 64 | $this->add_entity($entityuser 65 | ->add_join("JOIN {user} {$entityuseralias} ON {$entityuseralias}.id = {$ratingtablealias}.userid")); 66 | 67 | // Now we can call our helper methods to add the content we want to include in the report. 68 | $this->add_columns(); 69 | $this->add_filters(); 70 | 71 | // Set if report can be downloaded. 72 | $this->set_downloadable(true, get_string('pluginname', 'tool_courserating')); 73 | } 74 | 75 | /** 76 | * Validates access to view this report 77 | * 78 | * @return bool 79 | */ 80 | protected function can_view(): bool { 81 | return permission::can_view_report($this->get_course_id()); 82 | } 83 | 84 | /** 85 | * Adds the columns we want to display in the report 86 | * 87 | * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their 88 | * unique identifier 89 | */ 90 | protected function add_columns(): void { 91 | $columns = [ 92 | 'user:fullnamewithpicturelink', 93 | 'rating:timemodified', 94 | 'rating:rating', 95 | 'rating:review', 96 | 'rating:flags', 97 | ]; 98 | if (permission::can_delete_rating(0, $this->get_course_id())) { 99 | $columns[] = 'rating:actions'; 100 | } 101 | 102 | $this->add_columns_from_entities($columns); 103 | 104 | // Default sorting. 105 | $this->set_initial_sort_column('rating:timemodified', SORT_DESC); 106 | 107 | // Custom callbacks. 108 | if ($column = $this->get_column('rating:rating')) { 109 | $column->set_callback([helper::class, 'format_rating_in_course_report']); 110 | } 111 | if ($column = $this->get_column('rating:flags')) { 112 | $column->set_callback([helper::class, 'format_flags_in_course_report']); 113 | } 114 | } 115 | 116 | /** 117 | * Adds the filters we want to display in the report 118 | * 119 | * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their 120 | * unique identifier 121 | */ 122 | protected function add_filters(): void { 123 | $filters = [ 124 | 'user:fullname', 125 | 'rating:rating', 126 | 'rating:hasreview', 127 | 'rating:review', 128 | 'rating:flags', 129 | ]; 130 | 131 | $this->add_filters_from_entities($filters); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /db/install.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
76 |
77 |
78 | -------------------------------------------------------------------------------- /tests/reportbuilder/datasource/courseratings_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\reportbuilder\datasource; 18 | 19 | use core_reportbuilder\manager; 20 | use core_reportbuilder\local\helpers\aggregation; 21 | use core_reportbuilder\local\helpers\report; 22 | use core_reportbuilder\table\custom_report_table_view; 23 | use core_reportbuilder\tests\core_reportbuilder_testcase; 24 | use tool_courserating\api; 25 | 26 | /** 27 | * Tests for reportbuilder datasource courseratings. 28 | * 29 | * TODO when the minimum supported version is Moodle 4.0 - change this class to extend 30 | * {@see core_reportbuilder_testcase} and remove the component check and the methods copied from that class 31 | * 32 | * @package tool_courserating 33 | * @copyright 2022 Marina Glancy 34 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | * 36 | * @covers \tool_courserating\reportbuilder\datasource\courseratings 37 | */ 38 | final class courseratings_test extends core_reportbuilder_testcase { 39 | /** 40 | * setUp 41 | */ 42 | protected function setUp(): void { 43 | parent::setUp(); 44 | $this->resetAfterTest(); 45 | set_config( 46 | \tool_courserating\constants::SETTING_RATINGMODE, 47 | \tool_courserating\constants::RATEBY_ANYTIME, 48 | 'tool_courserating' 49 | ); 50 | } 51 | 52 | /** 53 | * Set up for test 54 | */ 55 | protected function set_up_for_test() { 56 | $course = $this->getDataGenerator()->create_course(); 57 | $this->getDataGenerator()->create_course(); 58 | $user1 = $this->getDataGenerator()->create_user(['firstname' => 'User1']); 59 | $user2 = $this->getDataGenerator()->create_user(['firstname' => 'User2']); 60 | /** @var \tool_courserating_generator $generator */ 61 | $generator = self::getDataGenerator()->get_plugin_generator('tool_courserating'); 62 | $rating1 = $generator->create_rating($user1->id, $course->id, 3, 'hello unclosed tag'); 63 | usleep(1000); // Make sure timestamp is different on the ratings. 64 | $rating2 = $generator->create_rating($user2->id, $course->id, 2); 65 | 66 | $this->setUser($user2); 67 | api::flag_review($rating1->get('id')); 68 | $this->setAdminUser(); 69 | } 70 | 71 | /** 72 | * Stress test datasource 73 | * 74 | * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php 75 | */ 76 | public function test_stress_datasource(): void { 77 | if (!\core_component::get_component_directory('core_reportbuilder')) { 78 | // TODO remove when the minimum supported version is Moodle 4.0. 79 | $this->markTestSkipped('Report builder not found'); 80 | } 81 | if (!PHPUNIT_LONGTEST) { 82 | $this->markTestSkipped('PHPUNIT_LONGTEST is not defined'); 83 | } 84 | 85 | $this->set_up_for_test(); 86 | 87 | $this->datasource_stress_test_columns(courseratings::class); 88 | $this->datasource_stress_test_columns_aggregation(courseratings::class); 89 | $this->datasource_stress_test_conditions(courseratings::class, 'course:coursefullnamewithlink'); 90 | } 91 | 92 | /** 93 | * Test for report content 94 | * 95 | * @return void 96 | */ 97 | public function test_content(): void { 98 | if (!\core_component::get_component_directory('core_reportbuilder')) { 99 | // TODO remove when the minimum supported version is Moodle 4.0. 100 | $this->markTestSkipped('Report builder not found'); 101 | } 102 | $this->set_up_for_test(); 103 | 104 | /** @var \core_reportbuilder_generator $generator */ 105 | $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); 106 | $report = $generator->create_report(['name' => 'CR', 'source' => courseratings::class]); 107 | 108 | // Add more columns. 109 | $c = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']); 110 | $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'rating:rating']); 111 | $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'rating:review']); 112 | $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'rating:flags']); 113 | // Add flags column twice to check how different DB engines handle it. 114 | $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'rating:flags']); 115 | report::toggle_report_column_sorting($report->get('id'), $c->get('id'), true); 116 | 117 | // Analyse results. 118 | $content = $this->get_custom_report_content($report->get('id')); 119 | $this->assertCount(3, $content); 120 | 121 | $this->assertEquals(['2.5', '2.5', ''], array_column($content, 'c2_avgrating')); 122 | $this->assertEquals(['User1', 'User2', null], array_column($content, 'c4_firstname')); 123 | $this->assertEquals( 124 | ['

hello unclosed tag
', '', null], 125 | array_column($content, 'c6_review') 126 | ); 127 | $this->assertEquals([1, 0, 0], array_column($content, 'c7_flags')); 128 | $this->assertEquals([1, 0, 0], array_column($content, 'c8_flags')); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /.github/workflows/moodle.yml: -------------------------------------------------------------------------------- 1 | name: Moodle Plugin CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-22.04 8 | 9 | services: 10 | postgres: 11 | image: postgres:15 12 | env: 13 | POSTGRES_USER: 'postgres' 14 | POSTGRES_HOST_AUTH_METHOD: 'trust' 15 | ports: 16 | - 5432:5432 17 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 18 | 19 | mariadb: 20 | image: mariadb:10 21 | env: 22 | MYSQL_USER: 'root' 23 | MYSQL_ALLOW_EMPTY_PASSWORD: "true" 24 | MYSQL_CHARACTER_SET_SERVER: "utf8mb4" 25 | MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci" 26 | ports: 27 | - 3306:3306 28 | options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | include: 34 | - php: '8.1' 35 | moodle-branch: 'MOODLE_401_STABLE' 36 | database: 'pgsql' 37 | - php: '7.4' 38 | moodle-branch: 'MOODLE_401_STABLE' 39 | database: 'mariadb' 40 | # Not tested on Moodle 4.2 - unittests fail because of a missing class core_reportbuilder\tests\core_reportbuilder_testcase 41 | # Functionality still works 42 | - php: '8.2' 43 | moodle-branch: 'MOODLE_403_STABLE' 44 | database: 'pgsql' 45 | - php: '8.0' 46 | moodle-branch: 'MOODLE_403_STABLE' 47 | database: 'mariadb' 48 | - php: '8.3' 49 | moodle-branch: 'MOODLE_404_STABLE' 50 | database: 'mariadb' 51 | - php: '8.1' 52 | moodle-branch: 'MOODLE_404_STABLE' 53 | database: 'pgsql' 54 | - php: '8.3' 55 | moodle-branch: 'MOODLE_405_STABLE' 56 | database: 'pgsql' 57 | - php: '8.1' 58 | moodle-branch: 'MOODLE_405_STABLE' 59 | database: 'mariadb' 60 | - php: '8.4' 61 | moodle-branch: 'MOODLE_500_STABLE' 62 | database: 'mariadb' 63 | - php: '8.2' 64 | moodle-branch: 'MOODLE_500_STABLE' 65 | database: 'pgsql' 66 | - php: '8.4' 67 | # Main job. Run all checks that do not require setup and only need to be run once. 68 | runchecks: 'all' 69 | moodle-branch: 'MOODLE_501_STABLE' 70 | database: 'pgsql' 71 | - php: '8.2' 72 | moodle-branch: 'MOODLE_501_STABLE' 73 | database: 'mariadb' 74 | 75 | steps: 76 | - name: Check out repository code 77 | uses: actions/checkout@v4 78 | with: 79 | path: plugin 80 | 81 | - name: Setup PHP ${{ matrix.php }} 82 | uses: shivammathur/setup-php@v2 83 | with: 84 | php-version: ${{ matrix.php }} 85 | extensions: ${{ matrix.extensions }} 86 | ini-values: max_input_vars=5000 87 | # If you are not using code coverage, keep "none". Otherwise, use "pcov" (Moodle 3.10 and up) or "xdebug". 88 | # If you try to use code coverage with "none", it will fallback to phpdbg (which has known problems). 89 | coverage: none 90 | 91 | - name: Initialise moodle-plugin-ci 92 | run: | 93 | civersion=$(if [[ "${{ matrix.php }}" =~ ^7.[23]$ ]]; then echo "^3"; fi) 94 | composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ${civersion:-^4} 95 | echo $(cd ci/bin; pwd) >> $GITHUB_PATH 96 | echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH 97 | sudo locale-gen en_AU.UTF-8 98 | echo "NVM_DIR=$HOME/.nvm" >> $GITHUB_ENV 99 | 100 | - name: Install moodle-plugin-ci 101 | run: moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 102 | env: 103 | DB: ${{ matrix.database }} 104 | MOODLE_BRANCH: ${{ matrix.moodle-branch }} 105 | # Uncomment this to run Behat tests using the Moodle App. 106 | # MOODLE_APP: 'true' 107 | 108 | - name: PHP Lint 109 | if: ${{ !cancelled() && matrix.runchecks == 'all' }} 110 | run: moodle-plugin-ci phplint 111 | 112 | - name: PHP Mess Detector 113 | continue-on-error: true # This step will show errors but will not fail 114 | if: ${{ !cancelled() && matrix.runchecks == 'all' }} 115 | run: moodle-plugin-ci phpmd 116 | 117 | - name: Moodle Code Checker 118 | if: ${{ !cancelled() && matrix.runchecks == 'all' }} 119 | run: moodle-plugin-ci phpcs --max-warnings 0 120 | 121 | - name: Moodle PHPDoc Checker 122 | if: ${{ !cancelled() && matrix.runchecks == 'all' }} 123 | run: moodle-plugin-ci phpdoc --max-warnings 0 124 | 125 | - name: Validating 126 | if: ${{ !cancelled() }} 127 | run: moodle-plugin-ci validate 128 | 129 | - name: Check upgrade savepoints 130 | if: ${{ !cancelled() && matrix.runchecks == 'all' }} 131 | run: moodle-plugin-ci savepoints 132 | 133 | - name: Mustache Lint 134 | if: ${{ !cancelled() && matrix.runchecks == 'all' }} 135 | run: moodle-plugin-ci mustache 136 | 137 | - name: Grunt 138 | if: ${{ !cancelled() && matrix.runchecks == 'all' }} 139 | run: moodle-plugin-ci grunt --max-lint-warnings 0 140 | 141 | - name: PHPUnit tests 142 | if: ${{ !cancelled() }} 143 | run: moodle-plugin-ci phpunit --fail-on-warning 144 | 145 | - name: Behat features 146 | id: behat 147 | if: ${{ !cancelled() }} 148 | run: moodle-plugin-ci behat --profile chrome --scss-deprecations 149 | 150 | - name: Upload Behat Faildump 151 | if: ${{ failure() && steps.behat.outcome == 'failure' }} 152 | uses: actions/upload-artifact@v4 153 | with: 154 | name: Behat Faildump (${{ join(matrix.*, ', ') }}) 155 | path: ${{ github.workspace }}/moodledata/behat_dump 156 | retention-days: 7 157 | if-no-files-found: ignore 158 | 159 | - name: Mark cancelled jobs as failed. 160 | if: ${{ cancelled() }} 161 | run: exit 1 162 | -------------------------------------------------------------------------------- /classes/form/addrating.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\form; 18 | 19 | use moodle_exception; 20 | use moodle_url; 21 | use tool_courserating\api; 22 | use tool_courserating\constants; 23 | use tool_courserating\helper; 24 | use tool_courserating\local\models\summary; 25 | use tool_courserating\permission; 26 | 27 | /** 28 | * Form to add or change a rating 29 | * 30 | * @package tool_courserating 31 | * @copyright 2022 Marina Glancy 32 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class addrating extends \core_form\dynamic_form { 35 | /** 36 | * Current course id 37 | * 38 | * @return int 39 | */ 40 | protected function get_course_id(): int { 41 | $courseid = $this->optional_param('courseid', 0, PARAM_INT); 42 | if (!$courseid) { 43 | throw new moodle_exception('missingparam', '', '', 'courseid'); 44 | } 45 | return $courseid; 46 | } 47 | 48 | /** 49 | * Form definition 50 | */ 51 | protected function definition() { 52 | global $OUTPUT; 53 | $mform = $this->_form; 54 | $mform->addElement('hidden', 'courseid', $this->get_course_id()); 55 | $mform->setType('courseid', PARAM_INT); 56 | $allowreviews = helper::get_course_allow_reviews($this->get_course_id()); 57 | 58 | $summary = summary::get_for_course($this->get_course_id()); 59 | if ($summary->get('cntall')) { 60 | $courseid = $this->get_course_id(); 61 | $str = permission::can_view_reviews($courseid) ? 62 | get_string('viewallreviews', 'tool_courserating') : 63 | get_string('viewallratings', 'tool_courserating'); 64 | $mform->addElement('html', <<$str

66 | EOF 67 | ); 68 | } 69 | 70 | $radioarray = []; 71 | foreach ([1, 2, 3, 4, 5] as $r) { 72 | $label = $OUTPUT->pix_icon('star', $r, 'tool_courserating', ['class' => 'star-on tool_courserating-stars']); 73 | $label .= $OUTPUT->pix_icon('star-o', $r, 'tool_courserating', ['class' => 'star-off tool_courserating-stars']); 74 | $label = \html_writer::span($label); 75 | /** @var \MoodleQuickForm_radio $el */ 76 | $el = $mform->createElement('radio', 'rating', '', $label, $r); 77 | $el->setAttributes($el->getAttributes() + ['class' => ' stars-' . $r]); 78 | $radioarray[] = $el; 79 | } 80 | $el = $mform->addGroup($radioarray, 'ratinggroup', get_string('rating', 'tool_courserating'), [' ', ' '], false); 81 | $el->setAttributes($el->getAttributes() + ['class' => 'tool_courserating-form-stars-group']); 82 | 83 | if ($allowreviews != constants::ALLOWREVIEWS_NO) { 84 | if (helper::get_setting(constants::SETTING_USEHTML)) { 85 | $options = helper::review_editor_options($this->get_context_for_dynamic_submission()); 86 | $mform->addElement( 87 | 'editor', 88 | 'review_editor', 89 | get_string('review', 'tool_courserating'), 90 | ['rows' => 4], 91 | $options 92 | ); 93 | } else { 94 | $mform->addElement( 95 | 'textarea', 96 | 'review', 97 | get_string('review', 'tool_courserating'), 98 | ['rows' => 4] 99 | ); 100 | $mform->setType('review', PARAM_TEXT); 101 | } 102 | } 103 | } 104 | 105 | /** 106 | * Form validation 107 | * 108 | * @param array $data 109 | * @param array $files 110 | * @return array 111 | */ 112 | public function validation($data, $files) { 113 | $errors = []; 114 | if (empty($data['rating'])) { 115 | $errors['ratinggroup'] = get_string('required'); 116 | } 117 | return $errors; 118 | } 119 | 120 | /** 121 | * Display the form 122 | * 123 | * @return void 124 | */ 125 | public function display() { 126 | parent::display(); 127 | global $PAGE; 128 | $PAGE->requires->js_call_amd( 129 | 'tool_courserating/rating', 130 | 'setupAddRatingForm', 131 | [$this->_form->getElement('ratinggroup')->getAttribute('id')] 132 | ); 133 | } 134 | 135 | /** 136 | * Current context 137 | * 138 | * @return \context 139 | */ 140 | protected function get_context_for_dynamic_submission(): \context { 141 | return \context_course::instance($this->get_course_id()); 142 | } 143 | 144 | /** 145 | * Check access and throw exception if not allowed 146 | * 147 | * @return void 148 | * @throws moodle_exception 149 | */ 150 | protected function check_access_for_dynamic_submission(): void { 151 | permission::require_can_add_rating($this->get_course_id()); 152 | } 153 | 154 | /** 155 | * Process form submission 156 | */ 157 | public function process_dynamic_submission() { 158 | $data = $this->get_data(); 159 | api::set_rating($this->get_course_id(), $data); 160 | } 161 | 162 | /** 163 | * Load in existing data as form defaults 164 | */ 165 | public function set_data_for_dynamic_submission(): void { 166 | $this->set_data(api::prepare_rating_for_form($this->get_course_id())); 167 | } 168 | 169 | /** 170 | * Fake URL for atto auto-save 171 | * 172 | * @return moodle_url 173 | */ 174 | protected function get_page_url_for_dynamic_submission(): moodle_url { 175 | return new moodle_url('/course/view.php', ['id' => $this->get_course_id(), 'addrating' => 1]); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /classes/reportbuilder/datasource/courseratings.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\reportbuilder\datasource; 18 | 19 | use core_reportbuilder\datasource; 20 | use core_reportbuilder\local\entities\course; 21 | use core_reportbuilder\local\filters\select; 22 | use core_reportbuilder\local\helpers\database; 23 | use core_reportbuilder\local\helpers\report; 24 | use core_reportbuilder\manager; 25 | use tool_courserating\reportbuilder\local\entities\summary; 26 | use tool_courserating\reportbuilder\local\entities\rating; 27 | use core_reportbuilder\local\entities\user; 28 | 29 | /** 30 | * Reportbuilder datasource courseratings. 31 | * 32 | * @package tool_courserating 33 | * @copyright 2022 Marina Glancy 34 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class courseratings extends datasource { 37 | /** 38 | * Return user friendly name of the datasource 39 | * 40 | * @return string 41 | */ 42 | public static function get_name(): string { 43 | return get_string('datasource_courseratings', 'tool_courserating'); 44 | } 45 | 46 | /** 47 | * Initialise report 48 | */ 49 | protected function initialise(): void { 50 | global $CFG; 51 | $courseentity = new course(); 52 | $coursetablealias = $courseentity->get_table_alias('course'); 53 | $this->set_main_table('course', $coursetablealias); 54 | $this->add_entity($courseentity); 55 | $paramsiteid = database::generate_param_name(); 56 | $this->add_base_condition_sql("{$coursetablealias}.id != :{$paramsiteid}", [$paramsiteid => SITEID]); 57 | 58 | // Join the coursecategory entity. 59 | if ($CFG->version > 2022110000) { 60 | // Course category entity was renamed in 4.1. 61 | $coursecategoryentity = new \core_course\reportbuilder\local\entities\course_category(); 62 | } else { 63 | $coursecategoryentity = new \core_course\local\entities\course_category(); 64 | } 65 | $coursecategorytablealias = $coursecategoryentity->get_table_alias('course_categories'); 66 | $coursecategoryjoin = "LEFT JOIN {course_categories} {$coursecategorytablealias} 67 | ON {$coursecategorytablealias}.id = {$coursetablealias}.category"; 68 | $this->add_entity($coursecategoryentity->add_join($coursecategoryjoin)); 69 | 70 | // Join the summary entity. 71 | $summaryentity = new summary(); 72 | $summarytablealias = $summaryentity->get_table_alias('tool_courserating_summary'); 73 | $summaryjoin = "LEFT JOIN {tool_courserating_summary} {$summarytablealias} 74 | ON {$summarytablealias}.courseid = {$coursetablealias}.id"; 75 | $this->add_entity($summaryentity->add_join($summaryjoin)); 76 | 77 | // Join the rating entity. 78 | $ratingentity = new rating(); 79 | $ratingtablealias = $ratingentity->get_table_alias('tool_courserating_rating'); 80 | $ratingjoin = "LEFT JOIN {tool_courserating_rating} {$ratingtablealias} 81 | ON {$ratingtablealias}.courseid = {$coursetablealias}.id"; 82 | $this->add_entity($ratingentity->add_join($ratingjoin)); 83 | 84 | // Join the user entity. 85 | $userentity = new user(); 86 | $usertablealias = $userentity->get_table_alias('user'); 87 | $userjoin = "LEFT JOIN {user} {$usertablealias} 88 | ON {$usertablealias}.id = {$ratingtablealias}.userid"; 89 | $this->add_entity($userentity->add_join($ratingjoin)->add_join($userjoin)); 90 | 91 | // Add all columns from entities to be available in custom reports. 92 | $this->add_columns_from_entity($courseentity->get_entity_name()); 93 | $this->add_columns_from_entity($coursecategoryentity->get_entity_name()); 94 | $this->add_columns_from_entity($summaryentity->get_entity_name()); 95 | $this->add_columns_from_entity($ratingentity->get_entity_name()); 96 | $this->add_columns_from_entity($userentity->get_entity_name()); 97 | 98 | // Add all filters from entities to be available in custom reports. 99 | $this->add_filters_from_entity($courseentity->get_entity_name()); 100 | $this->add_filters_from_entity($coursecategoryentity->get_entity_name()); 101 | $this->add_filters_from_entity($summaryentity->get_entity_name()); 102 | $this->add_filters_from_entity($ratingentity->get_entity_name()); 103 | $this->add_filters_from_entity($userentity->get_entity_name()); 104 | 105 | // Add all conditions from entities to be available in custom reports. 106 | $this->add_conditions_from_entity($courseentity->get_entity_name()); 107 | $this->add_conditions_from_entity($coursecategoryentity->get_entity_name()); 108 | $this->add_conditions_from_entity($summaryentity->get_entity_name()); 109 | $this->add_conditions_from_entity($ratingentity->get_entity_name()); 110 | $this->add_conditions_from_entity($userentity->get_entity_name()); 111 | } 112 | 113 | /** 114 | * Return the columns that will be added to the report as part of default setup 115 | * 116 | * @return string[] 117 | */ 118 | public function get_default_columns(): array { 119 | return [ 120 | 'course:coursefullnamewithlink', 121 | 'course_category:name', 122 | 'summary:avgrating', 123 | 'summary:stars', 124 | ]; 125 | } 126 | 127 | /** 128 | * Return the filters that will be added to the report once is created 129 | * 130 | * @return string[] 131 | */ 132 | public function get_default_filters(): array { 133 | return [ 134 | 'course:courseselector', 135 | ]; 136 | } 137 | 138 | /** 139 | * Return the conditions that will be added to the report once is created 140 | * 141 | * @return string[] 142 | */ 143 | public function get_default_conditions(): array { 144 | return [ 145 | 'summary:ratingmode', 146 | ]; 147 | } 148 | 149 | /** 150 | * Set default columns and the sortorder 151 | */ 152 | public function add_default_columns(): void { 153 | parent::add_default_columns(); 154 | 155 | $persistent = $this->get_report_persistent(); 156 | $report = manager::get_report_from_persistent($persistent); 157 | foreach ($report->get_active_columns() as $column) { 158 | if ($column->get_unique_identifier() === 'course:coursefullnamewithlink') { 159 | report::toggle_report_column_sorting($persistent->get('id'), $column->get_persistent()->get('id'), true); 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * Add default conditions and their values 166 | */ 167 | public function add_default_conditions(): void { 168 | parent::add_default_conditions(); 169 | $this->set_condition_values([ 170 | 'summary:ratingmode_operator' => select::NOT_EQUAL_TO, 171 | 'summary:ratingmode_value' => 0, 172 | ]); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /tests/behat/teacher.feature: -------------------------------------------------------------------------------- 1 | @tool @tool_courserating @javascript 2 | Feature: Viewing and managing course ratings as a teacher and manager 3 | 4 | Background: 5 | Given the following "courses" exist: 6 | | fullname | shortname | numsections | 7 | | Course 1 | C1 | 1 | 8 | | Course 2 | C2 | 1 | 9 | | Course 3 | C3 | 1 | 10 | Given the following "users" exist: 11 | | username | firstname | lastname | email | 12 | | teacher1 | Teacher | 1 | teacher1@example.com | 13 | | student1 | Student | 1 | student1@example.com | 14 | | student2 | Student | 2 | student2@example.com | 15 | | student3 | Student | 3 | student3@example.com | 16 | | manager1 | Manager | 1 | manager1@example.com | 17 | And the following "course enrolments" exist: 18 | | user | course | role | 19 | | student1 | C1 | student | 20 | | student2 | C1 | student | 21 | | student3 | C1 | student | 22 | | teacher1 | C1 | editingteacher | 23 | And the following "tool_courserating > ratings" exist: 24 | | user | course | rating | review | 25 | | student1 | C1 | 3 | abcdef | 26 | | student2 | C1 | 4 | hello | 27 | | student3 | C1 | 1 | | 28 | And the following "role assigns" exist: 29 | | user | role | contextlevel | reference | 30 | | manager1 | manager | System | | 31 | 32 | Scenario Outline: Removing reviews as manager through reviews popup 33 | Given the following config values are set as admin: 34 | | usehtml | | tool_courserating | 35 | When I log in as "manager1" 36 | And I am on "Course 1" course homepage 37 | And I click on "2.7" "text" in the "#page-header .tool_courserating-ratings" "css_element" 38 | And I should see "abcdef" in the "Student 1" "tool_courserating > Review" 39 | And I click on "Flag" "link" in the "Student 1" "tool_courserating > Review" 40 | And I should see "You have flagged this review as inappropriate/offensive." in the "Student 1" "tool_courserating > Review" 41 | And I should see "1 user(s) have flagged this review as inappropriate/offensive." 42 | And I should see "Permanently delete" 43 | And I click on "Close" "button" in the ".modal-dialog" "css_element" 44 | And I click on "2.7" "text" in the "#page-header .tool_courserating-ratings" "css_element" 45 | And I click on "Permanently delete" "link" in the "Student 1" "tool_courserating > Review" 46 | And I set the field "Reason for deletion" to "go away" 47 | And I press "Save changes" 48 | And I should see "hello" in the "Student 2" "tool_courserating > Review" 49 | And I should not see "Student 1" 50 | And I should not see "2.7" 51 | And I should see "2.5" in the "[data-purpose='average-rating']" "css_element" 52 | And I click on "Close" "button" in the ".modal-dialog" "css_element" 53 | And I should not see "2.7" 54 | And I should see "2.5" in the "#page-header" "css_element" 55 | 56 | Examples: 57 | | usehtml | 58 | | 0 | 59 | | 1 | 60 | 61 | Scenario Outline: Removing reviews as manager through the course report 62 | Given the following config values are set as admin: 63 | | usehtml | | tool_courserating | 64 | When I log in as "manager1" 65 | And I am on "Course 1" course homepage 66 | And I navigate to "Course ratings" in current page administration 67 | And I click on "Permanently delete" "link" in the "Student 3" "table_row" 68 | And I set the field "Reason for deletion" to "go away" 69 | And I press "Save changes" 70 | And I should see "Rating deleted" in the "Student 3" "table_row" 71 | And I am on "Course 1" course homepage 72 | And I navigate to "Course ratings" in current page administration 73 | And I should see "Student 1" 74 | And I should not see "Student 3" 75 | 76 | Examples: 77 | | usehtml | 78 | | 0 | 79 | | 1 | 80 | 81 | Scenario Outline: Viewing reviews as teacher through the course report 82 | Given the following config values are set as admin: 83 | | usehtml | | tool_courserating | 84 | When I log in as "teacher1" 85 | And I am on "Course 1" course homepage 86 | And I navigate to "Course ratings" in current page administration 87 | And I should see "abcdef" in the "Student 1" "table_row" 88 | And I should see "hello" in the "Student 2" "table_row" 89 | And I should not see "Permanently delete" 90 | 91 | Examples: 92 | | usehtml | 93 | | 0 | 94 | | 1 | 95 | 96 | Scenario Outline: Creating course rating report in report builder 97 | Given the following config values are set as admin: 98 | | usehtml | | tool_courserating | 99 | Given reportbuilder is available for tool_courserating 100 | Given I log in as "admin" 101 | And I change window size to "large" 102 | When I navigate to "Reports > Report builder > Custom reports" in site administration 103 | And I click on "New report" "button" 104 | And I set the following fields in the "New report" "dialogue" to these values: 105 | | Name | My report | 106 | | Report source | Course ratings | 107 | | Include default setup | 1 | 108 | And I click on "Save" "button" in the "New report" "dialogue" 109 | And I click on "Switch to preview mode" "button" 110 | Then I should see "My report" 111 | And the following should exist in the "reportbuilder-table" table: 112 | | Course full name with link | Course rating | 113 | | Course 1 | 2.7 | 114 | 115 | Examples: 116 | | usehtml | 117 | | 0 | 118 | | 1 | 119 | 120 | Scenario Outline: Deleting ratings from the custom report in report builder 121 | Given the following config values are set as admin: 122 | | usehtml | | tool_courserating | 123 | Given reportbuilder is available for tool_courserating 124 | Given I log in as "admin" 125 | When I navigate to "Reports > Report builder > Custom reports" in site administration 126 | And I click on "New report" "button" 127 | And I set the following fields in the "New report" "dialogue" to these values: 128 | | Name | My report | 129 | | Report source | Course ratings | 130 | | Include default setup | 0 | 131 | And I click on "Save" "button" in the "New report" "dialogue" 132 | And I click on "Add column 'Course full name with link'" "link" 133 | And I click on "Add column 'Full name with link'" "link" 134 | And I click on "Add column 'Review'" "link" 135 | And I click on "Add column 'Actions'" "link" 136 | And I click on "Switch to preview mode" "button" 137 | And I click on "Permanently delete" "link" in the "Student 3" "table_row" 138 | And I set the field "Reason for deletion" to "go away" 139 | And I press "Save changes" 140 | And I should see "Rating deleted" in the "Student 3" "table_row" 141 | And I press "Switch to edit mode" 142 | And I click on "Switch to preview mode" "button" 143 | And I should see "Student 1" 144 | And I should not see "Student 3" 145 | 146 | Examples: 147 | | usehtml | 148 | | 0 | 149 | | 1 | 150 | 151 | Scenario: Viewing reviews in the course popup when they are hidden from students 152 | Given the following config values are set as admin: 153 | | allowreviews | 2 | tool_courserating | 154 | When I log in as "manager1" 155 | And I am on "Course 1" course homepage 156 | And I click on "2.7" "text" in the "#page-header .tool_courserating-ratings" "css_element" 157 | And I should see "abcdef" in the "Student 1" "tool_courserating > Review" 158 | And I should see "Students can see only the average rating but not the text reviews." 159 | -------------------------------------------------------------------------------- /tests/privacy/provider_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating\privacy; 18 | 19 | use core_privacy\local\request\writer; 20 | use core_privacy\local\request\approved_contextlist; 21 | use tool_courserating\api; 22 | use core_privacy\local\request\approved_userlist; 23 | use tool_courserating\local\models\flag; 24 | use tool_courserating\local\models\rating; 25 | 26 | /** 27 | * Tests for privacy provider class 28 | * 29 | * @package tool_courserating 30 | * @covers \tool_courserating\privacy\provider 31 | * @copyright 2022 Marina Glancy 32 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | final class provider_test extends \core_privacy\tests\provider_testcase { 35 | /** 36 | * Overriding setUp() function to always reset after tests. 37 | */ 38 | public function setUp(): void { 39 | parent::setUp(); 40 | $this->resetAfterTest(true); 41 | set_config( 42 | \tool_courserating\constants::SETTING_RATINGMODE, 43 | \tool_courserating\constants::RATEBY_ANYTIME, 44 | 'tool_courserating' 45 | ); 46 | } 47 | 48 | /** 49 | * Test for provider::get_contexts_for_userid(). 50 | */ 51 | public function test_get_contexts_for_userid(): void { 52 | global $DB; 53 | 54 | $user = $this->getDataGenerator()->create_user(); 55 | $course = $this->getDataGenerator()->create_course(); 56 | $this->setUser($user); 57 | \tool_courserating\api::set_rating($course->id, (object)['rating' => 5]); 58 | 59 | $this->setAdminUser(); 60 | 61 | $contextlist = provider::get_contexts_for_userid($user->id); 62 | $contexts = $contextlist->get_contexts(); 63 | $this->assertCount(1, $contexts); 64 | 65 | $courseids = array_column($contexts, 'instanceid'); 66 | $this->assertEqualsCanonicalizing([$course->id], $courseids); 67 | } 68 | 69 | /** 70 | * Test for provider::export_user_data(). 71 | */ 72 | public function test_export_user_data(): void { 73 | 74 | [$user, $course] = $this->setup_test_scenario_data(); 75 | $coursectx = \context_course::instance($course->id); 76 | $this->setAdminUser(); 77 | 78 | // Test the User's retrieved contextlist contains two contexts. 79 | $contextlist = provider::get_contexts_for_userid($user->id); 80 | $contexts = $contextlist->get_contexts(); 81 | $this->assertCount(1, $contexts); 82 | 83 | // Add a system, course category and course context to the approved context list. 84 | $systemctx = \context_system::instance(); 85 | $approvedcontextids = [ 86 | $systemctx->id, 87 | $coursectx->id, 88 | ]; 89 | 90 | // Retrieve the User's tool_cohortroles data. 91 | $approvedcontextlist = new approved_contextlist($user, 'tool_courserating', $approvedcontextids); 92 | provider::export_user_data($approvedcontextlist); 93 | 94 | // Test the tool_cohortroles data is exported at the system context level. 95 | $writer = writer::with_context($systemctx); 96 | $this->assertFalse($writer->has_any_data()); 97 | // Test the tool_cohortroles data is not exported at the course context level. 98 | $writer = writer::with_context($coursectx); 99 | $this->assertTrue($writer->has_any_data()); 100 | $this->assertNotEmpty($writer->get_data(['Course ratings', $course->shortname])); 101 | } 102 | 103 | /** 104 | * Set up scenario data 105 | * 106 | * @return array 107 | */ 108 | protected function setup_test_scenario_data() { 109 | $user1 = $this->getDataGenerator()->create_user(); 110 | $user2 = $this->getDataGenerator()->create_user(); 111 | $course = $this->getDataGenerator()->create_course(['shortname' => 'c1']); 112 | $this->setUser($user2); 113 | $r = \tool_courserating\api::set_rating($course->id, (object)['rating' => 5]); 114 | $this->setUser($user1); 115 | \tool_courserating\api::set_rating($course->id, (object)['rating' => 4]); 116 | api::flag_review($r->get('id')); 117 | return [$user1, $course]; 118 | } 119 | 120 | /** 121 | * Test for provider::delete_data_for_all_users_in_context(). 122 | */ 123 | public function test_delete_data_for_all_users_in_context(): void { 124 | global $DB; 125 | 126 | [$user, $course] = $this->setup_test_scenario_data(); 127 | $coursectx = \context_course::instance($course->id); 128 | $this->setAdminUser(); 129 | 130 | provider::delete_data_for_all_users_in_context($coursectx); 131 | $this->assertEmpty($DB->get_records(rating::TABLE)); 132 | $this->assertEmpty($DB->get_records(flag::TABLE)); 133 | } 134 | 135 | /** 136 | * Test for provider::delete_data_for_user(). 137 | */ 138 | public function test_delete_data_for_user(): void { 139 | global $DB; 140 | 141 | [$user, $course] = $this->setup_test_scenario_data(); 142 | $coursectx = \context_course::instance($course->id); 143 | $this->setAdminUser(); 144 | 145 | // Test the User's retrieved contextlist contains two contexts. 146 | $contextlist = provider::get_contexts_for_userid($user->id); 147 | $contexts = $contextlist->get_contexts(); 148 | $this->assertCount(1, $contexts); 149 | 150 | $approvedcontextlist = new approved_contextlist($user, 'tool_courserating', [$coursectx->id]); 151 | provider::delete_data_for_user($approvedcontextlist); 152 | } 153 | 154 | /** 155 | * Test that only users within a course context are fetched. 156 | */ 157 | public function test_get_users_in_context(): void { 158 | $component = 'tool_courserating'; 159 | 160 | [$user, $course] = $this->setup_test_scenario_data(); 161 | $coursectx = \context_course::instance($course->id); 162 | $this->setAdminUser(); 163 | 164 | $userlist = new \core_privacy\local\request\userlist($coursectx, $component); 165 | provider::get_users_in_context($userlist); 166 | $this->assertCount(2, $userlist); 167 | $this->assertTrue(in_array($user->id, $userlist->get_userids())); 168 | } 169 | 170 | /** 171 | * Test that data for users in approved userlist is deleted. 172 | */ 173 | public function test_delete_data_for_users(): void { 174 | $component = 'tool_courserating'; 175 | 176 | [$user, $course] = $this->setup_test_scenario_data(); 177 | $coursectx = \context_course::instance($course->id); 178 | $this->setAdminUser(); 179 | 180 | $userlist1 = new \core_privacy\local\request\userlist($coursectx, $component); 181 | provider::get_users_in_context($userlist1); 182 | $this->assertCount(2, $userlist1); 183 | $this->assertTrue(in_array($user->id, $userlist1->get_userids())); 184 | $userids = $userlist1->get_userids(); 185 | 186 | $approvedlist1 = new approved_userlist($coursectx, $component, $userids); 187 | provider::delete_data_for_users($approvedlist1); 188 | 189 | $userlist1 = new \core_privacy\local\request\userlist($coursectx, $component); 190 | provider::get_users_in_context($userlist1); 191 | $this->assertCount(0, $userlist1); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /lang/en/tool_courserating.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Plugin strings are defined here. 19 | * 20 | * @package tool_courserating 21 | * @category string 22 | * @copyright 2022 Marina Glancy 23 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | $string['addrating'] = 'Leave a rating'; 29 | $string['allowreviews'] = 'Accompanying rating with a review'; 30 | $string['allowreviewsconfig'] = "- No text reviews allowed - users can only submit star ratings. 31 | - Students can leave reviews but cannot read others - only users with the tool/courserating:reports capability can see the reviews. 32 | - Students can leave and read reviews - all users can see published reviews."; 33 | $string['allowreviewshidden'] = 'Students can leave reviews but cannot read others'; 34 | $string['allowreviewsno'] = 'No text reviews allowed'; 35 | $string['allowreviewsvisible'] = 'Students can leave and read reviews'; 36 | $string['barwithrating'] = 'View reviews with {$a->rating}-star ratings ({$a->percent} of all ratings)'; 37 | $string['cannotrate'] = 'You don\'t have permission to leave rating to this course'; 38 | $string['cannotview'] = 'You don\'t have permission to view ratings for this course'; 39 | $string['cfielddescription'] = 'Do not edit, the content will be populated automatically every time somebody leaves a rating for this course.'; 40 | $string['colorrating'] = 'Colour of the rating'; 41 | $string['colorratingconfig'] = 'This is usually slightly darker than the star colour for the best visual effect'; 42 | $string['colorstar'] = 'Colour of the stars'; 43 | $string['courserating:delete'] = 'Delete course ratings and reviews, view flagged reviews'; 44 | $string['courserating:rate'] = 'Leave a rating for the course'; 45 | $string['courserating:reports'] = 'View course ratings reports'; 46 | $string['coursereviews'] = 'Course reviews'; 47 | $string['datasource_courseratings'] = "Course ratings"; 48 | $string['deleterating'] = 'Permanently delete'; 49 | $string['deletereason'] = 'Reason for deletion'; 50 | $string['displayempty'] = 'Display no rating with gray stars'; 51 | $string['displayemptyconfig'] = 'For courses where rating is enabled but there are no ratings yet display gray stars. If not selected, such courses will have no rating displayed at all'; 52 | $string['editrating'] = 'Edit your rating'; 53 | $string['entity_rating'] = "Course rating by user"; 54 | $string['entity_summary'] = "Course rating summary"; 55 | $string['event:flag_created'] = 'Course rating flagged'; 56 | $string['event:flag_deleted'] = 'Course rating flag revoked'; 57 | $string['event:rating_created'] = 'Course rating created'; 58 | $string['event:rating_deleted'] = 'Course rating deleted'; 59 | $string['event:rating_updated'] = 'Course rating updated'; 60 | $string['flagrating'] = 'Flag'; 61 | $string['parentcss'] = 'CSS selector for parent element'; 62 | $string['parentcssconfig'] = 'Course rating will be displayed on the course page as the last child of the DOM element that matches this selector. You may need to override it if the site uses a custom theme and you want to specify a custom parent. If left empty, the default value "#page-header" will be used.'; 63 | $string['percourseoverride'] = 'Course overrides'; 64 | $string['percourseoverrideconfig'] = 'If enabled, a custom course field will be created that will allow to set when each individual course can be rated. The value of the setting "When can courses be rated" will be treated as the default'; 65 | $string['pluginname'] = 'Course ratings'; 66 | $string['privacy:metadata:tool_courserating:reason'] = 'Reason'; 67 | $string['privacy:metadata:tool_courserating:reasoncode'] = 'Reason code'; 68 | $string['privacy:metadata:tool_courserating:timecreated'] = 'Time created'; 69 | $string['privacy:metadata:tool_courserating:timemodified'] = 'Time modified'; 70 | $string['privacy:metadata:tool_courserating_flag'] = 'Flagged ratings'; 71 | $string['privacy:metadata:tool_courserating_flag:id'] = 'Id'; 72 | $string['privacy:metadata:tool_courserating_flag:ratingid'] = 'Rating id'; 73 | $string['privacy:metadata:tool_courserating_flag:userid'] = 'User id'; 74 | $string['privacy:metadata:tool_courserating_rating'] = 'Course ratings'; 75 | $string['privacy:metadata:tool_courserating_rating:cohortid'] = 'Course id'; 76 | $string['privacy:metadata:tool_courserating_rating:hasreview'] = 'Has review'; 77 | $string['privacy:metadata:tool_courserating_rating:id'] = 'Id'; 78 | $string['privacy:metadata:tool_courserating_rating:rating'] = 'Rating'; 79 | $string['privacy:metadata:tool_courserating_rating:review'] = 'Review'; 80 | $string['privacy:metadata:tool_courserating_rating:timecreated'] = 'Time created'; 81 | $string['privacy:metadata:tool_courserating_rating:timemodified'] = 'Time modified'; 82 | $string['privacy:metadata:tool_courserating_rating:userid'] = 'User'; 83 | $string['ratebyanybody'] = 'Students can rate the course at any time'; 84 | $string['ratebycompleted'] = 'Students can rate only after completing the course'; 85 | $string['ratebydefault'] = 'Default value is: "{$a}"'; 86 | $string['ratebynoone'] = 'Course ratings are disabled'; 87 | $string['ratedcategory'] = 'Category where course ratings are allowed'; 88 | $string['rating'] = 'Rating'; 89 | $string['rating_actions'] = "Actions"; 90 | $string['rating_hasreview'] = "Has review"; 91 | $string['rating_nofflags'] = "Number of flags"; 92 | $string['rating_rating'] = "Course rating"; 93 | $string['rating_review'] = "Review"; 94 | $string['rating_timecreated'] = "Time created"; 95 | $string['rating_timemodified'] = "Time modified"; 96 | $string['ratingasstars'] = 'Course rating as stars'; 97 | $string['ratingdeleted'] = 'Rating deleted'; 98 | $string['ratinglabel'] = 'Course rating'; 99 | $string['ratingmode'] = 'When can courses be rated'; 100 | $string['ratingmodeconfig'] = 'Additionally the capability to rate courses is checked'; 101 | $string['reindextask'] = 'Re-index course ratings'; 102 | $string['review'] = 'Review (optional)'; 103 | $string['reviewsarehidden'] = 'Students can see only the average rating but not the text reviews.'; 104 | $string['revokeratingflag'] = 'Revoke'; 105 | $string['settingsdescription'] = 'Changing some of the settings may require re-indexing of all courses and course ratings. This will happen automatically on next cron run.'; 106 | $string['showallratings'] = 'Show all'; 107 | $string['showallreviewsforrating'] = 'Showing reviews with {$a}-star ratings.'; 108 | $string['showmorereviews'] = 'Show more'; 109 | $string['summary_avgrating'] = "Course rating"; 110 | $string['summary_cnt01'] = "Ratio of 1-star ratings"; 111 | $string['summary_cnt02'] = "Ratio of 2-star ratings"; 112 | $string['summary_cnt03'] = "Ratio of 3-star ratings"; 113 | $string['summary_cnt04'] = "Ratio of 4-star ratings"; 114 | $string['summary_cnt05'] = "Ratio of 5-star ratings"; 115 | $string['summary_cntall'] = "Number of ratings"; 116 | $string['summary_cntreviews'] = "Number of reviews"; 117 | $string['summary_ratingmode'] = "Course rating mode"; 118 | $string['summary_sumrating'] = "Total of all ratings"; 119 | $string['usehtml'] = 'Use rich text editor for reviews'; 120 | $string['usehtmlconfig'] = 'Allow students to use rich text editor for the reviews, include links and attach files.'; 121 | $string['usersflagged'] = '{$a} user(s) have flagged this review as inappropriate/offensive.'; 122 | $string['viewallratings'] = 'View all ratings'; 123 | $string['viewallreviews'] = 'View all reviews'; 124 | $string['youflagged'] = 'You have flagged this review as inappropriate/offensive.'; 125 | -------------------------------------------------------------------------------- /classes/permission.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace tool_courserating; 18 | 19 | use required_capability_exception; 20 | use tool_courserating\local\models\rating; 21 | 22 | /** 23 | * Permission checks 24 | * 25 | * @package tool_courserating 26 | * @copyright 2022 Marina Glancy 27 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 | */ 29 | class permission { 30 | /** 31 | * User can view rating for the course (ratings are enabled for this course) 32 | * 33 | * @param int $courseid 34 | * @return bool 35 | */ 36 | public static function can_view_ratings(int $courseid): bool { 37 | global $USER; 38 | if (helper::get_course_rating_mode($courseid) == constants::RATEBY_NOONE) { 39 | return false; 40 | } 41 | $course = get_course($courseid); 42 | $context = \context_course::instance($courseid); 43 | return \core_course_category::can_view_course_info($course) || 44 | is_enrolled($context, $USER, '', true); 45 | } 46 | 47 | /** 48 | * User can view reviews for the course 49 | * 50 | * @param int $courseid 51 | * @return bool 52 | */ 53 | public static function can_view_reviews(int $courseid): bool { 54 | global $USER; 55 | if (!self::can_view_ratings($courseid)) { 56 | return false; 57 | } 58 | $allowreviews = helper::get_course_allow_reviews($courseid); 59 | if ($allowreviews == constants::ALLOWREVIEWS_NO) { 60 | return false; 61 | } 62 | return $allowreviews == constants::ALLOWREVIEWS_VISIBLE || 63 | has_capability('tool/courserating:reports', \context_course::instance($courseid)); 64 | } 65 | 66 | /** 67 | * Can the current user add a rating for the specified course 68 | * 69 | * Example of checking last access: 70 | * $lastaccess = $DB->get_field('user_lastaccess', 'timeaccess', ['userid' => $USER->id, 'courseid' => $courseid]); 71 | * 72 | * @param int $courseid 73 | * @return bool 74 | * @throws \coding_exception 75 | */ 76 | public static function can_add_rating(int $courseid): bool { 77 | global $CFG, $USER; 78 | if (!has_capability('tool/courserating:rate', \context_course::instance($courseid))) { 79 | return false; 80 | } 81 | $courseratingmode = helper::get_course_rating_mode($courseid); 82 | if ($courseratingmode == constants::RATEBY_NOONE) { 83 | return false; 84 | } 85 | if ($courseratingmode == constants::RATEBY_COMPLETED) { 86 | require_once($CFG->dirroot . '/completion/completion_completion.php'); 87 | // The course is supposed to be marked as completed at $timeend. 88 | $ccompletion = new \completion_completion(['userid' => $USER->id, 'course' => $courseid]); 89 | return $ccompletion->is_complete(); 90 | } 91 | return true; 92 | } 93 | 94 | /** 95 | * Does current user have capability to delete ratings 96 | * 97 | * @param int $ratingid 98 | * @param int|null $courseid 99 | * @return bool 100 | */ 101 | public static function can_delete_rating(int $ratingid, ?int $courseid = null): bool { 102 | if (!$courseid) { 103 | $courseid = (new rating($ratingid))->get('courseid'); 104 | } 105 | return has_capability('tool/courserating:delete', \context_course::instance($courseid)); 106 | } 107 | 108 | /** 109 | * Can current user flag the rating 110 | * 111 | * @param int $ratingid 112 | * @param int|null $courseid course id if known (saves a DB query) 113 | * @return bool 114 | */ 115 | public static function can_flag_rating(int $ratingid, ?int $courseid = null): bool { 116 | if (!isloggedin() || isguestuser()) { 117 | return false; 118 | } 119 | if (!$courseid) { 120 | $courseid = (new rating($ratingid))->get('courseid'); 121 | } 122 | return self::can_view_ratings($courseid); 123 | } 124 | 125 | /** 126 | * User can view the 'Course ratings' item in the course administration 127 | * 128 | * @param int $courseid 129 | * @return bool 130 | */ 131 | public static function can_view_report(int $courseid): bool { 132 | if (!helper::course_ratings_enabled_anywhere()) { 133 | return false; 134 | } 135 | $context = \context_course::instance($courseid); 136 | return has_capability('tool/courserating:reports', $context); 137 | } 138 | 139 | /** 140 | * Check that user can view rating or throw exception 141 | * 142 | * @param int $courseid 143 | * @throws \moodle_exception 144 | */ 145 | public static function require_can_view_ratings(int $courseid): void { 146 | if (!self::can_view_ratings($courseid)) { 147 | throw new \moodle_exception('cannotview', 'tool_courserating'); 148 | } 149 | } 150 | 151 | /** 152 | * Check that user can view reviews or throw exception 153 | * 154 | * @param int $courseid 155 | * @throws \moodle_exception 156 | */ 157 | public static function require_can_view_reviews(int $courseid): void { 158 | if (!self::can_view_reviews($courseid)) { 159 | throw new \moodle_exception('cannotview', 'tool_courserating'); 160 | } 161 | } 162 | 163 | /** 164 | * Check that user can add/change rating or throw exception 165 | * 166 | * @param int $courseid 167 | * @throws \moodle_exception 168 | */ 169 | public static function require_can_add_rating(int $courseid): void { 170 | if (!self::can_add_rating($courseid)) { 171 | throw new \moodle_exception('cannotrate', 'tool_courserating'); 172 | } 173 | } 174 | 175 | /** 176 | * Check that user can delete rating or throw exception 177 | * 178 | * @param int $ratingid 179 | * @param int|null $courseid 180 | * @throws required_capability_exception 181 | */ 182 | public static function require_can_delete_rating(int $ratingid, ?int $courseid = null): void { 183 | if (!$courseid) { 184 | $courseid = (new rating($ratingid))->get('courseid'); 185 | } 186 | if (!self::can_delete_rating($ratingid, $courseid)) { 187 | throw new required_capability_exception( 188 | \context_course::instance($courseid), 189 | 'tool/courserating:delete', 190 | 'nopermissions', 191 | '' 192 | ); 193 | } 194 | } 195 | 196 | /** 197 | * Check that user can flag rating or throw exception 198 | * 199 | * @param int $ratingid 200 | * @param int|null $courseid 201 | * @throws \moodle_exception 202 | */ 203 | public static function require_can_flag_rating(int $ratingid, ?int $courseid = null): void { 204 | if (!self::can_flag_rating($ratingid, $courseid)) { 205 | throw new \moodle_exception('cannotview', 'tool_courserating'); 206 | } 207 | } 208 | 209 | /** 210 | * Check that user can view rating or throw exception 211 | * 212 | * @param int $courseid 213 | * @throws \moodle_exception 214 | */ 215 | public static function require_can_view_reports(int $courseid): void { 216 | if (!\tool_courserating\helper::course_ratings_enabled_anywhere()) { 217 | // TODO create a new string, maybe link to settings for admins? 218 | throw new \moodle_exception('ratebynoone', 'tool_courserating'); 219 | } 220 | if (!self::can_view_report($courseid)) { 221 | throw new required_capability_exception( 222 | \context_course::instance($courseid), 223 | 'tool/courserating:reports', 224 | 'nopermissions', 225 | '' 226 | ); 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /tests/behat/student.feature: -------------------------------------------------------------------------------- 1 | @tool @tool_courserating @javascript 2 | Feature: Viewing and adding course ratings as a student 3 | 4 | Background: 5 | Given the following "courses" exist: 6 | | fullname | shortname | numsections | 7 | | Course 1 | C1 | 1 | 8 | | Course 2 | C2 | 1 | 9 | | Course 3 | C3 | 1 | 10 | Given the following "users" exist: 11 | | username | firstname | lastname | email | 12 | | teacher1 | Teacher | 1 | teacher1@example.com | 13 | | student1 | Student | 1 | student1@example.com | 14 | | student2 | Student | 2 | student2@example.com | 15 | | manager1 | Manager | 1 | manager1@example.com | 16 | And the following "course enrolments" exist: 17 | | user | course | role | 18 | | student1 | C1 | student | 19 | | student2 | C1 | student | 20 | | teacher1 | C1 | editingteacher | 21 | And the following "role assigns" exist: 22 | | user | role | contextlevel | reference | 23 | | manager1 | manager | System | | 24 | 25 | Scenario Outline: Rating a course as a student 26 | Given the following config values are set as admin: 27 | | usehtml | | tool_courserating | 28 | When I log in as "student1" 29 | And I am on "Course 1" course homepage 30 | And I follow "Leave a rating" 31 | And I click on ".tool_courserating-form-stars-group .stars-3" "css_element" 32 | And I set the field "Review (optional)" in the "Leave a rating" "dialogue" to "abcdef" 33 | And I press "Save changes" 34 | And I should see "3.0" in the ".tool_courserating-widget" "css_element" 35 | And I should see "(1)" in the ".tool_courserating-widget" "css_element" 36 | And I log out 37 | And I log in as "student2" 38 | And I am on "Course 1" course homepage 39 | And I should see "3.0" in the ".tool_courserating-widget" "css_element" 40 | And I should see "(1)" in the ".tool_courserating-widget" "css_element" 41 | And I follow "Leave a rating" 42 | And I click on ".tool_courserating-form-stars-group .stars-4" "css_element" 43 | And I press "Save changes" 44 | And I should see "3.5" in the ".tool_courserating-widget" "css_element" 45 | And I click on "(2)" "text" in the ".tool_courserating-widget" "css_element" 46 | And I follow "View all reviews" 47 | And I should see "3.5" in the "Course reviews" "dialogue" 48 | And I should see "abcdef" 49 | 50 | Examples: 51 | | usehtml | 52 | | 0 | 53 | | 1 | 54 | 55 | Scenario Outline: Flagging course ratings as a student 56 | Given the following config values are set as admin: 57 | | usehtml | | tool_courserating | 58 | Given the following "tool_courserating > ratings" exist: 59 | | user | course | rating | review | 60 | | student1 | C1 | 3 | abcdef | 61 | | student2 | C1 | 4 | hello | 62 | And I log in as "student2" 63 | And I am on "Course 1" course homepage 64 | And I should see "3.5" in the ".tool_courserating-widget" "css_element" 65 | And I should see "(2)" in the ".tool_courserating-widget" "css_element" 66 | And I click on ".tool_courserating-ratings" "css_element" 67 | And I follow "View all reviews" 68 | And I should see "abcdef" in the "Student 1" "tool_courserating > Review" 69 | And I click on "Flag" "link" in the "Student 1" "tool_courserating > Review" 70 | And I should see "You have flagged this review as inappropriate/offensive." in the "Student 1" "tool_courserating > Review" 71 | And I should not see "user(s) have flagged this review as inappropriate/offensive." 72 | And I should not see "Permanently delete" 73 | And I should not see "You have flagged this review as inappropriate/offensive." in the "Student 2" "tool_courserating > Review" 74 | And I log out 75 | And I log in as "manager1" 76 | And I am on course index 77 | And I click on ".tool_courserating-ratings" "css_element" in the "Course 1" "tool_courserating > Coursebox" 78 | And "Flag" "link" should exist in the "Student 1" "tool_courserating > Review" 79 | And I should see "1 user(s) have flagged this review as inappropriate/offensive." in the "Student 1" "tool_courserating > Review" 80 | And I should see "Permanently delete" in the "Student 1" "tool_courserating > Review" 81 | And I should not see "user(s) have flagged this review as inappropriate/offensive." in the "Student 2" "tool_courserating > Review" 82 | 83 | Examples: 84 | | usehtml | 85 | | 0 | 86 | | 1 | 87 | 88 | Scenario Outline: Viewing course ratings as a non-logged in user 89 | Given the following config values are set as admin: 90 | | usehtml | | tool_courserating | 91 | Given the following "tool_courserating > ratings" exist: 92 | | user | course | rating | review | 93 | | student1 | C1 | 3 | abcdef | 94 | | student2 | C1 | 4 | hello | 95 | And the following config values are set as admin: 96 | | frontpage | 7,6 | | 97 | | frontpageloggedin | 7,6 | | 98 | And I am on site homepage 99 | And I should see "3.5" in the "Course 1" "tool_courserating > Coursebox" 100 | And I should see "(2)" in the "Course 1" "tool_courserating > Coursebox" 101 | And I click on ".tool_courserating-ratings" "css_element" in the "Course 1" "tool_courserating > Coursebox" 102 | And I should see "3.5" in the "Course reviews" "dialogue" 103 | And I should see "abcdef" in the "Course reviews" "dialogue" 104 | And I should see "hello" in the "Course reviews" "dialogue" 105 | And I should not see "Flag" 106 | And I click on "Close" "button" in the "Course reviews" "dialogue" 107 | 108 | Examples: 109 | | usehtml | 110 | | 0 | 111 | | 1 | 112 | 113 | Scenario: Students can not leave text reviews 114 | Given the following config values are set as admin: 115 | | allowreviews | 1 | tool_courserating | 116 | When I log in as "student1" 117 | And I am on "Course 1" course homepage 118 | And I follow "Leave a rating" 119 | And I click on ".tool_courserating-form-stars-group .stars-3" "css_element" 120 | And I should not see "Review" in the "Leave a rating" "dialogue" 121 | And I press "Save changes" 122 | And I should see "3.0" in the ".tool_courserating-widget" "css_element" 123 | And I should see "(1)" in the ".tool_courserating-widget" "css_element" 124 | And I log out 125 | And I log in as "student2" 126 | And I am on "Course 1" course homepage 127 | And I should see "3.0" in the ".tool_courserating-widget" "css_element" 128 | And I should see "(1)" in the ".tool_courserating-widget" "css_element" 129 | And I follow "Leave a rating" 130 | And I click on ".tool_courserating-form-stars-group .stars-4" "css_element" 131 | And I press "Save changes" 132 | And I should see "3.5" in the ".tool_courserating-widget" "css_element" 133 | And I click on "(2)" "text" in the ".tool_courserating-widget" "css_element" 134 | And I should not see "View all reviews" 135 | And I follow "View all ratings" 136 | And I should see "3.5" in the "Course reviews" "dialogue" 137 | And I should not see "abcdef" 138 | 139 | Scenario: Students can leave text reviews but not read them 140 | Given the following config values are set as admin: 141 | | allowreviews | 2 | tool_courserating | 142 | When I log in as "student1" 143 | And I am on "Course 1" course homepage 144 | And I follow "Leave a rating" 145 | And I click on ".tool_courserating-form-stars-group .stars-3" "css_element" 146 | And I set the field "Review (optional)" in the "Leave a rating" "dialogue" to "abcdef" 147 | And I press "Save changes" 148 | And I should see "3.0" in the ".tool_courserating-widget" "css_element" 149 | And I should see "(1)" in the ".tool_courserating-widget" "css_element" 150 | And I log out 151 | And I log in as "student2" 152 | And I am on "Course 1" course homepage 153 | And I should see "3.0" in the ".tool_courserating-widget" "css_element" 154 | And I should see "(1)" in the ".tool_courserating-widget" "css_element" 155 | And I follow "Leave a rating" 156 | And I click on ".tool_courserating-form-stars-group .stars-4" "css_element" 157 | And I press "Save changes" 158 | And I should see "3.5" in the ".tool_courserating-widget" "css_element" 159 | And I click on "(2)" "text" in the ".tool_courserating-widget" "css_element" 160 | And I should not see "View all reviews" 161 | And I follow "View all ratings" 162 | And I should see "3.5" in the "Course reviews" "dialogue" 163 | And I should not see "abcdef" 164 | -------------------------------------------------------------------------------- /amd/build/rating.min.js: -------------------------------------------------------------------------------- 1 | define("tool_courserating/rating",["exports","core/str","core/toast","core_form/modalform","core/modal_factory","core/fragment","core/templates","core/modal_events","core/ajax"],(function(_exports,_str,_toast,_modalform,_modal_factory,_fragment,_templates,_modal_events,_ajax){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} 2 | /** 3 | * Manage the courses view for the overview block. 4 | * 5 | * @module tool_courserating/rating 6 | * @copyright 2022 Marina Glancy 7 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 8 | */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupViewRatingsPopup=_exports.setupAddRatingForm=_exports.init=_exports.hideEditField=void 0,_modalform=_interopRequireDefault(_modalform),_modal_factory=_interopRequireDefault(_modal_factory),_fragment=_interopRequireDefault(_fragment),_templates=_interopRequireDefault(_templates),_modal_events=_interopRequireDefault(_modal_events),_ajax=_interopRequireDefault(_ajax);const SELECTORS_COURSERATING=".customfield_tool_courserating",SELECTORS_COURSEWIDGET=".tool_courserating-widget",SELECTORS_ADD_RATING="[data-action=tool_courserating-addrating][data-courseid]",SELECTORS_VIEW_RATINGS_CFIELD=".tool_courserating-cfield .tool_courserating-ratings",SELECTORS_VIEW_RATINGS_LINK='[data-action="tool_courserating-viewratings"]',SELECTORS_DELETE_RATING="[data-action='tool_courserating-delete-rating']",SELECTORS_USER_RATING="[data-for='tool_courserating-user-rating']",SELECTORS_CFIELD_WRAPPER="[data-for='tool_courserating-cfield-wrapper'][data-courseid]",SELECTORS_USER_RATING_FLAG="[data-for='tool_courserating-user-flag']",SELECTORS_REVIEWS_LIST='.tool_courserating-reviews-popup [data-for="tool_courserating-reviews"]',SELECTORS_SHOWMORE_WRAPPER='.tool_courserating-reviews-popup [data-for="tool_courserating-reviews"] [data-for="tool_courserating-showmore"]',SELECTORS_SHOWMORE_BUTTON='.tool_courserating-reviews-popup [data-for="tool_courserating-reviews"] [data-for="tool_courserating-showmore"] [data-action="showmore"]',SELECTORS_RESET_WITHRATINGS='.tool_courserating-reviews-popup [data-for="tool_courserating-reviews"] [data-for="tool_courserating-resetwithrating"]',SELECTORS_POPUP_SUMMARY='.tool_courserating-reviews-popup [data-for="tool_courserating-summary"]',SELECTORS_SET_WITHRATINGS='.tool_courserating-reviews-popup [data-for="tool_courserating-summary"] [data-for="tool_courserating_setwithrating"]',SELECTORS_RBCELL='[data-for="tool_courserating-rbcell"][data-ratingid]';let systemContextId,viewRatingsModal,addRatingModal;_exports.init=systemContextIdParam=>{systemContextId=systemContextIdParam,document.addEventListener("click",(e=>{if(!e||!e.target||void 0===e.target.closest)return;const addRatingElement=e.target.closest(SELECTORS_ADD_RATING),viewRatingsElement=e.target.closest(SELECTORS_VIEW_RATINGS_CFIELD),deleteRatingElement=e.target.closest(SELECTORS_DELETE_RATING);if(addRatingElement){e.preventDefault();const courseid=addRatingElement.getAttribute("data-courseid");viewRatingsModal&&viewRatingsModal.destroy(),addRating(courseid)}else if(viewRatingsElement){e.preventDefault();const matches=(" "+viewRatingsElement.getAttribute("class")+" ").match(/ tool_courserating-ratings-courseid-(\d+) /);if(matches){const widget=viewRatingsElement.closest(SELECTORS_COURSEWIDGET);widget&&widget.querySelector(SELECTORS_ADD_RATING)?addRating(matches[1]):viewRatings(matches[1])}}else if(deleteRatingElement){e.preventDefault();const ratingid=deleteRatingElement.getAttribute("data-ratingid");deleteRating(ratingid)}})),document.addEventListener("core/inplace_editable:updated",(e=>reloadFlag(e.target)))};const reloadFlag=inplaceEditable=>{if("tool_courserating"===inplaceEditable.dataset.component&&"flag"===inplaceEditable.dataset.itemtype){const ratingid=inplaceEditable.dataset.itemid,node=document.querySelector("".concat(SELECTORS_USER_RATING_FLAG,"[data-ratingid='").concat(ratingid,"']"));node&&_fragment.default.loadFragment("tool_courserating","rating_flag",systemContextId,{ratingid:ratingid}).done(((html,js)=>{_templates.default.replaceNode(node,html,js)}))}},addRating=courseid=>{addRatingModal=new _modalform.default({formClass:"tool_courserating\\form\\addrating",args:{courseid:courseid},modalConfig:{title:(0,_str.get_string)("addrating","tool_courserating")}}),addRatingModal.addEventListener(addRatingModal.events.FORM_SUBMITTED,(()=>{(0,_str.get_string)("changessaved").then(_toast.add).catch(null),refreshRating(courseid)})),addRatingModal.show()},viewRatings=courseid=>{_modal_factory.default.create({type:_modal_factory.default.types.CANCEL,title:(0,_str.get_string)("coursereviews","tool_courserating"),large:!0,buttons:{cancel:(0,_str.get_string)("closebuttontitle","core")},removeOnClose:!0}).then((modal=>(modal.setLarge(),loadCourseRatingPopupContents({courseid:courseid}).done((_ref=>{let{html:html,js:js}=_ref;modal.setBody(html),_templates.default.runTemplateJS(js)})),modal.getRoot().on(_modal_events.default.hidden,(function(){modal.destroy()})),modal.show(),viewRatingsModal=modal,modal))).fail((()=>null))},deleteRating=ratingid=>{const form=new _modalform.default({formClass:"tool_courserating\\form\\deleterating",args:{ratingid:ratingid},modalConfig:{title:(0,_str.get_string)("deleterating","tool_courserating")}});form.addEventListener(form.events.FORM_SUBMITTED,(async e=>{const el=document.querySelector(SELECTORS_USER_RATING+"[data-ratingid='".concat(e.detail.ratingid,"'"));if(el&&el.remove(),refreshRating(e.detail.courseid),!el){const rbcell=document.querySelector(SELECTORS_RBCELL+"[data-ratingid='".concat(e.detail.ratingid,"'"));rbcell&&(rbcell.innerHTML=await(0,_str.get_string)("ratingdeleted","tool_courserating"))}})),form.show()},refreshRating=courseid=>{let el1=document.getElementsByClassName("tool_courserating-ratings-courseid-"+courseid);if(el1&&el1.length){const cfield=el1[0].closest(SELECTORS_COURSERATING);_fragment.default.loadFragment("tool_courserating","cfield",systemContextId,{courseid:courseid}).done(((html,js)=>{_templates.default.replaceNode(cfield,html,js)}))}const el2=document.querySelector(SELECTORS_CFIELD_WRAPPER+"[data-courseid='".concat(courseid,"']"));el2&&_fragment.default.loadFragment("tool_courserating","cfield",systemContextId,{courseid:courseid}).done(((html,js)=>{el2.innerHTML="",_templates.default.appendNodeContents(el2,html,js)}));const el3=document.querySelector("[data-for='tool_courserating-summary'][data-courseid='".concat(courseid,"']"));el3&&_fragment.default.loadFragment("tool_courserating","course_ratings_summary",systemContextId,{courseid:courseid}).done(((html,js)=>{el3.innerHTML="",_templates.default.appendNodeContents(el3,html,js)}))},setFormGroupClasses=(ratingFormGroup,value)=>{const addRemoveClass=(className,add)=>{add&&!ratingFormGroup.classList.contains(className)?ratingFormGroup.classList.add(className):!add&&ratingFormGroup.classList.contains(className)&&ratingFormGroup.classList.remove(className)};for(let i=1;i<=5;i++)addRemoveClass("s-"+i,i<=parseInt(value));addRemoveClass("tool_courserating-norating",0===parseInt(value))};_exports.setupAddRatingForm=grpId=>{const ratingFormGroup=document.getElementById(grpId),curchecked=ratingFormGroup.querySelector("input:checked");setFormGroupClasses(ratingFormGroup,curchecked?curchecked.value:0);let els=ratingFormGroup.querySelectorAll("input");for(let i=0;isetFormGroupClasses(ratingFormGroup,e.target.value)));let labels=ratingFormGroup.querySelectorAll("label");for(let i=0;i{const el=e.target.closest("label").querySelector("input");setFormGroupClasses(ratingFormGroup,el.value)})),labels[i].addEventListener("mouseleave",(()=>{const el=ratingFormGroup.querySelector("input:checked");setFormGroupClasses(ratingFormGroup,el?el.value:0)}));const viewratingsLink=ratingFormGroup.closest("form").querySelector(SELECTORS_VIEW_RATINGS_LINK);viewratingsLink&&viewratingsLink.addEventListener("click",(e=>{e.preventDefault(),addRatingModal.modal.destroy(),viewRatings(e.target.dataset.courseid)}))};_exports.setupViewRatingsPopup=()=>{const el=document.querySelector(SELECTORS_REVIEWS_LIST),reloadReviews=function(){let offset=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;const params={courseid:el.dataset.courseid,offset:offset,withrating:el.dataset.withrating};return _fragment.default.loadFragment("tool_courserating","course_reviews",el.dataset.systemcontextid,params)};null==el||el.addEventListener("click",(e=>{const button=e.target.closest(SELECTORS_SHOWMORE_BUTTON);if(button){const wrapper=button.closest(SELECTORS_SHOWMORE_WRAPPER);e.preventDefault(),reloadReviews(button.dataset.nextoffset).done(((html,js)=>_templates.default.replaceNode(wrapper,html,js)))}e.target.closest(SELECTORS_RESET_WITHRATINGS)&&(e.preventDefault(),el.dataset.withrating=0,reloadReviews(0).done(((html,js)=>_templates.default.replaceNodeContents(el,html,js))))}));const elSummary=document.querySelector(SELECTORS_POPUP_SUMMARY);null==elSummary||elSummary.addEventListener("click",(e=>{const withRatingButton=e.target.closest(SELECTORS_SET_WITHRATINGS);withRatingButton&&(el.dataset.withrating=el.dataset.withrating===withRatingButton.dataset.withrating?0:withRatingButton.dataset.withrating,reloadReviews(0).done(((html,js)=>_templates.default.replaceNodeContents(el,html,js))))}))};_exports.hideEditField=fieldname=>{const s="#fitem_id_customfield_"+fieldname;let el=document.querySelector(s+"_editor");el&&(el.style.display="none",el=document.querySelector(s+"_static"),el&&(el.style.display="none"))};const loadCourseRatingPopupContents=function(args){return!document.body.classList.contains("notloggedin")?_fragment.default.loadFragment("tool_courserating","course_ratings_popup",systemContextId,args).then(((html,js)=>({html:html,js:js}))):_ajax.default.call([{methodname:"tool_courserating_course_rating_popup",args:args}],void 0,!1)[0].then((function(data){return{html:data.html,js:_fragment.default.processCollectedJavascript(data.javascript)}}))}})); 9 | 10 | //# sourceMappingURL=rating.min.js.map --------------------------------------------------------------------------------