diff --git a/.github/workflows/config.json b/.github/workflows/config.json new file mode 100644 index 0000000000..91629598e8 --- /dev/null +++ b/.github/workflows/config.json @@ -0,0 +1,15 @@ +{ + "moodle-testmatrix": { + "MOODLE_405_STABLE": { + "php": ["8.1", "8.2", "8.3"] + }, + "MOODLE_500_STABLE": { + "php": ["8.2", "8.3", "8.4"], + "db": ["pgsql", "mariadb", "mysqli"] + }, + "MOODLE_501_STABLE": { + "php": ["8.2", "8.3", "8.3"], + "db": ["pgsql", "mariadb", "mysqli"] + } + } +} \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index de63c4d35d..29d8cb5417 100755 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,15 @@ CHANGELOG ========= +v.5.1-r1 (2025-11-27) +--------------------- +Moodleoverflow had a lot of bug fixes as well as code improving changes. +Here a list of the most important changes: +- Fix for errors and false rendering when editing posts ([#233](https://github.com/learnweb/moodle-mod_moodleoverflow/pull/233), [#244](https://github.com/learnweb/moodle-mod_moodleoverflow/pull/244)) +- Bugfix in "Mark posts as read"-button ([#232](https://github.com/learnweb/moodle-mod_moodleoverflow/pull/232)) +- No more missing activity completion ([#238](https://github.com/learnweb/moodle-mod_moodleoverflow/pull/238)) +- Improved behavior for moving discussions ([#239](https://github.com/learnweb/moodle-mod_moodleoverflow/pull/239), [#241](https://github.com/learnweb/moodle-mod_moodleoverflow/pull/241)) + v5.0-r1 (2025-08-01) ------------------ - Fixes Issues [#216](https://github.com/learnweb/moodle-mod_moodleoverflow/issues/216), @@ -14,4 +23,4 @@ v5.0-r1 (2025-08-01) 4.5.0 (2025-05-06) ------------------ -* Moodle 4.5 compatible version \ No newline at end of file +* Moodle 4.5 compatible version diff --git a/README.md b/README.md index ce45b9f7b7..e49cea1ac9 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,61 @@ # ![moodle-mod_groupmembers](pix/icon.png) Activity Module: Moodleoverflow -[![Coverage Status](https://coveralls.io/repos/github/learnweb/moodle-mod_moodleoverflow/badge.svg?branch=master)](https://coveralls.io/github/learnweb/moodle-mod_moodleoverflow?branch=master) +The Moodleoverflow activity provides users the ability to create discussion forums that are not strictly linear or chronological. +It has similarities to the Moodle _forum_, but it is more intended for question-and-answer style discussions and provides additional features that support meaningful interaction. Moodleoverflow features +are highly customizable to ensure that it fits the needs of all users. A Moodleoverflow instance represents a forum that contains multiple discussions with posts. -This plugin enables Moodle users to create a non-linear, non-chronologic discussion forum. -The plugin has similar features as the Moodle forum, but is not intended for general discussions, but rather for straightforward question-and-answer discussions. -Additionally, users can rate posts and gain a rating score ("reputation") by being rated by other users. -Users who have started a discussion can mark a post as helpful, and teachers can mark a post as a (correct) solution to the posed question. +## Installation +Clone the content into `{your/moodle/dirroot}/mod/moodleoverflow` and complete the installation in +_Site administration > Notifications_ or run `$ php admin/cli/upgrade.php` in your cli. -This plugin is developed by Kennet Winter, [Tamara Gunkel](https://github.com/TamaraGunkel), and [Jan Dageförde](https://github.com/Dagefoerde) -and is maintained by Learnweb (University of Münster). +## Core features +The main features of Moodleoverflow are the *rating and reputation system*, *subscription and read tracking*, the *anonymous mode* and *other forms of moderation*. -## Installation -This plugin should go into `mod/mooodleoverflow`. Upon installation, several default settings need to be defined that pre-configure future instances of this activity (see [Settings](#settings)). - -## Rating -If a post is rated up or down, the post owner's rating score increases or decreases. The rating score of a user is always shown after the user name. -In the settings, you can define what amount of reputation a downvote or upvote gives. -Posts with a high score are displayed further up than posts with a lower score. -If a post is marked as helpful or solved, the post owner's rating score also increases. By default, a mark gives a higher amount of reputation than an upvote. -A marked post is always displayed first, but you can choose which mark (solved or helpful) is more important. - -## Screenshots -Moodleoverflow activity:

- -

- -Every user can see the discussions and the posts. -The discussion overview shows the status, among other things. Thus users can see if a discussion is already solved (green tick) or if a post is marked as helpful (orange tick). -

- -

-Posts can be marked as helpful (orange) by the question owner or as solved (green) by a teacher. The current post is marked blue. -Additionally, everybody can vote posts up or down. The posts are ordered by the number of upvotes. The post owner's reputation increases if the post is upvoted and decreases if it is downvoted. -Post owners can edit their posts until 30 minutes after posting. Teachers can edit and delete posts from everybody without time restrictions. -

- -

-Users can attach files. If a picture is attached, it will be displayed as an image. If another file type is attached, the file will be shown but not the content. -

- -

-A discussion can be deleted by deleting the first post. - -### Students' view -Unlike teachers, students can't edit or delete a post or mark it as solved. -

- -

+### Rating and reputation system +Like in _Stackoverflow_, users can rate posts with up- and downvotes, which rank the posts in a discussion. Additionally, the user that started the +discussion can mark posts as _"helpful"_(orange) and course teachers can mark a post as _"solution"_(green). + +If enabled, Moodleoverflow tracks the users activity within a single course with a *reputation* score. Activities like voting or getting upvotes and helpful/solution marks +increase the reputation, while downvotes decrease it. A user's reputation is displayed when they write a post. Detailed explanation as well as instructions on how +to tailor the reputation system to your own needs can be found [here](https://github.com/learnweb/moodle-mod_moodleoverflow/wiki/Documentation-for-administrators). + +What a typical discussion looks like: + +lively_discussion + +Moodleoverflow offers the ability for teachers to show the user statistics for a single course. Teachers can then see which students are particularly active in Moodleoverflow forums: + +userstats + +
+ +### Subscription and read tracking +Users can subscribe to individual discussions or entire forums to receive notifications via email. Teachers have the ability to enforce subscriptions for important forums. +Moodleoverflow can track unread discussions and display a visual hint. All users can control their personal moodleoverflow settings in the overview: + +overview +A forum with unread posts: + +unread_posts + +
+ +### Moderation options +Teachers can restrict certain features in a forum. With the *anonymous mode* certain users (the discussion starter or all users) are anonymous within a forum. This can motivate users +to interact with other students and increase their activity in discussions. Especially in Q&A this can be an ice breaker. + +anonymous_forum + + +If teachers want to limit the time users can post replies, the *limited answer mode* can be activated when creating a moodleoverflow. The teacher sets a time frame in which +the students can write posts. This can be used to collect questions before answers are allowed, or to enforce a posting deadline. + +--- ## Settings -### Global -In the global settings, you can set e.g. the number of discussions per page, the maximum attachment size, or read tracking. -In addition to these settings which are the same as in the forum, you can define the amount of reputation a vote or mark gives. -

- -

- -### Course wide -In the course settings you can override a few settings like maximum attachment size or read tracking. -Moreover, you can decide if helpful or solved posts are displayed first and how the reputation is calculated. -

- -

-If read tracking is set to "optional" and turned on by the students, the unread posts are highlighted. -

- -

- -### Students -Depending on the global and course settings students can choose if they want to track posts and receive email notifications. -

- -

+ +The global settings allow administrators to enable or disable core features or to set the boundaries of what the teachers can customize in the local settings. +With the local settings in each moodleoverflow teachers can specify the use case of a forum and make use of the core features A detailed explanation of the settings can be found [here](https://github.com/learnweb/moodle-mod_moodleoverflow/wiki/Documentation-for-administrators) + +--- +This plugin was initially implemented by Kennet Winter and is further developed and maintained by the [Learnweb development team](https://github.com/learnweb) (University of Münster) diff --git a/backup/moodle2/backup_moodleoverflow_stepslib.php b/backup/moodle2/backup_moodleoverflow_stepslib.php index 522cc5a087..c22a84d5b8 100644 --- a/backup/moodle2/backup_moodleoverflow_stepslib.php +++ b/backup/moodle2/backup_moodleoverflow_stepslib.php @@ -74,9 +74,6 @@ protected function define_structure() { $read = new backup_nested_element('read', ['id'], [ 'userid', 'discussionid', 'postid', 'firstread', 'lastread', ]); - $grades = new backup_nested_element('grades'); - $grade = new backup_nested_element('grade', ['id'], ['userid', 'grade']); - $tracking = new backup_nested_element('tracking'); $track = new backup_nested_element('track', ['id'], ['userid']); diff --git a/backup/moodle2/restore_moodleoverflow_activity_task.class.php b/backup/moodle2/restore_moodleoverflow_activity_task.class.php index c706015b28..0eae787bd2 100644 --- a/backup/moodle2/restore_moodleoverflow_activity_task.class.php +++ b/backup/moodle2/restore_moodleoverflow_activity_task.class.php @@ -107,72 +107,17 @@ public static function define_decode_rules() { public static function define_restore_log_rules() { $rules = []; - $rules[] = new restore_log_rule( - 'moodleoverflow', - 'add', - 'view.php?id={course_module}', - '{moodleoverflow}' - ); - $rules[] = new restore_log_rule( - 'moodleoverflow', - 'update', - 'view.php?id={course_module}', - '{moodleoverflow}' - ); - $rules[] = new restore_log_rule( - 'moodleoverflow', - 'view', - 'view.php?id={course_module}', - '{moodleoverflow}' - ); - $rules[] = new restore_log_rule( - 'moodleoverflow', - 'view moodleoverflow', - 'view.php?id={course_module}', - '{moodleoverflow}' - ); - $rules[] = new restore_log_rule( - 'moodleoverflow', - 'mark read', - 'view.php?f={moodleoverflow}', - '{moodleoverflow}' - ); - $rules[] = new restore_log_rule( - 'moodleoverflow', - 'start tracking', - 'view.php?f={moodleoverflow}', - '{moodleoverflow}' - ); - $rules[] = new restore_log_rule( - 'moodleoverflow', - 'stop tracking', - 'view.php?f={moodloeoverflow}', - '{moodleoverflow}' - ); - $rules[] = new restore_log_rule( - 'moodleoverflow', - 'subscribe', - 'view.php?f={moodleoverflow}', - '{moodleoverflow}' - ); - $rules[] = new restore_log_rule( - 'moodleoverflow', - 'unsubscribe', - 'view.php?f={moodleoverflow}', - '{moodleoverflow}' - ); - $rules[] = new restore_log_rule( - 'moodleoverflow', - 'subscriber', - 'subscribers.php?id={moodleoverflow}', - '{moodleoverflow}' - ); - $rules[] = new restore_log_rule( - 'moodleoverflow', - 'subscribers', - 'subscribers.php?id={moodleoverflow}', - '{moodleoverflow}' - ); + $rules[] = new restore_log_rule('moodleoverflow', 'add', 'view.php?id={course_module}', '{moodleoverflow}'); + $rules[] = new restore_log_rule('moodleoverflow', 'update', 'view.php?id={course_module}', '{moodleoverflow}'); + $rules[] = new restore_log_rule('moodleoverflow', 'view', 'view.php?id={course_module}', '{moodleoverflow}'); + $rules[] = new restore_log_rule('moodleoverflow', 'view moodleoverflow', 'view.php?id={course_module}', '{moodleoverflow}'); + $rules[] = new restore_log_rule('moodleoverflow', 'mark read', 'view.php?f={moodleoverflow}', '{moodleoverflow}'); + $rules[] = new restore_log_rule('moodleoverflow', 'start tracking', 'view.php?f={moodleoverflow}', '{moodleoverflow}'); + $rules[] = new restore_log_rule('moodleoverflow', 'stop tracking', 'view.php?f={moodloeoverflow}', '{moodleoverflow}'); + $rules[] = new restore_log_rule('moodleoverflow', 'subscribe', 'view.php?f={moodleoverflow}', '{moodleoverflow}'); + $rules[] = new restore_log_rule('moodleoverflow', 'unsubscribe', 'view.php?f={moodleoverflow}', '{moodleoverflow}'); + $rules[] = new restore_log_rule('moodleoverflow', 'subscriber', 'subscribers.php?id={moodleoverflow}', '{moodleoverflow}'); + $rules[] = new restore_log_rule('moodleoverflow', 'subscribers', 'subscribers.php?id={moodleoverflow}', '{moodleoverflow}'); $rules[] = new restore_log_rule( 'moodleoverflow', 'view subscribers', @@ -205,12 +150,7 @@ public static function define_restore_log_rules() { null, 'delete discussion' ); - $rules[] = new restore_log_rule( - 'moodleoverflow', - 'delete discussion', - 'view.php?id={course_module}', - '{moodleoverflow}' - ); + $rules[] = new restore_log_rule('moodleoverflow', 'delete discussion', 'view.php?id={course_module}', '{moodleoverflow}'); $rules[] = new restore_log_rule( 'moodleoverflow', 'add post', @@ -235,7 +175,6 @@ public static function define_restore_log_rules() { 'discussion.php?d={moodleoverflow_discussion}', '[post]' ); - return $rules; } @@ -250,10 +189,6 @@ public static function define_restore_log_rules() { * activity level. All them are rules not linked to any module instance (cmid = 0) */ public static function define_restore_log_rules_for_course() { - $rules = []; - - $rules[] = new restore_log_rule('moodleoverflow', 'view all', 'index.php?id={course}', null); - - return $rules; + return [new restore_log_rule('moodleoverflow', 'view all', 'index.php?id={course}', null)]; } } diff --git a/classes/discussion/discussion.php b/classes/discussion/discussion.php index 6ba285a0f5..f32c44b74f 100644 --- a/classes/discussion/discussion.php +++ b/classes/discussion/discussion.php @@ -24,14 +24,15 @@ namespace mod_moodleoverflow\discussion; - -// Import namespace from the locallib, needs a check later which namespaces are really needed. -use mod_moodleoverflow\anonymous; // Important namespaces. +use coding_exception; +use dml_exception; +use Exception; +use mod_moodleoverflow\event\discussion_viewed; +use mod_moodleoverflow\ratings; use mod_moodleoverflow\readtracking; -use mod_moodleoverflow\review; use mod_moodleoverflow\post\post; -use mod_moodleoverflow\capabilities; +use moodle_exception; defined('MOODLE_INTERNAL') || die(); @@ -50,71 +51,71 @@ * Accessing these functions directly without the checks from the post control could lead to serious errors. */ class discussion { - /** @var int The discussion ID */ - private $id; + /** @var ?int The discussion ID */ + private ?int $id; /** @var int The course ID where the discussion is located */ - private $course; + private int $course; /** @var int The moodleoverflow ID where the discussion is located*/ - private $moodleoverflow; + private int $moodleoverflow; /** @var string The title of the discussion, the titel of the parent post*/ - public $name; + public string $name; /** @var int The id of the parent/first post*/ - private $firstpost; + private int $firstpost; /** @var int The user ID who started the discussion */ - private $userid; + private int $userid; /** @var int Unix-timestamp of modification */ - public $timemodified; + public int $timemodified; /** @var int Unix-timestamp of discussion creation */ - public $timestart; + public int $timestart; /** @var int the user ID who modified the discussion */ - public $usermodified; + public int $usermodified; // Not Database-related attributes. /** @var post[] an Array of posts that belong to this discussion */ - public $posts; + public array $posts; /** @var bool a variable for checking if this instance has all its posts */ - public $postsbuild; + public bool $postsbuild; /** @var object The moodleoverflow object where the discussion is located */ - public $moodleoverflowobject; + public object $moodleoverflowobject; /** @var object The course module object */ - public $cmobject; + public object $cmobject; // Constructors and other builders. /** * Constructor to build a new discussion. - * @param int $id The Discussion ID. - * @param int $course The course ID. - * @param int $moodleoverflow The moodleoverflow ID. - * @param char $name Discussion Title. - * @param int $firstpost . - * @param int $userid The course ID. - * @param int $timemodified The course ID. - * @param int $timestart The course ID. - * @param int $usermodified The course ID. + * @param ?int $id The Discussion ID. + * @param int $course The course ID. + * @param int $moodleoverflow The moodleoverflow ID. + * @param string $name Discussion Title. + * @param int $firstpost . + * @param int $userid The course ID. + * @param int $timemodified The course ID. + * @param int $timestart The course ID. + * @param int $usermodified The course ID. */ public function __construct( - $id, - $course, - $moodleoverflow, - $name, - $firstpost, - $userid, - $timemodified, - $timestart, - $usermodified + ?int $id, + int $course, + int $moodleoverflow, + string $name, + int $firstpost, + int $userid, + int $timemodified, + int $timestart, + int $usermodified ) { $this->id = $id; $this->course = $course; @@ -132,54 +133,19 @@ public function __construct( /** * Builds a Discussion from a DB record. * - * @param object $record Data object. + * @param object $record Data object. * @return discussion discussion instance */ - public static function from_record($record) { - $id = null; - if (object_property_exists($record, 'id') && $record->id) { - $id = $record->id; - } - - $course = 0; - if (object_property_exists($record, 'course') && $record->course) { - $course = $record->course; - } - - $moodleoverflow = 0; - if (object_property_exists($record, 'moodleoverflow') && $record->moodleoverflow) { - $moodleoverflow = $record->moodleoverflow; - } - - $name = ''; - if (object_property_exists($record, 'name') && $record->name) { - $name = $record->name; - } - - $firstpost = 0; - if (object_property_exists($record, 'firstpost') && $record->firstpost) { - $firstpost = $record->firstpost; - } - - $userid = 0; - if (object_property_exists($record, 'userid') && $record->userid) { - $userid = $record->userid; - } - - $timemodified = 0; - if (object_property_exists($record, 'timemodified') && $record->timemodified) { - $timemodified = $record->timemodified; - } - - $timestart = 0; - if (object_property_exists($record, 'timestart') && $record->timestart) { - $timestart = $record->timestart; - } - - $usermodified = 0; - if (object_property_exists($record, 'usermodified') && $record->usermodified) { - $usermodified = $record->usermodified; - } + public static function from_record(object $record): discussion { + $id = !empty($record->id) ? $record->id : null; + $course = !empty($record->course) ? $record->course : 0; + $moodleoverflow = !empty($record->moodleoverflow) ? $record->moodleoverflow : 0; + $name = !empty($record->name) ? $record->name : ''; + $firstpost = !empty($record->firstpost) ? $record->firstpost : 0; + $userid = !empty($record->userid) ? $record->userid : 0; + $timemodified = !empty($record->timemodified) ? $record->timemodified : 0; + $timestart = !empty($record->timestart) ? $record->timestart : 0; + $usermodified = !empty($record->usermodified) ? $record->usermodified : 0; $instance = new self($id, $course, $moodleoverflow, $name, $firstpost, $userid, $timemodified, $timestart, $usermodified); @@ -191,30 +157,29 @@ public static function from_record($record) { /** * Function to build a new discussion without specifying the Discussion ID. - * @param int $course The course ID. - * @param int $moodleoverflow The moodleoverflow ID. - * @param char $name Discussion Title. - * @param int $firstpost . - * @param int $userid The course ID. - * @param int $timemodified The course ID. - * @param int $timestart The course ID. - * @param int $usermodified The course ID. + * @param int $course The course ID. + * @param int $moodleoverflow The moodleoverflow ID. + * @param string $name Discussion Title. + * @param int $firstpost . + * @param int $userid The course ID. + * @param int $timemodified The course ID. + * @param int $timestart The course ID. + * @param int $usermodified The course ID. * * @return object discussion object without id. */ public static function construct_without_id( - $course, - $moodleoverflow, - $name, - $firstpost, - $userid, - $timemodified, - $timestart, - $usermodified - ) { + int $course, + int $moodleoverflow, + string $name, + int $firstpost, + int $userid, + int $timemodified, + int $timestart, + int $usermodified + ): object { $id = null; - $instance = new self($id, $course, $moodleoverflow, $name, $firstpost, $userid, $timemodified, $timestart, $usermodified); - return $instance; + return new self($id, $course, $moodleoverflow, $name, $firstpost, $userid, $timemodified, $timestart, $usermodified); } // Discussion Functions. @@ -223,8 +188,11 @@ public static function construct_without_id( * Adds a new Discussion with a post. * * @param object $prepost The prepost object from the post_control. Has information about the post and other important stuff. + * @return bool|?int + * @throws dml_exception + * @throws coding_exception */ - public function moodleoverflow_add_discussion($prepost) { + public function moodleoverflow_add_discussion(object $prepost): bool|int|null { global $DB; // Add the discussion to the Database. @@ -261,7 +229,7 @@ public function moodleoverflow_add_discussion($prepost) { 'objectid' => $this->id, ]; // LEARNWEB-TODO: check if the event functions. - $event = \mod_moodleoverflow\event\discussion_viewed::create($params); + $event = discussion_viewed::create($params); $event->trigger(); // Return the id of the discussion. @@ -272,8 +240,9 @@ public function moodleoverflow_add_discussion($prepost) { * Delete a discussion with all of it's posts * @param object $prepost Information about the post from the post_control * @return bool Wether deletion was successful of not + * @throws moodle_exception */ - public function moodleoverflow_delete_discussion($prepost) { + public function moodleoverflow_delete_discussion(object $prepost): bool { global $DB; $this->existence_check(); $this->posts_check(); @@ -324,8 +293,9 @@ public function moodleoverflow_delete_discussion($prepost) { * Adds a new post to this discussion and the DB. * * @param object $prepost The prepost object from the post_control. Has Information about the post and other important stuff. + * @throws dml_exception|moodle_exception */ - public function moodleoverflow_add_post_to_discussion($prepost) { + public function moodleoverflow_add_post_to_discussion(object $prepost) { global $DB; $this->existence_check(); $this->posts_check(); @@ -361,10 +331,10 @@ public function moodleoverflow_add_post_to_discussion($prepost) { /** * Deletes a post that is in this discussion from the DB. * @param object $prepost The prepost object from the post_control. Has Information about the post and other important stuff. - * @return bool Wether the deletion was possible - * @throws moodle_exception if post is not in this discussion or something failed. + * @return bool If the deletion was possible + * @throws moodle_exception */ - public function moodleoverflow_delete_post_from_discussion($prepost) { + public function moodleoverflow_delete_post_from_discussion(object $prepost): bool { $this->existence_check(); $this->posts_check(); @@ -390,8 +360,9 @@ public function moodleoverflow_delete_post_from_discussion($prepost) { /** * Edits the message of a post from this discussion. * @param object $prepost The prepost object from the post_control. Has Information about the post and other important stuff. + * @throws dml_exception|moodle_exception */ - public function moodleoverflow_edit_post_from_discussion($prepost) { + public function moodleoverflow_edit_post_from_discussion(object $prepost): bool { global $DB; $this->existence_check(); $this->posts_check(); @@ -420,8 +391,9 @@ public function moodleoverflow_edit_post_from_discussion($prepost) { * the timemodified and the usermodified need to be adapted to the last added or edited post. * * @return bool true if the DB needed to be adapted. false if it didn't change. + * @throws dml_exception|moodle_exception */ - public function moodleoverflow_discussion_adapt_to_last_post() { + public function moodleoverflow_discussion_adapt_to_last_post(): bool { global $DB; $this->existence_check(); @@ -454,9 +426,10 @@ public function moodleoverflow_discussion_adapt_to_last_post() { /** * Getter for the post ID - * @return int $this->id The post ID. + * @return ?int $this->id The post ID. + * @throws moodle_exception */ - public function get_id() { + public function get_id(): ?int { $this->existence_check(); return $this->id; } @@ -464,8 +437,9 @@ public function get_id() { /** * Getter for the courseid * @return int $this->course The ID of the course where the discussion is located. + * @throws moodle_exception */ - public function get_courseid() { + public function get_courseid(): int { $this->existence_check(); return $this->course; } @@ -473,8 +447,9 @@ public function get_courseid() { /** * Getter for the moodleoverflowid * @return int $this->moodleoverflow The ID of the moodleoverflow where the discussion is located. + * @throws moodle_exception */ - public function get_moodleoverflowid() { + public function get_moodleoverflowid(): int { $this->existence_check(); return $this->moodleoverflow; } @@ -482,8 +457,9 @@ public function get_moodleoverflowid() { /** * Getter for the firstpostid * @return int $this->firstpost The ID of the first post. + * @throws moodle_exception */ - public function get_firstpostid() { + public function get_firstpostid(): int { $this->existence_check(); return $this->firstpost; } @@ -491,8 +467,9 @@ public function get_firstpostid() { /** * Getter for the userid * @return int $this->userid The ID of the user who wrote the first post. + * @throws moodle_exception */ - public function get_userid() { + public function get_userid(): int { $this->existence_check(); return $this->userid; } @@ -500,13 +477,13 @@ public function get_userid() { /** * Returns the ratings from this discussion. * @return array of votings + * @throws moodle_exception */ - public function moodleoverflow_get_discussion_ratings() { + public function moodleoverflow_get_discussion_ratings(): array { $this->existence_check(); $this->posts_check(); - $discussionratings = \mod_moodleoverflow\ratings::moodleoverflow_get_ratings_by_discussion($this->id); - return $discussionratings; + return ratings::moodleoverflow_get_ratings_by_discussion($this->id); } /** @@ -514,8 +491,9 @@ public function moodleoverflow_get_discussion_ratings() { * The first/parent post is on the first position in the array. * * @return array $posts Array ob posts objects + * @throws moodle_exception */ - public function moodleoverflow_get_discussion_posts() { + public function moodleoverflow_get_discussion_posts(): array { global $DB; $this->existence_check(); @@ -551,13 +529,14 @@ public function moodleoverflow_get_discussion_posts() { * Returns the moodleoverflowobject * * @return object $moodleoverflowobject + * @throws dml_exception|moodle_exception */ - public function get_moodleoverflow() { + public function get_moodleoverflow(): object { global $DB; $this->existence_check(); if (empty($this->moodleoverflowobject)) { - $this->moodleoverflowobject = $DB->get_records('moodleoverflow', ['id' => $this->moodleoverflow]); + $this->moodleoverflowobject = $DB->get_record('moodleoverflow', ['id' => $this->moodleoverflow]); } return $this->moodleoverflowobject; @@ -567,20 +546,21 @@ public function get_moodleoverflow() { * Returns the coursemodule * * @return object $cmobject + * @throws dml_exception + * @throws moodle_exception */ - public function get_coursemodule() { - global $DB; + public function get_coursemodule(): object { $this->existence_check(); if (empty($this->cmobject)) { if ( - !$this->cmobject = $DB->get_coursemodule_from_instance( + !$this->cmobject = get_coursemodule_from_instance( 'moodleoverflow', $this->get_moodleoverflow()->id, $this->get_moodleoverflow()->course ) ) { - throw new \moodle_exception('invalidcoursemodule'); + throw new moodle_exception('invalidcoursemodule'); } } @@ -591,8 +571,9 @@ public function get_coursemodule() { * This getter works as an help function in case another file/function needs the db-object of this instance (as the function * is not adapted/refactored to the new way of working with discussion). * @return object + * @throws moodle_exception */ - public function get_db_object() { + public function get_db_object(): object { $this->existence_check(); return $this->build_db_object(); } @@ -604,7 +585,7 @@ public function get_db_object() { * As this is an private function, it doesn't need an existence check. * @return object $dbobject */ - private function build_db_object() { + private function build_db_object(): object { $dbobject = new \stdClass(); $dbobject->id = $this->id; $dbobject->course = $this->course; @@ -625,41 +606,37 @@ private function build_db_object() { * Makes sure that the instance exists in the database. Every function in this class requires this check * (except the function that adds the discussion to the database) * - * @return true + * @return void * @throws moodle_exception */ - private function existence_check() { - if (empty($this->id) || $this->id == false || $this->id == null) { - throw new \moodle_exception('noexistingdiscussion', 'moodleoverflow'); + private function existence_check(): void { + if (empty($this->id)) { + throw new moodle_exception('noexistingdiscussion', 'moodleoverflow'); } - return true; } /** * Makes sure that the instance knows all of its posts (That all posts of the db are in the local array). * Not all functions need this check. - * @return true + * @return void * @throws moodle_exception */ - private function posts_check() { + private function posts_check(): void { if (!$this->postsbuild) { - throw new \moodle_exception('notallpostsavailable', 'moodleoverflow'); + throw new moodle_exception('notallpostsavailable', 'moodleoverflow'); } - return true; } /** * Check, if certain posts really exists in this discussion. * * @param int $postid The ID of the post that is being checked. - * @return true + * @return void * @throws moodle_exception; */ - private function post_exists_check($postid) { + private function post_exists_check(int $postid): void { if (!$this->posts[$postid]) { - throw new \moodle_exception('postnotpartofdiscussion', 'moodleoverflow'); + throw new moodle_exception('postnotpartofdiscussion', 'moodleoverflow'); } - - return true; } } diff --git a/classes/manager/mail_manager.php b/classes/manager/mail_manager.php index 44a1124b59..3b96f23c84 100644 --- a/classes/manager/mail_manager.php +++ b/classes/manager/mail_manager.php @@ -24,20 +24,13 @@ namespace mod_moodleoverflow\manager; -use context_course; use context_module; -use core\context\course; -use core\cron; -use core\message\message; -use core_php_time_limit; use core_user; use dml_exception; use mod_moodleoverflow\anonymous; use mod_moodleoverflow\output\moodleoverflow_email; use mod_moodleoverflow\subscriptions; use moodle_exception; -use moodle_url; -use renderer_base; use stdClass; /** diff --git a/classes/output/moodleoverflow_email.php b/classes/output/moodleoverflow_email.php index 13d1d90b92..8c3a4060ec 100644 --- a/classes/output/moodleoverflow_email.php +++ b/classes/output/moodleoverflow_email.php @@ -24,7 +24,6 @@ namespace mod_moodleoverflow\output; -use mod_moodleoverflow\anonymous; use mod_moodleoverflow\subscriptions; /** diff --git a/classes/post/post.php b/classes/post/post.php index da37936246..0d4f97c188 100644 --- a/classes/post/post.php +++ b/classes/post/post.php @@ -25,14 +25,17 @@ namespace mod_moodleoverflow\post; -// Import namespace from the locallib, needs a check later which namespaces are really needed. -use mod_moodleoverflow\anonymous; -use mod_moodleoverflow\capabilities; -use mod_moodleoverflow\review; +use coding_exception; +use core_user\fields; +use dml_exception; +use mod_moodleoverflow\event\post_deleted; +use mod_moodleoverflow\ratings; use mod_moodleoverflow\readtracking; use mod_moodleoverflow\discussion\discussion; use mod_moodleoverflow_post_form; use moodle_exception; +use moodle_url; +use stdClass; defined('MOODLE_INTERNAL') || die(); @@ -59,94 +62,94 @@ class post { // Attributes. The most important attributes are private and can only be changed by internal functions. // Other attributes can be accessed directly. - /** @var int The post ID */ - private $id; + /** @var ?int The post ID */ + private ?int $id; /** @var int The corresponding discussion ID */ - private $discussion; + private int $discussion; /** @var int The parent post ID */ - private $parent; + private int $parent; /** @var int The ID of the User who wrote the post */ - private $userid; + private int $userid; /** @var int Creation timestamp */ - public $created; + public int $created; /** @var int Modification timestamp */ - public $modified; + public int $modified; /** @var string The message (content) of the post */ - public $message; + public string $message; /** @var int The message format*/ - public $messageformat; + public int $messageformat; /** @var string Attachment of the post */ - public $attachment; + public string $attachment; /** @var int Mailed status*/ - public $mailed; + public int $mailed; /** @var int Review status */ - public $reviewed; + public int $reviewed; - /** @var int The time where the post was reviewed*/ - public $timereviewed; + /** @var ?int The time when the post was reviewed*/ + public ?int $timereviewed; // Not database related functions. - /** @var int This variable is optional, it contains important information for the add_attachment function */ - public $formattachments; + /** @var ?int This variable is optional, it contains important information for the add_attachment function */ + public ?int $formattachments; /** @var string The subject/title of the Discussion */ - public $subject; + public string $subject; /** @var discussion The discussion where the post is located */ public discussion $discussionobject; /** @var object The Moodleoverflow where the post is located*/ - public $moodleoverflowobject; + public object $moodleoverflowobject; /** @var object The course module object */ - public $cmobject; + public object $cmobject; - /** @var object The parent post of an answerpost */ - public $parentpost; + /** @var ?object The parent post of an answerpost */ + public ?object $parentpost; // Constructors and other builders. /** * Constructor to make a new post. - * @param int $id The post ID. - * @param int $discussion The discussion ID. - * @param int $parent The parent post ID. - * @param int $userid The user ID that created the post. - * @param int $created Creation timestamp - * @param int $modified Modification timestamp - * @param string $message The message (content) of the post - * @param int $messageformat The message format - * @param char $attachment Attachment of the post - * @param int $mailed Mailed status - * @param int $reviewed Review status - * @param int $timereviewed The time where the post was reviewed - * @param object $formattachments Information about attachments of the post_form + * @param ?int $id The post ID. + * @param int $discussion The discussion ID. + * @param int $parent The parent post ID. + * @param int $userid The user ID that created the post. + * @param int $created Creation timestamp + * @param int $modified Modification timestamp + * @param string $message The message (content) of the post + * @param int $messageformat The message format + * @param string $attachment Attachment of the post + * @param int $mailed Mailed status + * @param int $reviewed Review status + * @param ?int $timereviewed The time when the post was reviewed + * @param ?int $formattachments Information about attachments of the post_form */ public function __construct( - $id, - $discussion, - $parent, - $userid, - $created, - $modified, - $message, - $messageformat, - $attachment, - $mailed, - $reviewed, - $timereviewed, - $formattachments = false + ?int $id, + int $discussion, + int $parent, + int $userid, + int $created, + int $modified, + string $message, + int $messageformat, + string $attachment, + int $mailed, + int $reviewed, + ?int $timereviewed, + ?int $formattachments = null ) { $this->id = $id; $this->discussion = $discussion; @@ -166,69 +169,22 @@ public function __construct( /** * Builds a Post from a DB record. * Look up database structure for standard values. - * @param object $record Data object. + * @param object $record Data object. * @return post post instance */ - public static function from_record($record): post { - $id = null; - if (object_property_exists($record, 'id') && $record->id) { - $id = $record->id; - } - - $discussion = 0; - if (object_property_exists($record, 'discussion') && $record->discussion) { - $discussion = $record->discussion; - } - - $parent = 0; - if (object_property_exists($record, 'parent') && $record->parent) { - $parent = $record->parent; - } - - $userid = 0; - if (object_property_exists($record, 'userid') && $record->userid) { - $userid = $record->userid; - } - - $created = 0; - if (object_property_exists($record, 'created') && $record->created) { - $created = $record->created; - } - - $modified = 0; - if (object_property_exists($record, 'modified') && $record->modified) { - $modified = $record->modified; - } - - $message = ''; - if (object_property_exists($record, 'message') && $record->message) { - $message = $record->message; - } - - $messageformat = 0; - if (object_property_exists($record, 'messageformat') && $record->messageformat) { - $messageformat = $record->messageformat; - } - - $attachment = ''; - if (object_property_exists($record, 'attachment') && $record->attachment) { - $attachment = $record->attachment; - } - - $mailed = 0; - if (object_property_exists($record, 'mailed') && $record->mailed) { - $mailed = $record->mailed; - } - - $reviewed = 1; - if (object_property_exists($record, 'reviewed') && $record->reviewed) { - $reviewed = $record->reviewed; - } - - $timereviewed = null; - if (object_property_exists($record, 'timereviewed') && $record->timereviewed) { - $timereviewed = $record->timereviewed; - } + public static function from_record(object $record): post { + $id = !empty($record->id) ? $record->id : null; + $discussion = !empty($record->discussion) ? $record->discussion : 0; + $parent = !empty($record->parent) ? $record->parent : 0; + $userid = !empty($record->userid) ? $record->userid : 0; + $created = !empty($record->created) ? $record->created : 0; + $modified = !empty($record->modified) ? $record->modified : 0; + $message = !empty($record->message) ? $record->message : ''; + $messageformat = !empty($record->messageformat) ? $record->messageformat : 0; + $attachment = !empty($record->attachment) ? $record->attachment : ''; + $mailed = !empty($record->mailed) ? $record->mailed : 0; + $reviewed = !empty($record->reviewed) ? $record->reviewed : 1; + $timereviewed = !empty($record->timereviewed) ? $record->timereviewed : null; return new self( $id, @@ -249,35 +205,35 @@ public static function from_record($record): post { /** * Function to make a new post without specifying the Post ID. * - * @param int $discussion The discussion ID. - * @param int $parent The parent post ID. - * @param int $userid The user ID that created the post. - * @param int $created Creation timestamp - * @param int $modified Modification timestamp - * @param string $message The message (content) of the post - * @param int $messageformat The message format - * @param char $attachment Attachment of the post - * @param int $mailed Mailed status - * @param int $reviewed Review status - * @param int $timereviewed The time where the post was reviewed - * @param object $formattachments Information about attachments from the post_form + * @param int $discussion The discussion ID. + * @param int $parent The parent post ID. + * @param int $userid The user ID that created the post. + * @param int $created Creation timestamp + * @param int $modified Modification timestamp + * @param string $message The message (content) of the post + * @param int $messageformat The message format + * @param string $attachment Attachment of the post + * @param int $mailed Mailed status + * @param int $reviewed Review status + * @param ?int $timereviewed The time when the post was reviewed + * @param ?int $formattachments Information about attachments from the post_form * * @return object post object without id */ public static function construct_without_id( - $discussion, - $parent, - $userid, - $created, - $modified, - $message, - $messageformat, - $attachment, - $mailed, - $reviewed, - $timereviewed, - $formattachments = false - ) { + int $discussion, + int $parent, + int $userid, + int $created, + int $modified, + string $message, + int $messageformat, + string $attachment, + int $mailed, + int $reviewed, + ?int $timereviewed, + ?int $formattachments = null + ): object { $id = null; return new self( $id, @@ -300,12 +256,12 @@ public static function construct_without_id( /** * Adds a new post in an existing discussion. - * @return bool|int The Id of the post if operation was successful + * @return bool|int|null The Id of the post if operation was successful * @throws coding_exception - * @throws dml_exception + * @throws dml_exception|moodle_exception */ - public function moodleoverflow_add_new_post() { - global $USER, $DB; + public function moodleoverflow_add_new_post(): bool|int|null { + global $DB; // Add post to the database. $this->id = $DB->insert_record('moodleoverflow_posts', $this->build_db_object()); @@ -349,11 +305,12 @@ public function moodleoverflow_add_new_post() { /** * Deletes a single moodleoverflow post. * - * @param bool $deletechildren The child posts + * @param bool $deletechildren The child posts * * @return bool Whether the deletion was successful or not + * @throws moodle_exception */ - public function moodleoverflow_delete_post($deletechildren) { + public function moodleoverflow_delete_post(bool $deletechildren): bool { global $DB, $USER; $this->existence_check(); @@ -369,7 +326,7 @@ public function moodleoverflow_delete_post($deletechildren) { if ($deletechildren && $childposts) { foreach ($childposts as $childpost) { $child = $this->from_record($childpost); - $child->moodleoverflow_delete_post($deletechildren); + $child->moodleoverflow_delete_post(true); } } @@ -420,7 +377,7 @@ public function moodleoverflow_delete_post($deletechildren) { if ($this->userid !== $USER->id) { $params['relateduserid'] = $this->userid; } - $event = \mod_moodleoverflow\event\post_deleted::create($params); + $event = post_deleted::create($params); $event->trigger(); // Set the id of this instance to null, so that working with it is not possible anymore. @@ -440,14 +397,15 @@ public function moodleoverflow_delete_post($deletechildren) { /** * Edits the message from this instance. - * @param int $time The time the post was modified (given from the discussion class). - * @param string $postmessage The new message - * @param int $messageformat - * @param object $formattachments Information about attachments from the post_form + * @param int $time The time the post was modified (given from the discussion class). + * @param string $postmessage The new message + * @param int $messageformat + * @param ?int $formattachments Information about attachments from the post_form * * @return true if the post has been edited successfully + * @throws moodle_exception */ - public function moodleoverflow_edit_post($time, $postmessage, $messageformat, $formattachments) { + public function moodleoverflow_edit_post(int $time, string $postmessage, int $messageformat, ?int $formattachments): bool { global $DB; $this->existence_check(); @@ -488,24 +446,21 @@ public function moodleoverflow_edit_post($time, $postmessage, $messageformat, $f * Gets a post with all info ready for moodleoverflow_print_post. * Most of these joins are just to get the forum id. * - * @return mixed array of posts or false + * @return object array of posts or false + * @throws moodle_exception */ - public function moodleoverflow_get_complete_post() { - global $DB, $CFG; + public function moodleoverflow_get_complete_post(): object { + global $DB; $this->existence_check(); - if ($CFG->branch >= 311) { - $allnames = \core_user\fields::for_name()->get_sql('u', false, '', '', false)->selects; - } else { - $allnames = implode(', ', fields::get_name_fields()); - } + $allnames = fields::for_name()->get_sql('u', false, '', '', false)->selects; $sql = "SELECT p.*, d.moodleoverflow, $allnames, u.email, u.picture, u.imagealt FROM {moodleoverflow_posts} p JOIN {moodleoverflow_discussions} d ON p.discussion = d.id LEFT JOIN {user} u ON p.userid = u.id WHERE p.id = " . $this->id . " ;"; - $post = $DB->get_records_sql($sql); + $post = $DB->get_record_sql($sql); if ($post->userid == 0) { $post->message = get_string('privacy:anonym_post_message', 'mod_moodleoverflow'); } @@ -516,13 +471,14 @@ public function moodleoverflow_get_complete_post() { * If successful, this function returns the name of the file * * @return bool + * @throws moodle_exception */ - public function moodleoverflow_add_attachment() { + public function moodleoverflow_add_attachment(): void { global $DB; $this->existence_check(); if (empty($this->formattachments)) { - return true; // Nothing to do. + return; // Nothing to do. } $context = \context_module::instance($this->get_coursemodule()->id); @@ -542,10 +498,10 @@ public function moodleoverflow_add_attachment() { /** * Returns attachments with information for the template * - * * @return array + * @throws moodle_exception */ - public function moodleoverflow_get_attachments() { + public function moodleoverflow_get_attachments(): array { global $CFG, $OUTPUT; $this->existence_check(); @@ -598,9 +554,10 @@ public function moodleoverflow_get_attachments() { /** * Getter for the postid - * @return int $this->id The post ID. + * @return ?int $this->id The post ID. + * @throws moodle_exception */ - public function get_id() { + public function get_id(): ?int { $this->existence_check(); return $this->id; } @@ -608,8 +565,9 @@ public function get_id() { /** * Getter for the discussionid * @return int $this->discussion The ID of the discussion where the post is located. + * @throws moodle_exception */ - public function get_discussionid() { + public function get_discussionid(): int { $this->existence_check(); return $this->discussion; } @@ -617,8 +575,9 @@ public function get_discussionid() { /** * Getter for the parentid * @return int $this->parent The ID of the parent post. + * @throws moodle_exception */ - public function get_parentid() { + public function get_parentid(): int { $this->existence_check(); return $this->parent; } @@ -626,8 +585,9 @@ public function get_parentid() { /** * Getter for the userid * @return int $this->userid The ID of the user who wrote the post. + * @throws moodle_exception */ - public function get_userid() { + public function get_userid(): int { $this->existence_check(); return $this->userid; } @@ -635,8 +595,9 @@ public function get_userid() { /** * Returns the moodleoverflow where the post is located. * @return object $moodleoverflowobject + * @throws moodle_exception */ - public function get_moodleoverflow() { + public function get_moodleoverflow(): object { global $DB; $this->existence_check(); @@ -652,6 +613,7 @@ public function get_moodleoverflow() { * Returns the discussion where the post is located. * * @return discussion $discussionobject. + * @throws moodle_exception */ public function get_discussion(): discussion { global $DB; @@ -668,8 +630,9 @@ public function get_discussion(): discussion { * Returns the coursemodule * * @return object $cmobject + * @throws moodle_exception */ - public function get_coursemodule() { + public function get_coursemodule(): object { $this->existence_check(); if (empty($this->cmobject)) { @@ -681,16 +644,18 @@ public function get_coursemodule() { /** * Returns the parent post - * @return object|false $post|false + * @return post|null $post|false + * @throws dml_exception + * @throws moodle_exception */ - public function moodleoverflow_get_parentpost() { + public function moodleoverflow_get_parentpost(): post|null { global $DB; $this->existence_check(); if ($this->parent == 0) { // This post is the parent post. - $this->parentpost = false; - return false; + $this->parentpost = null; + return null; } if (empty($this->parentpost)) { @@ -703,9 +668,10 @@ public function moodleoverflow_get_parentpost() { /** * Returns children posts (answers) as DB-records. * - * @return array|false children/answer posts. + * @return array children/answer posts. + * @throws moodle_exception */ - public function moodleoverflow_get_childposts() { + public function moodleoverflow_get_childposts(): array { global $DB; $this->existence_check(); @@ -713,15 +679,16 @@ public function moodleoverflow_get_childposts() { return $childposts; } - return false; + return []; } /** * This getter works as an help function in case another file/function needs the db-object of this instance (as the function * is not adapted/refactored to the new way of working with discussion). * @return object + * @throws moodle_exception */ - public function get_db_object() { + public function get_db_object(): object { $this->existence_check(); return $this->build_db_object(); } @@ -732,14 +699,15 @@ public function get_db_object() { * Calculate the ratings of a post. * * @return object $ratingsobject. + * @throws moodle_exception */ - public function moodleoverflow_get_post_ratings() { + public function moodleoverflow_get_post_ratings(): object { $this->existence_check(); $discussionid = $this->get_discussion()->get_id(); - $postratings = \mod_moodleoverflow\ratings::moodleoverflow_get_ratings_by_discussion($discussionid, $this->id); + $postratings = ratings::moodleoverflow_get_ratings_by_discussion($discussionid, $this->id); - $ratingsobject = new \stdClass(); + $ratingsobject = new stdClass(); $ratingsobject->upvotes = $postratings->upvotes; $ratingsobject->downvotes = $postratings->downvotes; $ratingsobject->votesdifference = $postratings->upvotes - $postratings->downvotes; @@ -752,8 +720,10 @@ public function moodleoverflow_get_post_ratings() { /** * Marks the post as read if the user is tracking the discussion. * Uses function from mod_moodleoverflow\readtracking. + * @return void + * @throws moodle_exception */ - public function mark_post_read() { + public function mark_post_read(): void { global $USER; $cantrack = readtracking::moodleoverflow_can_track_moodleoverflows($this->get_moodleoverflow()); $istracked = readtracking::moodleoverflow_is_tracked($this->get_moodleoverflow()); @@ -768,8 +738,8 @@ public function mark_post_read() { * Builds an object from this instance that has only DB-relevant attributes. * @return object $dbobject */ - private function build_db_object() { - $dbobject = new \stdClass(); + private function build_db_object(): object { + $dbobject = new stdClass(); $dbobject->id = $this->id; $dbobject->discussion = $this->discussion; $dbobject->parent = $this->parent; @@ -791,15 +761,12 @@ private function build_db_object() { * * @param bool $onlyreviewed Whether to count only reviewed posts. * @return int Amount of replies + * @throws dml_exception */ - public function moodleoverflow_count_replies($onlyreviewed) { + public function moodleoverflow_count_replies(bool $onlyreviewed): int { global $DB; - $conditions = ['parent' => $this->id]; - - if ($onlyreviewed) { - $conditions['reviewed'] = '1'; - } + $conditions = ['parent' => $this->id] + ($onlyreviewed ? ['reviewed' => '1'] : []); // Return the amount of replies. return $DB->count_records('moodleoverflow_posts', $conditions); @@ -811,13 +778,12 @@ public function moodleoverflow_count_replies($onlyreviewed) { * Makes sure that the instance exists in the database. Every function in this class requires this check * (except the function that adds a post to the database) * - * @return true + * @return void * @throws moodle_exception */ - private function existence_check() { - if (empty($this->id) || $this->id == false || $this->id == null) { + private function existence_check(): void { + if (empty($this->id)) { throw new moodle_exception('noexistingpost', 'moodleoverflow'); } - return true; } } diff --git a/classes/post/post_control.php b/classes/post/post_control.php index b36242e113..9e087b5984 100644 --- a/classes/post/post_control.php +++ b/classes/post/post_control.php @@ -402,7 +402,6 @@ private function execute_create(object $form): void { // Please be aware that in future the use of get_db_object() should be replaced with only $this->info->discussion, // as the subscription class should be refactored with the new way of working with posts. subscriptions::moodleoverflow_post_subscription( - $form, $this->info->moodleoverflow, $discussion->get_db_object(), $this->info->modulecontext @@ -459,7 +458,6 @@ private function execute_reply(object $form): void { // LEARNWEB-TODO: Please be aware that in future the use of build_db_object() should be replaced with only // $this->info->discussion, as the subscription class should be refactored with the new way of working with posts. subscriptions::moodleoverflow_post_subscription( - $form, $this->info->moodleoverflow, $this->info->discussion->get_db_object(), $this->info->modulecontext diff --git a/classes/privacy/data_export_helper.php b/classes/privacy/data_export_helper.php index 32a886c4a5..24f39ce78c 100644 --- a/classes/privacy/data_export_helper.php +++ b/classes/privacy/data_export_helper.php @@ -36,12 +36,12 @@ class data_export_helper { /** * Store all information about all discussions that we have detected this user to have access to. * - * @param int $userid The userid of the user whose data is to be exported. - * @param array $mappings A list of mappings from forumid => contextid. + * @param int $userid The userid of the user whose data is to be exported. + * @param array $mappings A list of mappings from forumid => contextid. * - * @return array Which forums had data written for them. + * @return array Which forums had data written for them. */ - public static function export_discussion_data($userid, array $mappings) { + public static function export_discussion_data(int $userid, array $mappings): array { global $DB; // Find all of the discussions, and discussion subscriptions for this forum. [$foruminsql, $forumparams] = $DB->get_in_or_equal(array_keys($mappings), SQL_PARAMS_NAMED); diff --git a/classes/ratings.php b/classes/ratings.php index fd983aae23..a9127d73c1 100644 --- a/classes/ratings.php +++ b/classes/ratings.php @@ -310,8 +310,6 @@ public static function moodleoverflow_user_rated($postid, $userid = null) { * @return array */ public static function moodleoverflow_get_rating($postid) { - global $DB; - // Retrieve the full post. $post = moodleoverflow_get_record_or_exception('moodleoverflow_posts', ['id' => $postid], 'postnotexist'); diff --git a/classes/readtracking.php b/classes/readtracking.php index fbd1292538..055fb2aae1 100644 --- a/classes/readtracking.php +++ b/classes/readtracking.php @@ -24,7 +24,9 @@ namespace mod_moodleoverflow; +use coding_exception; use context_module; +use dml_exception; use moodle_exception; /** @@ -76,18 +78,18 @@ public static function moodleoverflow_can_track_moodleoverflows($moodleoverflow /** * Tells whether a specific moodleoverflow is tracked by the user. * - * @param object $moodleoverflow - * @param object|null $user + * @param object $moodleoverflow + * @param ?object $user * * @return bool + * @throws dml_exception + * @throws coding_exception */ - public static function moodleoverflow_is_tracked($moodleoverflow, $user = null) { + public static function moodleoverflow_is_tracked(object $moodleoverflow, ?object $user = null): bool { global $USER, $DB; // Get the user. - if (is_null($user)) { - $user = $USER; - } + $user = $user ?? $USER; // Guests cannot track a moodleoverflow. The moodleoverflow should be generally trackable. if (isguestuser($USER) || empty($USER->id) || !self::moodleoverflow_can_track_moodleoverflows($moodleoverflow)) { diff --git a/classes/subscriptions.php b/classes/subscriptions.php index 982a090b0d..00916110ef 100644 --- a/classes/subscriptions.php +++ b/classes/subscriptions.php @@ -53,7 +53,7 @@ class subscriptions { * * @var array[] An array of arrays. */ - protected static $moodleoverflowcache = []; + protected static array $moodleoverflowcache = []; /** * The list of moodleoverflows which have been wholly retrieved for the subscription cache. @@ -63,7 +63,7 @@ class subscriptions { * * @var bool[] */ - protected static $fetchedmoodleoverflows = []; + protected static array $fetchedinstances = []; /** * The subscription cache for moodleoverflow discussions. @@ -75,7 +75,7 @@ class subscriptions { * * @var array[] */ - protected static $discussioncache = []; + protected static array $discussioncache = []; /** * The list of moodleoverflows which have been wholly retrieved for the discussion subscription cache. @@ -85,7 +85,7 @@ class subscriptions { * * @var bool[] */ - protected static $fetcheddiscussions = []; + protected static array $fetcheddiscussions = []; /** * Returns whether a user is subscribed to this moodleoverflow or a specific discussion within the moodleoverflow. @@ -191,7 +191,7 @@ public static function fill_subscription_cache($moodleoverflowid, $userid = null global $DB; // Check if the moodleoverflow has not been fetched as a whole. - if (!isset(self::$fetchedmoodleoverflows[$moodleoverflowid])) { + if (!isset(self::$fetchedinstances[$moodleoverflowid])) { // Is a specified user requested? if (isset($userid)) { // Create the cache for the user. @@ -226,7 +226,7 @@ public static function fill_subscription_cache($moodleoverflowid, $userid = null } // Mark the moodleoverflow as fetched. - self::$fetchedmoodleoverflows[$moodleoverflowid] = true; + self::$fetchedinstances[$moodleoverflowid] = true; $subscriptions->close(); } } @@ -444,15 +444,15 @@ public static function get_unsubscribable_moodleoverflows() { $mergedparams = array_merge($courseparams, $params); $moodleoverflows = $DB->get_recordset_sql($sql, $mergedparams); - // Loop through all of the results and add them to an array. - $unsubscribablemoodleoverflows = []; + // Loop through all results and add them to an array. + $unsubscribableinstances = []; foreach ($moodleoverflows as $moodleoverflow) { - $unsubscribablemoodleoverflows[] = $moodleoverflow; + $unsubscribableinstances[] = $moodleoverflow; } $moodleoverflows->close(); // Return the array. - return $unsubscribablemoodleoverflows; + return $unsubscribableinstances; } /** @@ -630,7 +630,7 @@ public static function reset_moodleoverflow_cache() { self::$moodleoverflowcache = []; // Reset the fetched moodleoverflows. - self::$fetchedmoodleoverflows = []; + self::$fetchedinstances = []; } /** @@ -967,14 +967,13 @@ public static function moodleoverflow_get_subscribe_link($moodleoverflow, $conte * Given a new post, subscribes the user to the thread the post was posted in. * * LEARNWEB-TODO: Refactor this function to the new way of working with discussion and posts. - * @param object $fromform The submitted form * @param stdClass $moodleoverflow The moodleoverflow record * @param stdClass $discussion The discussion record * @param context_module $modulecontext The context of the module * * @return bool */ - public static function moodleoverflow_post_subscription($fromform, $moodleoverflow, $discussion, $modulecontext) { + public static function moodleoverflow_post_subscription($moodleoverflow, $discussion, $modulecontext) { global $USER; // Check for some basic information. diff --git a/classes/tables/userstats_table.php b/classes/tables/userstats_table.php index 3319490f56..03b07141e0 100644 --- a/classes/tables/userstats_table.php +++ b/classes/tables/userstats_table.php @@ -222,16 +222,6 @@ private function badge_render($number) { } } - /** - * error handling - * @param object $colname - * @param int $attempt - * @return null - */ - public function other_cols($colname, $attempt) { - return null; - } - // Helper functions. /** diff --git a/classes/task/send_review_mails.php b/classes/task/send_review_mails.php index fceae57182..fa8d419d98 100644 --- a/classes/task/send_review_mails.php +++ b/classes/task/send_review_mails.php @@ -24,9 +24,9 @@ namespace mod_moodleoverflow\task; +use core\cron; use core\session\exception; use mod_moodleoverflow\anonymous; -use mod_moodleoverflow\manager\mail_manager; use mod_moodleoverflow\output\moodleoverflow_email; defined('MOODLE_INTERNAL') || die(); @@ -64,7 +64,7 @@ public function execute() { * Sends initial notifications for needed reviews to all users with review capability. */ public function send_review_notifications() { - global $DB, $OUTPUT, $PAGE, $CFG; + global $DB, $OUTPUT, $PAGE; $rendererhtml = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'htmlemail'); $renderertext = $PAGE->get_renderer('mod_moodleoverflow', 'email', 'textemail'); @@ -123,12 +123,7 @@ public function send_review_notifications() { foreach ($usersto as $userto) { try { - // Check for moodle version. Version 401 supported until 8 December 2025. - if ($CFG->branch >= 402) { - \core\cron::setup_user($userto, $course); - } else { - cron_setup_user($userto, $course); - } + cron::setup_user($userto, $course); $maildata = new moodleoverflow_email( $course, diff --git a/externallib.php b/externallib.php index 0c991eb78f..4378f8b3ad 100644 --- a/externallib.php +++ b/externallib.php @@ -303,7 +303,7 @@ public static function review_reject_post($postid, $reason = null) { if (!$post->parent) { // Delete discussion, if this is the question. - moodleoverflow_delete_discussion($discussion, $course, $cm, $moodleoverflow); + moodleoverflow_delete_discussion($discussion, $cm, $moodleoverflow); } else { moodleoverflow_delete_post($post, true, $cm, $moodleoverflow); } diff --git a/lib.php b/lib.php index 12a7bc6ddd..6fad020f42 100644 --- a/lib.php +++ b/lib.php @@ -30,9 +30,8 @@ */ // LEARNWEB-TODO: Adapt functions to the new way of working with posts and discussions (Replace the post/discussion functions). -use core\context\course; use core_completion\api; -use mod_moodleoverflow\anonymous; +use mod_moodleoverflow\subscriptions; defined('MOODLE_INTERNAL') || die(); require_once(dirname(__FILE__) . '/locallib.php'); @@ -124,11 +123,10 @@ function moodleoverflow_supports(string $feature): mixed { * of the new instance. * * @param stdClass $data Submitted data from the form in mod_form.php - * @param ?mod_moodleoverflow_mod_form $mform The form instance itself (if needed) * * @return int The id of the newly inserted moodleoverflow record */ -function moodleoverflow_add_instance(stdClass $data, ?mod_moodleoverflow_mod_form $mform = null) { +function moodleoverflow_add_instance(stdClass $data) { global $DB; // Set the current time. @@ -147,7 +145,7 @@ function moodleoverflow_add_instance(stdClass $data, ?mod_moodleoverflow_mod_for * Handle changes following the creation of a moodleoverflow instance. * This function is typically called by the course_module_created observer. * - * @param object $context The context of the moodleoverflow + * @param context_module $context The context of the moodleoverflow * @param stdClass $moodleoverflow The moodleoverflow object */ function moodleoverflow_instance_created($context, $moodleoverflow) { @@ -155,11 +153,11 @@ function moodleoverflow_instance_created($context, $moodleoverflow) { // Check if users are forced to be subscribed to the moodleoverflow instance. if ($moodleoverflow->forcesubscribe == MOODLEOVERFLOW_INITIALSUBSCRIBE) { // Get a list of all potential subscribers. - $users = \mod_moodleoverflow\subscriptions::get_potential_subscribers($context, 'u.id, u.email'); + $users = subscriptions::get_potential_subscribers($context, 'u.id, u.email'); // Subscribe all potential subscribers to this moodleoverflow. foreach ($users as $user) { - \mod_moodleoverflow\subscriptions::subscribe_user($user->id, $moodleoverflow, $context); + subscriptions::subscribe_user($user->id, $moodleoverflow, $context); } } } @@ -172,11 +170,10 @@ function moodleoverflow_instance_created($context, $moodleoverflow) { * will update an existing instance with new data. * * @param stdClass $data The data from the form in mod_form.php - * @param mod_moodleoverflow_mod_form|null $mform The form instance itself (if needed) * * @return boolean Success/Fail */ -function moodleoverflow_update_instance(stdClass $data, ?mod_moodleoverflow_mod_form $mform = null) { +function moodleoverflow_update_instance(stdClass $data): bool { global $DB; $data->timemodified = time(); @@ -194,11 +191,11 @@ function moodleoverflow_update_instance(stdClass $data, ?mod_moodleoverflow_mod_ if ($data->forcesubscribe != $oldmoodleoverflow->forcesubscribe) { if ($data->forcesubscribe == MOODLEOVERFLOW_INITIALSUBSCRIBE) { // Get a list of potential subscribers. - $users = \mod_moodleoverflow\subscriptions::get_potential_subscribers($modulecontext, 'u.id, u.email', ''); + $users = subscriptions::get_potential_subscribers($modulecontext, 'u.id, u.email', ''); // Subscribe all those users to the moodleoverflow instance. foreach ($users as $user) { - \mod_moodleoverflow\subscriptions::subscribe_user($user->id, $data, $modulecontext); + subscriptions::subscribe_user($user->id, $data, $modulecontext); } } else if ($data->forcesubscribe == MOODLEOVERFLOW_CHOOSESUBSCRIBE) { // Delete all current subscribers. @@ -291,7 +288,7 @@ function moodleoverflow_delete_instance($id) { if ($discussions = $DB->get_records('moodleoverflow_discussions', ['moodleoverflow' => $moodleoverflow->id])) { require_once('locallib.php'); foreach ($discussions as $discussion) { - if (!moodleoverflow_delete_discussion($discussion, $course, $cm, $moodleoverflow)) { + if (!moodleoverflow_delete_discussion($discussion, $cm, $moodleoverflow)) { $result = false; } } @@ -416,7 +413,7 @@ function moodleoverflow_get_file_info($browser, $areas, $course, $cm, $context, * @param array $options additional options affecting the file serving */ function moodleoverflow_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = []) { - global $DB, $CFG; + global $DB; if ($context->contextlevel != CONTEXT_MODULE) { return false; } @@ -490,8 +487,8 @@ function moodleoverflow_extend_settings_navigation(settings_navigation $settings $enrolled = is_enrolled($context, $USER, '', false); $activeenrolled = is_enrolled($context, $USER, '', true); $canmanage = has_capability('mod/moodleoverflow:managesubscriptions', $context); - $forcesubscribed = \mod_moodleoverflow\subscriptions::is_forcesubscribed($moodleoverflow); - $subscdisabled = \mod_moodleoverflow\subscriptions::subscription_disabled($moodleoverflow); + $forcesubscribed = subscriptions::is_forcesubscribed($moodleoverflow); + $subscdisabled = subscriptions::subscription_disabled($moodleoverflow); $cansubscribe = $activeenrolled && (!$subscdisabled || $canmanage) && !($forcesubscribed && has_capability('mod/moodleoverflow:allowforcesubscribe', $context)); $cantrack = \mod_moodleoverflow\readtracking::moodleoverflow_can_track_moodleoverflows($moodleoverflow); @@ -513,7 +510,7 @@ function moodleoverflow_extend_settings_navigation(settings_navigation $settings // Display a link to subscribe or unsubscribe. if ($cansubscribe) { // Choose the linktext depending on the current state of subscription. - $issubscribed = \mod_moodleoverflow\subscriptions::is_subscribed($USER->id, $moodleoverflow, $context); + $issubscribed = subscriptions::is_subscribed($USER->id, $moodleoverflow, $context); if ($issubscribed) { $linktext = get_string('unsubscribe', 'moodleoverflow'); } else { @@ -650,7 +647,7 @@ function moodleoverflow_can_create_attachment($moodleoverflow, $context) { * @return array array of grades */ function moodleoverflow_get_user_grades($moodleoverflow, $userid = 0) { - global $CFG, $DB; + global $DB; $params = ["moodleoverflowid" => $moodleoverflow->id]; diff --git a/locallib.php b/locallib.php index 38eca74d03..fec264e9d1 100644 --- a/locallib.php +++ b/locallib.php @@ -24,6 +24,8 @@ * @copyright 2017 Kennet Winter * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ + +use core_user\fields; use mod_moodleoverflow\anonymous; use mod_moodleoverflow\capabilities; use mod_moodleoverflow\event\post_deleted; @@ -46,7 +48,7 @@ * @return array */ function moodleoverflow_get_discussions($cm, $page = -1, $perpage = 0) { - global $DB, $CFG, $USER; + global $DB, $USER; // LEARNWEB-TODO Refactor variable naming. $discussion->id is first post and $discussion->discussion is discussion id? @@ -69,28 +71,13 @@ function moodleoverflow_get_discussions($cm, $page = -1, $perpage = 0) { } // Get all name fields as sql string snippet. - if ($CFG->branch >= 311) { - $allnames = \core_user\fields::for_name()->get_sql('u', false, '', '', false)->selects; - } else { - $allnames = get_all_user_name_fields(true, 'u'); - } + $allnames = fields::for_name()->get_sql('u', false, '', '', false)->selects; $postdata = 'p.id, p.modified, p.discussion, p.userid, p.reviewed'; $discussiondata = 'd.name, d.timemodified, d.timestart, d.usermodified, d.firstpost'; $userdata = 'u.email, u.picture, u.imagealt'; - if ($CFG->branch >= 311) { - $usermodifiedfields = \core_user\fields::for_name()->get_sql( - 'um', - false, - 'um', - '', - false - )->selects . + $usermodifiedfields = fields::for_name()->get_sql('um', false, 'um', '', false)->selects . ', um.email AS umemail, um.picture AS umpicture, um.imagealt AS umimagealt'; - } else { - $usermodifiedfields = get_all_user_name_fields(true, 'um', null, 'um') . - ', um.email AS umemail, um.picture AS umpicture, um.imagealt AS umimagealt'; - } $params = [$cm->instance]; $whereconditions = ['d.moodleoverflow = ?', 'p.parent = 0']; @@ -227,11 +214,11 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - $canmovetopic = false; if ((!is_guest($context, $USER) && isloggedin()) && has_capability('mod/moodleoverflow:movetopic', $context)) { $modinfo = get_fast_modinfo($moodleoverflow->course); - $coursemoodleoverflows = $modinfo->get_instances_of('moodleoverflow'); - $coursemoodleoverflows = array_filter($coursemoodleoverflows, function ($cm) { + $coursemodules = $modinfo->get_instances_of('moodleoverflow'); + $coursemodules = array_filter($coursemodules, function ($cm) { return $cm->deletioninprogress == 0; }); - $canmovetopic = count($coursemoodleoverflows) > 1; + $canmovetopic = count($coursemodules) > 1; } // Iterate through every visible discussion. @@ -313,11 +300,7 @@ function moodleoverflow_print_latest_discussions($moodleoverflow, $cm, $page = - // Get information about the user who started the discussion. $startuser = new stdClass(); - if ($CFG->branch >= 311) { - $startuserfields = \core_user\fields::get_picture_fields(); - } else { - $startuserfields = explode(',', user_picture::fields()); - } + $startuserfields = fields::get_picture_fields(); $startuser = username_load_fields_from_object($startuser, $discussion, null, $startuserfields); $startuser->id = $discussion->userid; @@ -676,7 +659,7 @@ function moodleoverflow_get_discussions_unread($cm) { */ function moodleoverflow_get_post_full($postid) { global $DB; - $allnames = \core_user\fields::for_name()->get_sql('u', false, '', '', false)->selects; + $allnames = fields::for_name()->get_sql('u', false, '', '', false)->selects; $sql = "SELECT p.*, d.moodleoverflow, $allnames, u.email, u.picture, u.imagealt FROM {moodleoverflow_posts} p JOIN {moodleoverflow_discussions} d ON p.discussion = d.id @@ -971,7 +954,7 @@ function moodleoverflow_print_discussion( $multiplemarks = false, ?stdClass $limitedanswersetting = null ) { - global $USER, $DB; + global $USER; // Check if the current is the starter of the discussion. $ownpost = (isloggedin() && ($USER->id == $post->userid)); @@ -1077,10 +1060,9 @@ function moodleoverflow_print_discussion( * @return array */ function moodleoverflow_get_all_discussion_posts($discussionid, $tracking, $modcontext) { - global $DB, $USER, $CFG; + global $DB, $USER; // Initiate tracking settings. - $params = []; $trackingselector = ""; $trackingjoin = ""; $params = []; @@ -1093,11 +1075,7 @@ function moodleoverflow_get_all_discussion_posts($discussionid, $tracking, $modc } // Get all username fields. - if ($CFG->branch >= 311) { - $allnames = \core_user\fields::for_name()->get_sql('u', false, '', '', false)->selects; - } else { - $allnames = get_all_user_name_fields(true, 'u'); - } + $allnames = fields::for_name()->get_sql('u', false, '', '', false)->selects; $additionalwhere = ''; @@ -1217,7 +1195,7 @@ function moodleoverflow_print_post( $multiplemarks = false, ?stdClass $limitedanswersetting = null ) { - global $USER, $CFG, $OUTPUT, $PAGE, $DB; + global $USER, $CFG, $OUTPUT, $PAGE; // Require the filelib. require_once($CFG->libdir . '/filelib.php'); @@ -1284,11 +1262,7 @@ function moodleoverflow_print_post( // Build the object that represents the posting user. $postinguser = new stdClass(); - if ($CFG->branch >= 311) { - $postinguserfields = \core_user\fields::get_picture_fields(); - } else { - $postinguserfields = explode(',', user_picture::fields()); - } + $postinguserfields = fields::get_picture_fields(); $postinguser = username_load_fields_from_object($postinguser, $post, null, $postinguserfields); // Post was anonymized. @@ -1943,13 +1917,12 @@ function moodleoverflow_add_new_post($post) { * Deletes a discussion and handles all associated cleanups. * * @param object $discussion The discussion object - * @param object $course The course object * @param object $cm * @param object $moodleoverflow The moodleoverflow object * * @return bool Whether the deletion was successful. */ -function moodleoverflow_delete_discussion($discussion, $course, $cm, $moodleoverflow) { +function moodleoverflow_delete_discussion($discussion, $cm, $moodleoverflow) { global $DB; // Initiate a pointer. diff --git a/mod_form.php b/mod_form.php index 106c422e98..80a32632de 100644 --- a/mod_form.php +++ b/mod_form.php @@ -71,11 +71,7 @@ public function definition() { $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); // Adding the standard "intro" and "introformat" fields. - if ($CFG->branch >= 29) { - $this->standard_intro_elements(); - } else { - $this->add_intro_editor(); - } + $this->standard_intro_elements(); $currentsetting = $this->current && property_exists($this->current, 'anonymous') ? $this->current->anonymous : 0; $possiblesettings = [ @@ -174,7 +170,7 @@ public function definition() { $mform->addElement( 'header', 'gradeheading', - $CFG->branch >= 311 ? get_string('gradenoun') : get_string('grade') + get_string('gradenoun') ); $mform->addElement('text', 'grademaxgrade', get_string('modgrademaxgrade', 'grades')); diff --git a/templates/discussions.mustache b/templates/discussions.mustache index 11d78abe75..3a5c48b376 100644 --- a/templates/discussions.mustache +++ b/templates/discussions.mustache @@ -37,7 +37,7 @@ {{#discussions}}
-
+
{{> mod_moodleoverflow/postvoting }}
@@ -77,9 +77,9 @@
- {{{ subjecttext }}} + {{{ subjecttext }}} -
+ \ No newline at end of file diff --git a/tests/behat/behat_mod_moodleoverflow.php b/tests/behat/behat_mod_moodleoverflow.php index 69305a4903..bfdffc7c0e 100644 --- a/tests/behat/behat_mod_moodleoverflow.php +++ b/tests/behat/behat_mod_moodleoverflow.php @@ -131,7 +131,7 @@ public function i_add_a_moodleoverflow_discussion_with_posts_from_different_user } /** - * The admins adds a moodleoverflow discussions with a tracking type. Used in the track_read_posts_feature. + * The admin adds a moodleoverflow discussions with a tracking type. Used in the track_read_posts_feature. * * @Given /^The admin posts "(?P[^"]*)" in "(?P[^"]*)" with tracking type "(?P[^"]*)"$/ * @@ -153,6 +153,24 @@ public function admin_adds_moodleoverflow_with_tracking_type(string $subject, st ])); } + /** + * The admin adds a moodleoverflow discussion. + * + * @Given /^The admin posts "(?P[^"]*)" in "(?P[^"]*)"$/ + * + * @param string $subject + * @param string $name + * @return void + */ + public function admin_adds_discussion(string $subject, string $name): void { + $this->execute('behat_auth::i_log_in_as', 'admin'); + $this->execute('behat_navigation::i_am_on_course_homepage', 'Course 1'); + $this->i_add_a_moodleoverflow_discussion_to_moodleoverflow_with($name, new TableNode([ + ['Subject', $subject], + ['Message', 'Test post message'], + ])); + } + /** * Logs in as a user and navigates in a course to dedicated moodleoverflow discussion. * @@ -225,30 +243,10 @@ public function i_comment_moodleoverflow_post_with_message(string $postmessage, * @param TableNode $data */ public function i_add_moodleoverflow_to_course_and_fill_form(string $courseshortname, string $sectionnumber, TableNode $data) { - global $CFG; - - if ($CFG->branch >= 404) { - $this->execute( - "behat_course::i_add_to_course_section_and_i_fill_the_form_with", - [$this->escape('moodleoverflow'), $this->escape($courseshortname), $this->escape($sectionnumber), $data] - ); - } else { - // This is the code from the deprecated behat function "i_add_to_section_and_i_fill_the_form_with". - // Add activity to section. - $this->execute( - "behat_course::i_add_to_section", - [$this->escape('moodleoverflow'), $this->escape($sectionnumber)] - ); - - // Wait to be redirected. - $this->execute('behat_general::wait_until_the_page_is_ready'); - - // Set form fields. - $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data); - - // Save course settings. - $this->execute("behat_forms::press_button", get_string('savechangesandreturntocourse')); - } + $this->execute( + "behat_course::i_add_to_course_section_and_i_fill_the_form_with", + [$this->escape('moodleoverflow'), $this->escape($courseshortname), $this->escape($sectionnumber), $data] + ); } /** diff --git a/tests/behat/track_read_posts.feature b/tests/behat/track_read_posts.feature index c9a4261147..8b495d7169 100644 --- a/tests/behat/track_read_posts.feature +++ b/tests/behat/track_read_posts.feature @@ -1,4 +1,4 @@ -@mod @mod_moodleoverflow +@mod @mod_moodleoverflow @javascript Feature: A teacher can set one of 3 possible options for tracking read moodleoverflow posts In order to ease the moodleoverflow posts follow up As a user @@ -67,3 +67,17 @@ Feature: A teacher can set one of 3 possible options for tracking read moodleove And I click on "1" "link" in the "Test post subject" moodleoverflow discussion card And I am on "Course 1" course homepage And I should not see "1 unread post" + + Scenario: Marking all unread posts as read. + Given the following config values are set as admin: + | allowforcedreadtracking | 1 | moodleoverflow | + And The admin posts "Test post subject" in "Test moodleoverflow name" with tracking type "2" + And The admin posts "Test post subject 2" in "Test moodleoverflow name" + And I log in as "student1" + And I am on "Course 1" course homepage + And I should see "2 unread post" + And I follow "Test moodleoverflow name" + When I click on "Mark all posts as read" "link" + And I am on "Course 1" course homepage + Then I should not see "2 unread post" + diff --git a/tests/discussion_test.php b/tests/discussion_test.php index b4a2efac8e..2b9dabeec0 100644 --- a/tests/discussion_test.php +++ b/tests/discussion_test.php @@ -87,7 +87,7 @@ public function test_create_discussion(): void { $prepost->message = 'a message'; $prepost->messageformat = 1; $prepost->reviewed = 0; - $prepost->formattachments = ''; + $prepost->formattachments = null; $prepost->modulecontext = $this->modulecontext; // Log in as the teacher. diff --git a/tests/generator/lib.php b/tests/generator/lib.php index 0e9e499140..57ea194432 100644 --- a/tests/generator/lib.php +++ b/tests/generator/lib.php @@ -64,28 +64,15 @@ public function reset() { * @return stdClass */ public function create_instance($record = null, ?array $options = null) { - - // Transform the record. - $record = (object) (array) $record; - - if (!isset($record->name)) { - $record->name = 'Test MO Instance'; - } - if (!isset($record->intro)) { - $record->intro = 'Test Intro'; - } - if (!isset($record->introformat)) { - $record->introformat = 1; - } - if (!isset($record->timecreated)) { - $record->timecreated = time(); - } - if (!isset($record->timemodified)) { - $record->timemodified = time(); - } - if (!isset($record->forcesubscribe)) { - $record->forcesubscribe = MOODLEOVERFLOW_CHOOSESUBSCRIBE; - } + // Transform the record and set default values if not provided. + $record = (object) array_merge([ + 'name' => 'Test MO Instance', + 'intro' => 'Test Intro', + 'introformat' => 1, + 'timecreated' => time(), + 'timemodified' => time(), + 'forcesubscribe' => MOODLEOVERFLOW_CHOOSESUBSCRIBE, + ], (array) $record); return parent::create_instance($record, (array) $options); } @@ -104,50 +91,32 @@ public function create_discussion($record = null, $forum = null) { // Increment the discussion count. $this->discussioncount++; - - // Create the record. $record = (array) $record; - - // Check needed submitted values. - if (!isset($record['course'])) { - throw new coding_exception('course must be present in phpunit_util:create_discussion() $record'); - } - if (!isset($record['moodleoverflow'])) { - throw new coding_exception('moodleoverflow must be present in phpunit_util:create_discussion() $record'); - } - if (!isset($record['userid'])) { - throw new coding_exception('userid must be present in phpunit_util:create_discussion() $record'); + // Ensure required fields are set. + foreach (['course', 'moodleoverflow', 'userid'] as $field) { + if (empty($record[$field])) { + throw new coding_exception("$field must be present in phpunit_util:create_discussion() $record"); + } } // Set default values. - if (!isset($record['name'])) { - $record['name'] = 'Discussion ' . $this->discussioncount; - } - if (!isset($record['message'])) { - $record['message'] = html_writer::tag('p', 'Message for discussion ' . $this->discussioncount); - } - if (!isset($record['messageformat'])) { - $record['messageformat'] = editors_get_preferred_format(); - } - if (!isset($record['timestart'])) { - $record['timestart'] = "0"; - } - if (!isset($record['timeend'])) { - $record['timeend'] = "0"; - } - if (isset($record['mailed'])) { - $mailed = $record['mailed']; - } - if (isset($record['timemodified'])) { - $timemodified = $record['timemodified']; - } - $record['attachments'] = null; - - // Transform the array into an object. + $record = array_merge([ + 'name' => 'Discussion ' . $this->discussioncount, + 'message' => html_writer::tag('p', 'Message for discussion ' . $this->discussioncount), + 'messageformat' => editors_get_preferred_format(), + 'timestart' => "0", + 'timeend' => "0", + 'attachments' => null, + 'draftideditor' => -1, + ], (array) $record); + + // Extract optional fields. + $mailed = $record['mailed'] ?? null; + $timemodified = $record['timemodified'] ?? null; + + // Convert the record to an object. $record = (object) $record; - $record->draftideditor = -1; - // Get the module context. $cm = get_coursemodule_from_instance('moodleoverflow', $forum->id); @@ -188,40 +157,29 @@ public function create_discussion($record = null, $forum = null) { */ public function create_post($record = null, $straighttodb = true) { global $DB; - // Increment the forum post count. + + // Increment the forum post count and set the current time. $this->postcount++; - // Variable to store time. $time = time() + $this->postcount; - $record = (array) $record; - if (!isset($record['discussion'])) { + + // Ensure required fields are set and provide default values. + $record = (object) array_merge([ + 'parent' => 0, + 'message' => html_writer::tag('p', 'Forum message post ' . $this->postcount), + 'created' => $time, + 'modified' => $time, + 'mailed' => 0, + 'messageformat' => 0, + 'attachment' => "", + ], (array) $record); + + if (empty($record->discussion)) { throw new coding_exception('discussion must be present in phpunit_util::create_post() $record'); } - if (!isset($record['userid'])) { + if (empty($record->userid)) { throw new coding_exception('userid must be present in phpunit_util::create_post() $record'); } - if (!isset($record['parent'])) { - $record['parent'] = 0; - } - if (!isset($record['message'])) { - $record['message'] = html_writer::tag('p', 'Forum message post ' . $this->postcount); - } - if (!isset($record['created'])) { - $record['created'] = $time; - } - if (!isset($record['modified'])) { - $record['modified'] = $time; - } - if (!isset($record['mailed'])) { - $record['mailed'] = 0; - } - if (!isset($record['messageformat'])) { - $record['messageformat'] = 0; - } - if (!isset($record['attachment'])) { - $record['attachment'] = ""; - } - $record = (object) $record; // Add the post. if ($straighttodb) { $record->id = $DB->insert_record('moodleoverflow_posts', $record); @@ -229,6 +187,7 @@ public function create_post($record = null, $straighttodb = true) { $record->draftideditor = -1; $record->id = moodleoverflow_add_new_post($record); } + // Update the last post. moodleoverflow_discussion_update_last_post($record->discussion); @@ -246,31 +205,21 @@ public function create_rating($record = null) { global $DB; $time = time(); - $record = (array) $record; - if (!isset($record['moodleoverflowid'])) { - throw new coding_exception('moodleoverflowid must be present in phpunit_util::create_rating() $record'); - } - if (!isset($record['discussionid'])) { - throw new coding_exception('discussionid must be present in phpunit_util::create_rating() $record'); - } - if (!isset($record['userid'])) { - throw new coding_exception('userid must be present in phpunit_util::create_rating() $record'); - } - if (!isset($record['postid'])) { - throw new coding_exception('postid must be present in phpunit_util::create_rating() $record'); - } - if (!isset($record['rating'])) { - $record['rating'] = 1; - } - if (!isset($record['firstrated'])) { - $record['firstrated'] = $time; - } - if (!isset($record['lastchanged'])) { - $record['lastchanged'] = $time; + $record = array_merge([ + 'rating' => 1, + 'firstrated' => $time, + 'lastchanged' => $time, + ], (array) $record); + + // Ensure required fields are set. + foreach (['moodleoverflowid', 'discussionid', 'userid', 'postid'] as $field) { + if (empty($record[$field])) { + throw new coding_exception("$field must be present in phpunit_util::create_rating() \$record"); + } } $record = (object) $record; - // Add the post. + // Add the rating. $record->id = $DB->insert_record('moodleoverflow_ratings', $record); return $record; diff --git a/tests/notification_mail_test.php b/tests/notification_mail_test.php index d422819b19..0f82986e13 100644 --- a/tests/notification_mail_test.php +++ b/tests/notification_mail_test.php @@ -81,8 +81,7 @@ public function tearDown(): void { * @covers \mod_moodleoverflow\task\send_mails */ public function test_sortorder(): void { - global $DB; - $result = $this->helper_run_task(); + $this->helper_run_task(); $this->assertTrue(true); /* LEARNWEB-TODO: Add tests. A simple test coverage of the notification mails are in review_test.php for now. They need to be removed from there and added here (+extending test cases). */ @@ -99,7 +98,6 @@ public function test_sortorder(): void { * - a student in each course. */ private function helper_course_set_up(): void { - global $DB; $datagenerator = $this->getDataGenerator(); $plugingenerator = $datagenerator->get_plugin_generator('mod_moodleoverflow'); $this->testdata->mailsink = $this->redirectEmails(); diff --git a/tests/privacy_provider_test.php b/tests/privacy_provider_test.php index 28f91775e5..d4f8174c43 100644 --- a/tests/privacy_provider_test.php +++ b/tests/privacy_provider_test.php @@ -23,10 +23,6 @@ */ namespace mod_moodleoverflow; -defined('MOODLE_INTERNAL') || die(); - -global $CFG; - use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\approved_userlist; use core_privacy\local\request\userlist; diff --git a/tests/ratings_test.php b/tests/ratings_test.php index dc93fad9fa..9040f9e067 100644 --- a/tests/ratings_test.php +++ b/tests/ratings_test.php @@ -22,7 +22,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace mod_moodleoverflow; -use mod_moodleoverflow\ratings; use stdClass; defined('MOODLE_INTERNAL') || die(); @@ -39,27 +38,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class ratings_test extends \advanced_testcase { - /** @var stdClass test course */ - private $course; - - /** @var stdClass coursemodule */ - private $coursemodule; - - /** @var stdClass test moodleoverflow */ - private $moodleoverflow; - - /** @var stdClass test teacher */ - private $teacher; - - /** @var stdClass test user */ - private $user1; - - /** @var stdClass another test user */ - private $user2; - - /** @var stdClass a discussion */ - private $discussion; - /** @var stdClass a post from the teacher*/ private $post; @@ -81,9 +59,6 @@ final class ratings_test extends \advanced_testcase { /** @var stdClass answer from user 2 */ private $answer6; - /** @var \mod_moodleoverflow_generator $generator */ - private $generator; - /** * Test setUp. */ diff --git a/tests/review_test.php b/tests/review_test.php index be2e381162..c04cb7294d 100644 --- a/tests/review_test.php +++ b/tests/review_test.php @@ -24,7 +24,6 @@ namespace mod_moodleoverflow; -use mod_moodleoverflow\manager\mail_manager; use mod_moodleoverflow\task\send_mails; use mod_moodleoverflow\task\send_review_mails; @@ -258,15 +257,10 @@ private function run_send_notification_mails() { * @param object|array $actual */ private function assert_matches_properties($expected, $actual) { - global $CFG; $expected = (array)$expected; $actual = (object)$actual; foreach ($expected as $key => $value) { - if ($CFG->branch >= 404) { - $this->assertObjectHasProperty($key, $actual, "Failed asserting that attribute '$key' exists."); - } else { - $this->assertObjectHasAttribute($key, $actual, "Failed asserting that attribute '$key' exists."); - } + $this->assertObjectHasProperty($key, $actual, "Failed asserting that attribute '$key' exists."); $this->assertEquals($value, $actual->$key, "Failed asserting that \$obj->$key '" . $actual->$key . "' equals '$value'"); } } diff --git a/tests/userstats_test.php b/tests/userstats_test.php index 4107194693..890beca0cc 100644 --- a/tests/userstats_test.php +++ b/tests/userstats_test.php @@ -232,7 +232,6 @@ public function test_total_anonymous(): void { // Get the current userstats to compare later. $olduserstats = $this->create_statstable(); - $oldupvotesuser1 = $this->get_specific_userstats($olduserstats, $this->user1, 'receivedupvotes'); $oldactivityuser1 = $this->get_specific_userstats($olduserstats, $this->user1, 'forumactivity'); $oldupvotesuser2 = $this->get_specific_userstats($olduserstats, $this->user2, 'receivedupvotes'); diff --git a/version.php b/version.php index 4121f57dca..32e56d7e3c 100644 --- a/version.php +++ b/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2025111200; -$plugin->requires = 2022112819; // Require Moodle 4.1. +$plugin->version = 2025112700; +$plugin->requires = 2024100700.00; // Require Moodle 4.5. $plugin->supported = [405, 501]; $plugin->component = 'mod_moodleoverflow'; -$plugin->maturity = MATURITY_ALPHA; +$plugin->maturity = MATURITY_STABLE; $plugin->release = 'v5.1-r1';