From 402ed8cf32beba8a9568e5f3e323408edb982e6b Mon Sep 17 00:00:00 2001 From: tamaro Date: Wed, 30 Jul 2025 17:50:55 +0200 Subject: [PATCH 01/12] replace with core helpicon renderer --- classes/output/helpicon.php | 72 ------------------------------ classes/tables/userstats_table.php | 23 ++-------- lang/en/moodleoverflow.php | 25 ++++++----- locallib.php | 34 +++++++------- 4 files changed, 34 insertions(+), 120 deletions(-) delete mode 100644 classes/output/helpicon.php diff --git a/classes/output/helpicon.php b/classes/output/helpicon.php deleted file mode 100644 index b1867f850f..0000000000 --- a/classes/output/helpicon.php +++ /dev/null @@ -1,72 +0,0 @@ -. - -/** - * Use of the Helpicon from Moodle core. - * @package mod_moodleoverflow - * @copyright 2023 Tamaro Walter - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_moodleoverflow\output; - -/** - * Builds a Helpicon, that shows a String when hovering over it. - * @package mod_moodleoverflow - * @copyright 2023 Tamaro Walter - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class helpicon { - - /** @var object The Helpicon*/ - private $helpobject; - - /** - * Builds a Helpicon and stores it in helpobject. - * - * @param string $htmlclass The classname in which the icon will be. - * @param string $content A string that shows the information that the icon has. - */ - public function __construct($htmlclass, $content) { - global $CFG; - $iconurl = $CFG->wwwroot . '/pix/a/help.svg'; - $iconstyle = ['style' => - 'max-width: 20px; max-height: 20px; margin: 0; padding: 0; box-sizing: content-box; margin-right: .5rem;']; - $icon = \html_writer::img($iconurl, $content, $iconstyle); - - $class = $htmlclass; - $iconattributes = ['role' => 'button', - 'style' => 'display: inline;', - 'data-container' => 'body', - 'data-toggle' => 'popover', - 'data-placement' => 'right', - 'data-action' => 'showhelpicon', - 'data-html' => 'true', - 'data-trigger' => 'focus', - 'tabindex' => '0', - 'data-content' => '

' . $content . '

', ]; - $this->helpobject = \html_writer::span($icon, $class, $iconattributes); - } - - /** - * Returns the Helpicon, so that it can be used. - * - * @return object The Helpicon - */ - public function get_helpicon() { - return $this->helpobject; - } -} diff --git a/classes/tables/userstats_table.php b/classes/tables/userstats_table.php index dc2db1e610..4e4bd348a0 100644 --- a/classes/tables/userstats_table.php +++ b/classes/tables/userstats_table.php @@ -30,7 +30,6 @@ require_once($CFG->dirroot . '/mod/moodleoverflow/lib.php'); require_once($CFG->dirroot . '/mod/moodleoverflow/locallib.php'); require_once($CFG->libdir . '/tablelib.php'); -use mod_moodleoverflow\output\helpicon; /** * Table listing all user statistics of a course @@ -50,9 +49,6 @@ class userstats_table extends \flexible_table { /** @var array table that will have objects with every user and his statistics. */ private $userstatsdata = []; - /** @var \stdClass Help icon for amountofactivity-column.*/ - private $helpactivity; - /** * Constructor for workflow_table. * @@ -62,13 +58,13 @@ class userstats_table extends \flexible_table { * @param string $url The url of the table */ public function __construct($uniqueid, $courseid, $moodleoverflow, $url) { - global $PAGE; + global $PAGE, $OUTPUT; parent::__construct($uniqueid); $PAGE->requires->js_call_amd('mod_moodleoverflow/activityhelp', 'init'); $this->courseid = $courseid; $this->moodleoverflowid = $moodleoverflow; - $this->set_helpactivity(); + $helpactivity = $OUTPUT->help_icon('helpamountofactivity', 'moodleoverflow'); $this->set_attribute('class', 'moodleoverflow-statistics-table'); $this->set_attribute('id', $uniqueid); @@ -78,8 +74,8 @@ public function __construct($uniqueid, $courseid, $moodleoverflow, $url) { $this->define_headers([get_string('fullnameuser'), get_string('userstatsupvotes', 'moodleoverflow'), get_string('userstatsdownvotes', 'moodleoverflow'), - (get_string('userstatsforumactivity', 'moodleoverflow') . $this->helpactivity->object), - (get_string('userstatscourseactivity', 'moodleoverflow') . $this->helpactivity->object), + (get_string('userstatsforumactivity', 'moodleoverflow') . $helpactivity), + (get_string('userstatscourseactivity', 'moodleoverflow') . $helpactivity), get_string('userstatsforumreputation', 'moodleoverflow'), get_string('userstatscoursereputation', 'moodleoverflow'), ]); $this->get_table_data(); @@ -148,17 +144,6 @@ public function get_usertable() { return $this->userstatsdata; } - /** - * Setup the help icon for amount of activity - */ - public function set_helpactivity() { - $htmlclass = 'helpactivityclass btn btn-link'; - $content = get_string('helpamountofactivity', 'moodleoverflow'); - $helpobject = new helpicon($htmlclass, $content); - $this->helpactivity = new \stdClass(); - $this->helpactivity->object = $helpobject->get_helpicon(); - } - // Functions that show the data. /** diff --git a/lang/en/moodleoverflow.php b/lang/en/moodleoverflow.php index dd3cbdd861..3d588d299b 100644 --- a/lang/en/moodleoverflow.php +++ b/lang/en/moodleoverflow.php @@ -167,7 +167,8 @@ $string['grademaxgradeerror'] = 'Maximum grade must be a positive integer different than 0'; $string['gradesreport'] = 'Grades report'; $string['gradesupdated'] = 'Grades updated'; -$string['helpamountofactivity'] = 'Each activity like writing a post, starting a discussion or giving a rating gives 1 point'; +$string['helpamountofactivity_help'] = 'Each activity like writing a post, starting a discussion or giving a rating gives 1 point'; +$string['helpamountofactivity'] = 'Help icon for activity'; $string['hiddenmoodleoverflowpost'] = 'Hidden forum post'; $string['invaliddiscussionid'] = 'Discussion ID was incorrect'; $string['invalidforcesubscribe'] = 'Invalid force subscription mode'; @@ -177,20 +178,24 @@ $string['invalidratingid'] = 'The submitted rating is neither an upvote nor a downvote.'; $string['jump_to_next_post_needing_review'] = 'Jump to next post needing to be reviewed.'; $string['la_endtime'] = 'Time at which students can no longer answer'; -$string['la_endtime_help'] = 'Students can not answer to qustions after the set up date'; +$string['la_endtime_help'] = 'Students can not answer questions after the set up date'; $string['la_endtime_ruleerror'] = 'End time must be in the future'; +$string['la_heading'] = 'Limited Answer Mode'; +$string['la_helpicon_teacher'] = 'This can be changed in the settings of the Moodleoverflow.'; +$string['la_info_endtime'] = 'Posts can not be answered after {$a->limitedanswerdate}.'; +$string['la_info_start'] = 'This Moodleoverflow is in a limited answer mode.'; +$string['la_info_starttime'] = 'Posts can not be answered until {$a->limitedanswerdate}.'; $string['la_sequence_error'] = 'The end time must be after the start time'; $string['la_starttime'] = 'Time at which students can start to answer'; -$string['la_starttime_help'] = 'Students can not answer to questions until the set up date'; +$string['la_starttime_help'] = 'Students can start to answer questions after the set up date'; $string['la_starttime_ruleerror'] = 'Start time must be in the future'; +$string['la_student_helpicon'] = "limited answer help icon"; +$string['la_student_helpicon_help'] = "This moodleoveroverflow is currently in a restricted mode. You can answer as soon as the teacher allows it."; +$string['la_teacher_helpicon'] = "limited answer help icon"; +$string['la_teacher_helpicon_help'] = "This moodleoverflow is currently in a restricted mode. This can be changed in the settings of this acticity."; +$string['la_warning_answers'] = 'There are already answered posts in this Moodleoverflow.'; +$string['la_warning_conclusion'] = 'You can only set a time until students are able to answer'; $string['lastpost'] = 'Last post'; -$string['limitedanswer_helpicon_teacher'] = 'This can be changed in the settings of the Moodleoverflow.'; -$string['limitedanswer_info_endtime'] = 'Posts can not be answered after {$a->limitedanswerdate}.'; -$string['limitedanswer_info_start'] = 'This Moodleoverflow is in a limited answer mode.'; -$string['limitedanswer_info_starttime'] = 'Posts can not be answered until {$a->limitedanswerdate}.'; -$string['limitedanswerheading'] = 'Limited Answer Mode'; -$string['limitedanswerwarning_answers'] = 'There are already answered posts in this Moodleoverflow.'; -$string['limitedanswerwarning_conclusion'] = 'You can only set a time until students are able to answer'; $string['mailindexlink'] = 'Change your forum preferences: {$a}'; $string['manydiscussions'] = 'Discussions per page'; $string['markallread'] = 'Mark all posts in this discussion as read'; diff --git a/locallib.php b/locallib.php index 79fde129e7..f049731883 100644 --- a/locallib.php +++ b/locallib.php @@ -27,7 +27,6 @@ use mod_moodleoverflow\anonymous; use mod_moodleoverflow\capabilities; use mod_moodleoverflow\event\post_deleted; -use mod_moodleoverflow\output\helpicon; use mod_moodleoverflow\ratings; use mod_moodleoverflow\readtracking; use mod_moodleoverflow\review; @@ -1327,21 +1326,21 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co // Check if limitedanswertime is on. $settingexist = $limitedanswersetting->la_starttime != 0 || $limitedanswersetting->la_endtime != 0; if ($settingexist) { - $infolimited = $limitedanswersetting->la_starttime ? " " . get_string('limitedanswer_info_starttime', + $infolimited = $limitedanswersetting->la_starttime ? " " . get_string('la_info_starttime', 'moodleoverflow', ['limitedanswerdate' => date('d.m.Y H:i', $limitedanswersetting->la_starttime)]) : ''; - $infolimited .= $limitedanswersetting->la_endtime ? " " . get_string('limitedanswer_info_endtime', 'moodleoverflow', + $infolimited .= $limitedanswersetting->la_endtime ? " " . get_string('la_info_endtime', 'moodleoverflow', ['limitedanswerdate' => date('d.m.Y H:i', $limitedanswersetting->la_endtime)]) : ''; echo html_writer::div($infolimited, 'alert alert-warning', ['role' => 'alert']); } if (is_currently_time_limited($limitedanswersetting)) { if (!has_capability('mod/moodleoverflow:addinstance', $modulecontext)) { // In case the user can not change the limited answer time he/she can not answer. - render_limited_answer('text-muted', $commands, $infolimited, 'student', $str->replyfirst); + render_limited_answer('text-muted', $commands, 'student', $str->replyfirst); } else { // The user is a teacher. $replyurl = new moodle_url('/mod/moodleoverflow/post.php#mformmoodleoverflow', ['reply' => $post->id]); $answerbutton = html_writer::link($replyurl, $str->replyfirst, ['class' => 'onlyifreviewed answerbutton']); - render_limited_answer('', $commands, $infolimited, 'teacher', $answerbutton); + render_limited_answer('', $commands, 'teacher', $answerbutton); } } else { $replyurl = new moodle_url('/mod/moodleoverflow/post.php#mformmoodleoverflow', ['reply' => $post->id]); @@ -1537,30 +1536,27 @@ function is_currently_time_limited($limitedanswersetting): bool { /** * Renders the answer action in a post. - * @param String $htmlattributes additional attributes passed to the html class and the command (either 'text-muted' or empty). + * @param string $htmlattributes additional attributes passed to the html class and the command (either 'text-muted' or empty). * @param array $commands array of actions available to the user in a post. - * @param String $infolimited information about the limited answer setting. - * @param String $role either 'student' or 'teacher'. - * @param String $helpstring content for the tag specifing the helpicon for the answer button. + * @param string $role either 'student' or 'teacher'. + * @param string $helpstring content for the tag specifing the helpicon for the answer button. * @return void * @throws coding_exception */ -function render_limited_answer($htmlattributes, &$commands, $infolimited, $role, $helpstring) { +function render_limited_answer(string $htmlattributes, array &$commands, string $role, string $helpstring): void { + global $OUTPUT; $limitedanswerattributes = ['class' => 'onlyifreviewed ' . $htmlattributes]; - $htmlclass = 'onlyifreviewed helpicon ' . $htmlattributes; - $content = get_string('limitedanswer_info_start', 'moodleoverflow'); - $content .= $infolimited; - $htmlattributes == '' ? $content .= " " . get_string('limitedanswer_helpicon_teacher', 'moodleoverflow') : $content .= ''; - $helpobject = new helpicon($htmlclass, $content); - $helpicon = $helpobject->get_helpicon(); + $helpicon = match ($htmlattributes) { + 'text-muted' => $OUTPUT->help_icon('la_student_helpicon', 'moodleoverflow'), + '' => $OUTPUT->help_icon('la_teacher_helpicon', 'moodleoverflow'), + }; + // Build a html span that has the answer button and the help icon. $limitedanswerobject = html_writer::tag('span', $helpstring . ' ' . $helpicon); // Save the span in the commands with an extra value. - $commands[] = ['text' => $limitedanswerobject, - 'attributes' => $limitedanswerattributes, - 'limitedanswer' => $role, ]; + $commands[] = ['text' => $limitedanswerobject, 'attributes' => $limitedanswerattributes, 'limitedanswer' => $role]; } /** From 40652ce6bcf047acb3421d2dde23b8615795f882 Mon Sep 17 00:00:00 2001 From: tamaro Date: Wed, 30 Jul 2025 17:50:55 +0200 Subject: [PATCH 02/12] replace with core helpicon renderer --- classes/output/helpicon.php | 72 ------------------------------ classes/tables/userstats_table.php | 23 ++-------- lang/en/moodleoverflow.php | 25 ++++++----- locallib.php | 34 +++++++------- 4 files changed, 34 insertions(+), 120 deletions(-) delete mode 100644 classes/output/helpicon.php diff --git a/classes/output/helpicon.php b/classes/output/helpicon.php deleted file mode 100644 index b1867f850f..0000000000 --- a/classes/output/helpicon.php +++ /dev/null @@ -1,72 +0,0 @@ -. - -/** - * Use of the Helpicon from Moodle core. - * @package mod_moodleoverflow - * @copyright 2023 Tamaro Walter - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_moodleoverflow\output; - -/** - * Builds a Helpicon, that shows a String when hovering over it. - * @package mod_moodleoverflow - * @copyright 2023 Tamaro Walter - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class helpicon { - - /** @var object The Helpicon*/ - private $helpobject; - - /** - * Builds a Helpicon and stores it in helpobject. - * - * @param string $htmlclass The classname in which the icon will be. - * @param string $content A string that shows the information that the icon has. - */ - public function __construct($htmlclass, $content) { - global $CFG; - $iconurl = $CFG->wwwroot . '/pix/a/help.svg'; - $iconstyle = ['style' => - 'max-width: 20px; max-height: 20px; margin: 0; padding: 0; box-sizing: content-box; margin-right: .5rem;']; - $icon = \html_writer::img($iconurl, $content, $iconstyle); - - $class = $htmlclass; - $iconattributes = ['role' => 'button', - 'style' => 'display: inline;', - 'data-container' => 'body', - 'data-toggle' => 'popover', - 'data-placement' => 'right', - 'data-action' => 'showhelpicon', - 'data-html' => 'true', - 'data-trigger' => 'focus', - 'tabindex' => '0', - 'data-content' => '

' . $content . '

', ]; - $this->helpobject = \html_writer::span($icon, $class, $iconattributes); - } - - /** - * Returns the Helpicon, so that it can be used. - * - * @return object The Helpicon - */ - public function get_helpicon() { - return $this->helpobject; - } -} diff --git a/classes/tables/userstats_table.php b/classes/tables/userstats_table.php index dc2db1e610..4e4bd348a0 100644 --- a/classes/tables/userstats_table.php +++ b/classes/tables/userstats_table.php @@ -30,7 +30,6 @@ require_once($CFG->dirroot . '/mod/moodleoverflow/lib.php'); require_once($CFG->dirroot . '/mod/moodleoverflow/locallib.php'); require_once($CFG->libdir . '/tablelib.php'); -use mod_moodleoverflow\output\helpicon; /** * Table listing all user statistics of a course @@ -50,9 +49,6 @@ class userstats_table extends \flexible_table { /** @var array table that will have objects with every user and his statistics. */ private $userstatsdata = []; - /** @var \stdClass Help icon for amountofactivity-column.*/ - private $helpactivity; - /** * Constructor for workflow_table. * @@ -62,13 +58,13 @@ class userstats_table extends \flexible_table { * @param string $url The url of the table */ public function __construct($uniqueid, $courseid, $moodleoverflow, $url) { - global $PAGE; + global $PAGE, $OUTPUT; parent::__construct($uniqueid); $PAGE->requires->js_call_amd('mod_moodleoverflow/activityhelp', 'init'); $this->courseid = $courseid; $this->moodleoverflowid = $moodleoverflow; - $this->set_helpactivity(); + $helpactivity = $OUTPUT->help_icon('helpamountofactivity', 'moodleoverflow'); $this->set_attribute('class', 'moodleoverflow-statistics-table'); $this->set_attribute('id', $uniqueid); @@ -78,8 +74,8 @@ public function __construct($uniqueid, $courseid, $moodleoverflow, $url) { $this->define_headers([get_string('fullnameuser'), get_string('userstatsupvotes', 'moodleoverflow'), get_string('userstatsdownvotes', 'moodleoverflow'), - (get_string('userstatsforumactivity', 'moodleoverflow') . $this->helpactivity->object), - (get_string('userstatscourseactivity', 'moodleoverflow') . $this->helpactivity->object), + (get_string('userstatsforumactivity', 'moodleoverflow') . $helpactivity), + (get_string('userstatscourseactivity', 'moodleoverflow') . $helpactivity), get_string('userstatsforumreputation', 'moodleoverflow'), get_string('userstatscoursereputation', 'moodleoverflow'), ]); $this->get_table_data(); @@ -148,17 +144,6 @@ public function get_usertable() { return $this->userstatsdata; } - /** - * Setup the help icon for amount of activity - */ - public function set_helpactivity() { - $htmlclass = 'helpactivityclass btn btn-link'; - $content = get_string('helpamountofactivity', 'moodleoverflow'); - $helpobject = new helpicon($htmlclass, $content); - $this->helpactivity = new \stdClass(); - $this->helpactivity->object = $helpobject->get_helpicon(); - } - // Functions that show the data. /** diff --git a/lang/en/moodleoverflow.php b/lang/en/moodleoverflow.php index dd3cbdd861..9e2371ec9c 100644 --- a/lang/en/moodleoverflow.php +++ b/lang/en/moodleoverflow.php @@ -167,7 +167,8 @@ $string['grademaxgradeerror'] = 'Maximum grade must be a positive integer different than 0'; $string['gradesreport'] = 'Grades report'; $string['gradesupdated'] = 'Grades updated'; -$string['helpamountofactivity'] = 'Each activity like writing a post, starting a discussion or giving a rating gives 1 point'; +$string['helpamountofactivity'] = 'Help icon for activity'; +$string['helpamountofactivity_help'] = 'Each activity like writing a post, starting a discussion or giving a rating gives 1 point'; $string['hiddenmoodleoverflowpost'] = 'Hidden forum post'; $string['invaliddiscussionid'] = 'Discussion ID was incorrect'; $string['invalidforcesubscribe'] = 'Invalid force subscription mode'; @@ -177,20 +178,24 @@ $string['invalidratingid'] = 'The submitted rating is neither an upvote nor a downvote.'; $string['jump_to_next_post_needing_review'] = 'Jump to next post needing to be reviewed.'; $string['la_endtime'] = 'Time at which students can no longer answer'; -$string['la_endtime_help'] = 'Students can not answer to qustions after the set up date'; +$string['la_endtime_help'] = 'Students can not answer questions after the set up date'; $string['la_endtime_ruleerror'] = 'End time must be in the future'; +$string['la_heading'] = 'Limited Answer Mode'; +$string['la_helpicon_teacher'] = 'This can be changed in the settings of the Moodleoverflow.'; +$string['la_info_endtime'] = 'Posts can not be answered after {$a->limitedanswerdate}.'; +$string['la_info_start'] = 'This Moodleoverflow is in a limited answer mode.'; +$string['la_info_starttime'] = 'Posts can not be answered until {$a->limitedanswerdate}.'; $string['la_sequence_error'] = 'The end time must be after the start time'; $string['la_starttime'] = 'Time at which students can start to answer'; -$string['la_starttime_help'] = 'Students can not answer to questions until the set up date'; +$string['la_starttime_help'] = 'Students can start to answer questions after the set up date'; $string['la_starttime_ruleerror'] = 'Start time must be in the future'; +$string['la_student_helpicon'] = "limited answer help icon"; +$string['la_student_helpicon_help'] = "This moodleoveroverflow is currently in a restricted mode. You can answer as soon as the teacher allows it."; +$string['la_teacher_helpicon'] = "limited answer help icon"; +$string['la_teacher_helpicon_help'] = "This moodleoverflow is currently in a restricted mode. This can be changed in the settings of this acticity."; +$string['la_warning_answers'] = 'There are already answered posts in this Moodleoverflow.'; +$string['la_warning_conclusion'] = 'You can only set a time until students are able to answer'; $string['lastpost'] = 'Last post'; -$string['limitedanswer_helpicon_teacher'] = 'This can be changed in the settings of the Moodleoverflow.'; -$string['limitedanswer_info_endtime'] = 'Posts can not be answered after {$a->limitedanswerdate}.'; -$string['limitedanswer_info_start'] = 'This Moodleoverflow is in a limited answer mode.'; -$string['limitedanswer_info_starttime'] = 'Posts can not be answered until {$a->limitedanswerdate}.'; -$string['limitedanswerheading'] = 'Limited Answer Mode'; -$string['limitedanswerwarning_answers'] = 'There are already answered posts in this Moodleoverflow.'; -$string['limitedanswerwarning_conclusion'] = 'You can only set a time until students are able to answer'; $string['mailindexlink'] = 'Change your forum preferences: {$a}'; $string['manydiscussions'] = 'Discussions per page'; $string['markallread'] = 'Mark all posts in this discussion as read'; diff --git a/locallib.php b/locallib.php index 79fde129e7..f049731883 100644 --- a/locallib.php +++ b/locallib.php @@ -27,7 +27,6 @@ use mod_moodleoverflow\anonymous; use mod_moodleoverflow\capabilities; use mod_moodleoverflow\event\post_deleted; -use mod_moodleoverflow\output\helpicon; use mod_moodleoverflow\ratings; use mod_moodleoverflow\readtracking; use mod_moodleoverflow\review; @@ -1327,21 +1326,21 @@ function moodleoverflow_print_post($post, $discussion, $moodleoverflow, $cm, $co // Check if limitedanswertime is on. $settingexist = $limitedanswersetting->la_starttime != 0 || $limitedanswersetting->la_endtime != 0; if ($settingexist) { - $infolimited = $limitedanswersetting->la_starttime ? " " . get_string('limitedanswer_info_starttime', + $infolimited = $limitedanswersetting->la_starttime ? " " . get_string('la_info_starttime', 'moodleoverflow', ['limitedanswerdate' => date('d.m.Y H:i', $limitedanswersetting->la_starttime)]) : ''; - $infolimited .= $limitedanswersetting->la_endtime ? " " . get_string('limitedanswer_info_endtime', 'moodleoverflow', + $infolimited .= $limitedanswersetting->la_endtime ? " " . get_string('la_info_endtime', 'moodleoverflow', ['limitedanswerdate' => date('d.m.Y H:i', $limitedanswersetting->la_endtime)]) : ''; echo html_writer::div($infolimited, 'alert alert-warning', ['role' => 'alert']); } if (is_currently_time_limited($limitedanswersetting)) { if (!has_capability('mod/moodleoverflow:addinstance', $modulecontext)) { // In case the user can not change the limited answer time he/she can not answer. - render_limited_answer('text-muted', $commands, $infolimited, 'student', $str->replyfirst); + render_limited_answer('text-muted', $commands, 'student', $str->replyfirst); } else { // The user is a teacher. $replyurl = new moodle_url('/mod/moodleoverflow/post.php#mformmoodleoverflow', ['reply' => $post->id]); $answerbutton = html_writer::link($replyurl, $str->replyfirst, ['class' => 'onlyifreviewed answerbutton']); - render_limited_answer('', $commands, $infolimited, 'teacher', $answerbutton); + render_limited_answer('', $commands, 'teacher', $answerbutton); } } else { $replyurl = new moodle_url('/mod/moodleoverflow/post.php#mformmoodleoverflow', ['reply' => $post->id]); @@ -1537,30 +1536,27 @@ function is_currently_time_limited($limitedanswersetting): bool { /** * Renders the answer action in a post. - * @param String $htmlattributes additional attributes passed to the html class and the command (either 'text-muted' or empty). + * @param string $htmlattributes additional attributes passed to the html class and the command (either 'text-muted' or empty). * @param array $commands array of actions available to the user in a post. - * @param String $infolimited information about the limited answer setting. - * @param String $role either 'student' or 'teacher'. - * @param String $helpstring content for the tag specifing the helpicon for the answer button. + * @param string $role either 'student' or 'teacher'. + * @param string $helpstring content for the tag specifing the helpicon for the answer button. * @return void * @throws coding_exception */ -function render_limited_answer($htmlattributes, &$commands, $infolimited, $role, $helpstring) { +function render_limited_answer(string $htmlattributes, array &$commands, string $role, string $helpstring): void { + global $OUTPUT; $limitedanswerattributes = ['class' => 'onlyifreviewed ' . $htmlattributes]; - $htmlclass = 'onlyifreviewed helpicon ' . $htmlattributes; - $content = get_string('limitedanswer_info_start', 'moodleoverflow'); - $content .= $infolimited; - $htmlattributes == '' ? $content .= " " . get_string('limitedanswer_helpicon_teacher', 'moodleoverflow') : $content .= ''; - $helpobject = new helpicon($htmlclass, $content); - $helpicon = $helpobject->get_helpicon(); + $helpicon = match ($htmlattributes) { + 'text-muted' => $OUTPUT->help_icon('la_student_helpicon', 'moodleoverflow'), + '' => $OUTPUT->help_icon('la_teacher_helpicon', 'moodleoverflow'), + }; + // Build a html span that has the answer button and the help icon. $limitedanswerobject = html_writer::tag('span', $helpstring . ' ' . $helpicon); // Save the span in the commands with an extra value. - $commands[] = ['text' => $limitedanswerobject, - 'attributes' => $limitedanswerattributes, - 'limitedanswer' => $role, ]; + $commands[] = ['text' => $limitedanswerobject, 'attributes' => $limitedanswerattributes, 'limitedanswer' => $role]; } /** From a6d56f5b9e10ae42941d9fcdee353441125e5ecd Mon Sep 17 00:00:00 2001 From: tamaro Date: Thu, 31 Jul 2025 12:15:39 +0200 Subject: [PATCH 03/12] delete unnecessary config file --- .github/workflows/config.json | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/workflows/config.json diff --git a/.github/workflows/config.json b/.github/workflows/config.json deleted file mode 100644 index 7a643bc495..0000000000 --- a/.github/workflows/config.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "moodle-plugin-ci": "4.5.7", - "main-moodle": "MOODLE_500_STABLE", - "main-php": "8.3", - "main-db": "pgsql", - "moodle-testmatrix": { - "MOODLE_401_STABLE": { - "php": ["8.0", "8.1"] - }, - "MOODLE_404_STABLE": { - "php": ["8.1", "8.2", "8.3"] - }, - "MOODLE_405_STABLE": { - "php": ["8.1", "8.2", "8.3"], - "db": ["pgsql", "mariadb", "mysqli"] - }, - "MOODLE_500_STABLE": { - "php": ["8.2", "8.3", "8.4"], - "db": ["pgsql", "mariadb", "mysqli"] - } - } -} \ No newline at end of file From e8fbb8651c28a4eef083afb068d4534a8c41799f Mon Sep 17 00:00:00 2001 From: tamaro Date: Thu, 31 Jul 2025 12:16:04 +0200 Subject: [PATCH 04/12] use right language strings --- mod_form.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mod_form.php b/mod_form.php index 987b98f23f..81e5d35b3a 100644 --- a/mod_form.php +++ b/mod_form.php @@ -230,7 +230,7 @@ public function definition() { $mform->setDefault('allowmultiplemarks', 0); // Limited answer options. - $mform->addElement('header', 'limitedanswerheading', get_string('limitedanswerheading', 'moodleoverflow')); + $mform->addElement('header', 'limitedanswerheading', get_string('la_heading', 'moodleoverflow')); $answersfound = false; if (!empty($this->current->id)) { @@ -244,8 +244,8 @@ public function definition() { $answerpostscount = $answerpostscount[array_key_first($answerpostscount)]->answerposts; $answersfound = $answerpostscount > 0; if ($answersfound) { - $warningstring = get_string('limitedanswerwarning_answers', 'moodleoverflow'); - $warningstring .= '
' . get_string('limitedanswerwarning_conclusion', 'moodleoverflow'); + $warningstring = get_string('la_warning_answers', 'moodleoverflow'); + $warningstring .= '
' . get_string('la_warning_conclusion', 'moodleoverflow'); $htmlwarning = html_writer::div($warningstring, 'alert alert-warning', ['role' => 'alert']); $mform->addElement('html', $htmlwarning); } From 1dbecb3d7c9f693319e0e8b7215aedcb1d9cf413 Mon Sep 17 00:00:00 2001 From: tamaro Date: Thu, 31 Jul 2025 15:41:33 +0200 Subject: [PATCH 05/12] use right class types --- classes/discussion/discussion.php | 4 ++-- classes/post/post.php | 14 +++++++------- classes/post/post_control.php | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/classes/discussion/discussion.php b/classes/discussion/discussion.php index 68b2d7a03d..30db7f1c8a 100644 --- a/classes/discussion/discussion.php +++ b/classes/discussion/discussion.php @@ -81,7 +81,7 @@ class discussion { // Not Database-related attributes. - /** @var array an Array of posts that belong to this discussion */ + /** @var post[] an Array of posts that belong to this discussion */ public $posts; /** @var bool a variable for checking if this instance has all its posts */ @@ -126,7 +126,7 @@ public function __construct($id, $course, $moodleoverflow, $name, $firstpost, * Builds a Discussion from a DB record. * * @param object $record Data object. - * @return object discussion instance + * @return discussion discussion instance */ public static function from_record($record) { $id = null; diff --git a/classes/post/post.php b/classes/post/post.php index ae9d122875..daa670ab19 100644 --- a/classes/post/post.php +++ b/classes/post/post.php @@ -103,8 +103,8 @@ class post { /** @var string The subject/title of the Discussion */ public $subject; - /** @var object The discussion where the post is located */ - public $discussionobject; + /** @var discussion The discussion where the post is located */ + public discussion $discussionobject; /** @var object The Moodleoverflow where the post is located*/ public $moodleoverflowobject; @@ -154,9 +154,9 @@ public function __construct($id, $discussion, $parent, $userid, $created, $modif * Builds a Post from a DB record. * Look up database structure for standard values. * @param object $record Data object. - * @return object post instance + * @return post post instance */ - public static function from_record($record) { + public static function from_record($record): post { $id = null; if (object_property_exists($record, 'id') && $record->id) { $id = $record->id; @@ -546,9 +546,9 @@ public function get_moodleoverflow() { /** * Returns the discussion where the post is located. * - * @return object $discussionobject. + * @return discussion $discussionobject. */ - public function get_discussion() { + public function get_discussion(): discussion { global $DB; $this->existence_check(); @@ -631,7 +631,7 @@ public function get_db_object() { public function moodleoverflow_get_post_ratings() { $this->existence_check(); - $discussionid = $this->get_discussion()->id; + $discussionid = $this->get_discussion()->get_id(); $postratings = \mod_moodleoverflow\ratings::moodleoverflow_get_ratings_by_discussion($discussionid, $this->id); $ratingsobject = new \stdClass(); diff --git a/classes/post/post_control.php b/classes/post/post_control.php index c32b0a8e91..d2fb43be86 100644 --- a/classes/post/post_control.php +++ b/classes/post/post_control.php @@ -796,11 +796,11 @@ private function check_moodleoverflow_exists(int $moodleoverflowid): object { /** * Checks if the related discussion exists. * @param int $discussionid - * @return object $discussion + * @return discussion $discussion * @throws dml_exception * @throws moodle_exception */ - private function check_discussion_exists(int $discussionid): object { + private function check_discussion_exists(int $discussionid): discussion { global $DB; if (!$discussionrecord = $DB->get_record('moodleoverflow_discussions', ['id' => $discussionid])) { throw new moodle_exception('invaliddiscussionid', 'moodleoverflow'); @@ -811,11 +811,11 @@ private function check_discussion_exists(int $discussionid): object { /** * Checks if a post exists. * @param int $postid - * @return object $post + * @return post $post * @throws dml_exception * @throws moodle_exception */ - private function check_post_exists(int $postid): object { + private function check_post_exists(int $postid): post { global $DB; if (!$postrecord = $DB->get_record('moodleoverflow_posts', ['id' => $postid])) { throw new moodle_exception('invalidpostid', 'moodleoverflow'); From 400a66e8ffbc11abb8e2ec2f1e78880565b7aff5 Mon Sep 17 00:00:00 2001 From: tamaro Date: Thu, 31 Jul 2025 20:08:58 +0200 Subject: [PATCH 06/12] delete discussion cascading from first post --- classes/discussion/discussion.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/classes/discussion/discussion.php b/classes/discussion/discussion.php index 30db7f1c8a..bbc99a9777 100644 --- a/classes/discussion/discussion.php +++ b/classes/discussion/discussion.php @@ -258,9 +258,8 @@ public function moodleoverflow_delete_discussion($prepost) { $transaction = $DB->start_delegated_transaction(); // Delete every post of this discussion. - foreach ($this->posts as $post) { - $post->moodleoverflow_delete_post(false); - } + $firstpost = $this->posts[$this->firstpost]; + $firstpost->moodleoverflow_delete_post(true); // Delete the read-records for the discussion. readtracking::moodleoverflow_delete_read_records(-1, -1, $this->id); From dc2d3fc9d12f3ea6e744fa475e71e8690f092d55 Mon Sep 17 00:00:00 2001 From: tamaro Date: Thu, 31 Jul 2025 20:09:16 +0200 Subject: [PATCH 07/12] add tests for deletion of posts --- tests/behat/behat_mod_moodleoverflow.php | 136 ++++++++++++++++++++++- tests/behat/delete_post.feature | 30 +++++ 2 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 tests/behat/delete_post.feature diff --git a/tests/behat/behat_mod_moodleoverflow.php b/tests/behat/behat_mod_moodleoverflow.php index 9965c0e557..a3ed3728cc 100644 --- a/tests/behat/behat_mod_moodleoverflow.php +++ b/tests/behat/behat_mod_moodleoverflow.php @@ -41,6 +41,140 @@ */ class behat_mod_moodleoverflow extends behat_base { + + /** + * Build basic background for moodleoverflow tests. + * Builds: + * - A course + * - One teacher and one student, both enrolled in the course + * - A moodleoverflow activity + * - A discussion started by the teacher + * - A reply to the discussion by the student + * + * @Given /^I add a moodleoverflow discussion with posts from different users$/ + * @return void + */ + public function i_add_a_moodleoverflow_discussion_with_posts_from_different_users(): void { + global $DB; + $time = time(); + $starttime = $time - 31556926; + $endtime = $time + 31556926; + + // Create users. + $this->execute('behat_data_generators::the_following_entities_exist', ['users', new TableNode([ + ['username', 'firstname', 'lastname'], + ['teacher1', 'Tamaro', 'Walter'], + ['student1', 'John', 'Smith'], + ])]); + + $teacher = $DB->get_record('user', ['username' => 'teacher1']); + $student = $DB->get_record('user', ['username' => 'student1']); + + // Create course. + $this->execute('behat_data_generators::the_following_entities_exist', ['courses', new TableNode([ + ['fullname', 'shortname', 'category', 'startdate', 'enddate'], + ['Course 1', 'C1', '0', $starttime, $endtime], + ])]); + $course = $DB->get_record('course', ['shortname' => 'C1']); + + // Enroll users. + $this->execute('behat_data_generators::the_following_entities_exist', ['course enrolments', new TableNode([ + ['user', 'course', 'role'], + ['teacher1', 'C1', 'teacher'], + ['student1', 'C1', 'student'], + ])]); + + // Create activity. + $this->execute('behat_data_generators::the_following_entities_exist', ['activities', new TableNode([ + ['activity', 'course', 'name'], + ['moodleoverflow', 'C1', 'Moodleoverflow 1'], + ])]); + $moodleoverflow = $DB->get_record('moodleoverflow', ['name' => 'Moodleoverflow 1']); + + // Create discussion. + $discussionid = $DB->insert_record('moodleoverflow_discussions', ['course' => $course->id, + 'moodleoverflow' => $moodleoverflow->id, 'name' => 'Discussion 1', 'firstpost' => 1, 'userid' => $teacher->id, + 'timemodified' => $time, 'timestart' => $time, 'usermodified' => $teacher->id, + ]); + + // Create teacher's post. + $teacherpostid = $DB->insert_record('moodleoverflow_posts', ['discussion' => $discussionid, + 'moodleoverflow' => $moodleoverflow->id, 'parent' => 0, 'userid' => $teacher->id, 'created' => $time, + 'modified' => $time, 'message' => 'Message from teacher', 'messageformat' => 1, 'attachment' => '', 'mailed' => 1, + 'reviewed' => '1', 'timereviewed' => null, + ]); + + // Update firstpost field in discussion. + $record = $DB->get_record('moodleoverflow_discussions', ['id' => $discussionid]); + $record->firstpost = $teacherpostid; + $DB->update_record('moodleoverflow_discussions', $record); + + // Create student reply. + $DB->insert_record('moodleoverflow_posts', ['discussion' => $discussionid, 'moodleoverflow' => $moodleoverflow->id, + 'parent' => $teacherpostid, 'userid' => $student->id, 'created' => $time, 'modified' => $time, + 'message' => 'Answer from student', 'messageformat' => 1, 'attachment' => '', 'mailed' => 1, 'reviewed' => '1', + 'timereviewed' => null, + ]); + } + + /** + * Logs in as a user and navigates in a course to dedicated moodleoverflow discussion. + * + * @Given /^I navigate as "(?P[^"]*)" to "(?P[^"]*)" "(?P[^"]*)" "(?P[^"]*)"$/ + * @param string $username The username to log in as + * @param string $coursefullname The full name of the course + * @param string $moodleoverflowname The name of the moodleoverflow activity + * @param string $discussionname The name of the discussion + * @return void + * @throws Exception + */ + public function i_navigate_as_user_to_the_discussion(string $username, string $coursefullname, string $moodleoverflowname, + string $discussionname): void { + $this->execute('behat_auth::i_log_in_as', $username); + $this->execute('behat_navigation::i_am_on_course_homepage', $coursefullname); + $this->execute('behat_general::click_link', $this->escape($moodleoverflowname)); + $this->execute('behat_general::click_link', $this->escape($discussionname)); + } + + /** + * Clicks on the delete button of a moodleoverflow post. + * + * @Given /^I try to delete moodleoverflow post "(?P(?:[^"]|\\")*)"$/ + * @param string $postmessage + * @return void + * @throws Exception + */ + public function i_try_to_delete_moodleoverflow_post(string $postmessage): void { + // Find the div containing the post message and click the delete link within it. + $this->execute('behat_general::i_click_on', [ + "//div[contains(@class, 'moodleoverflowpost')][contains(., '" . $this->escape($postmessage) . "')]//a[text()='Delete']", + "xpath_element" + ]); + $this->execute('behat_general::i_click_on', ['Continue', 'button']); + } + + /** + * Clicks on the comment button of a moodleoverflow post + * + * @Given /^I comment "(?P(?:[^"]|\\")*)" with "(?P(?:[^"]|\\")*)"$/ + * @param string $postmessage + * @param string $replymessage + * @return void + * @throws Exception + */ + public function i_comment_moodleoverflow_post_with_message(string $postmessage, string $replymessage): void { + $this->execute('behat_general::i_click_on', [ + "//div[contains(@class, 'moodleoverflowpost')][contains(., '" . $this->escape($postmessage) . "')]//a[text()='Comment']", + "xpath_element" + ]); + $table = new TableNode([ + ['Message', $replymessage], + ]); + $this->execute('behat_forms::i_set_the_following_fields_to_these_values', $table); + $this->execute('behat_forms::press_button', get_string('posttomoodleoverflow', 'moodleoverflow')); + $this->execute('behat_general::i_wait_to_be_redirected'); + } + /** * Adds a new moodleoverflow to the specified course and section. * @@ -104,7 +238,7 @@ public function i_add_a_moodleoverflow_discussion_to_moodleoverflow_with($moodle } /** - * Adds a reply to the specified post of the specified moodleoverflow. + * Adds a reply to the starter post of the specified moodleoverflow. * The step begins from the moodleoverflow's page or from the moodleoverflow's course page. * * @Given /^I reply "(?P(?:[^"]|\\")*)" post diff --git a/tests/behat/delete_post.feature b/tests/behat/delete_post.feature new file mode 100644 index 0000000000..6d90d2a17f --- /dev/null +++ b/tests/behat/delete_post.feature @@ -0,0 +1,30 @@ +@mod @mod_moodleoverflow @javascript @mod_moodleoverflow_delete @javascript +Feature: Teachers and students can delete posts + + Background: + Given I add a moodleoverflow discussion with posts from different users + + Scenario: Teacher deletes the whole discussion + Given I navigate as "teacher1" to "Course 1" "Moodleoverflow 1" "Discussion 1" + And I try to delete moodleoverflow post "Message from teacher" + Then I should not see "Discussion 1" + + Scenario: Teacher only deletes reply post + Given I navigate as "teacher1" to "Course 1" "Moodleoverflow 1" "Discussion 1" + And I try to delete moodleoverflow post "Answer from student" + Then I should see "Discussion 1" + And I should not see "Answer from student" + + Scenario: Student deletes own reply post + Given I navigate as "student1" to "Course 1" "Moodleoverflow 1" "Discussion 1" + And I try to delete moodleoverflow post "Answer from student" + Then I should see "Discussion 1" + And I should not see "Answer from student" + + Scenario: Student tries to delete his post which the teacher already replied + Given I navigate as "teacher1" to "Course 1" "Moodleoverflow 1" "Discussion 1" + And I comment "Answer from student" with "comment from teacher" + And I log out + And I navigate as "student1" to "Course 1" "Moodleoverflow 1" "Discussion 1" + And I try to delete moodleoverflow post "Answer from student" + Then I should see "You are not allowed to delete this post because it already has replies." \ No newline at end of file From d6ac16a480819e0ed298b736b3be2d1a50c5391c Mon Sep 17 00:00:00 2001 From: tamaro Date: Thu, 31 Jul 2025 22:56:13 +0200 Subject: [PATCH 08/12] move draft files to permanent file area for right picture rendering --- classes/post/post.php | 16 +++++++++++++++- classes/post_form.php | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/classes/post/post.php b/classes/post/post.php index daa670ab19..2e14fef937 100644 --- a/classes/post/post.php +++ b/classes/post/post.php @@ -31,6 +31,7 @@ use mod_moodleoverflow\review; use mod_moodleoverflow\readtracking; use mod_moodleoverflow\discussion\discussion; +use mod_moodleoverflow_post_form; use moodle_exception; defined('MOODLE_INTERNAL') || die(); @@ -261,6 +262,13 @@ public function moodleoverflow_add_new_post() { $this->id = $DB->insert_record('moodleoverflow_posts', $this->build_db_object()); $this->moodleoverflow_add_attachment(); + // Save draft files to permanent file area. + $context = \context_module::instance($this->get_coursemodule()->id); + $draftid = file_get_submitted_draft_itemid('introeditor'); + $this->message = file_save_draft_area_files($draftid, $context->id, 'mod_moodleoverflow', 'post', + $this->id, mod_moodleoverflow_post_form::editor_options($context, $this->id), $this->message); + $DB->update_record('moodleoverflow_posts', $this->build_db_object()); + if ($this->reviewed) { // Update the discussion. $DB->set_field('moodleoverflow_discussions', 'timemodified', $this->modified, ['id' => $this->discussion]); @@ -375,10 +383,16 @@ public function moodleoverflow_edit_post($time, $postmessage, $messageformat, $f // Update the attributes. $this->modified = $time; - $this->message = $postmessage; $this->messageformat = $messageformat; $this->formattachments = $formattachments; + // Update the message and save draft files to permanent file area. + $context = \context_module::instance($this->get_coursemodule()->id); + $draftid = file_get_submitted_draft_itemid('introeditor'); + $this->message = file_save_draft_area_files($draftid, $context->id, 'mod_moodleoverflow', 'post', + $this->id, mod_moodleoverflow_post_form::editor_options($context, $this->id), $this->message); + $DB->update_record('moodleoverflow_posts', $this->build_db_object()); + // Update the record in the database. $DB->update_record('moodleoverflow_posts', $this->build_db_object()); diff --git a/classes/post_form.php b/classes/post_form.php index 2e1c66e2b5..58212feee8 100644 --- a/classes/post_form.php +++ b/classes/post_form.php @@ -159,7 +159,7 @@ public static function editor_options(context_module $context, $postid) { 'maxbytes' => $maxbytes, 'trusttext' => true, 'return_types' => FILE_INTERNAL | FILE_EXTERNAL, - 'subdirs' => file_area_contains_subdirs($context, 'mod_forum', 'post', $postid), + 'subdirs' => file_area_contains_subdirs($context, 'mod_moodleoverflow', 'post', $postid), ]; } } From 0804fe1a096ac511118984cf3dca195cdaf9415e Mon Sep 17 00:00:00 2001 From: tamaro Date: Thu, 31 Jul 2025 23:08:29 +0200 Subject: [PATCH 09/12] code cleaning --- tests/behat/behat_mod_moodleoverflow.php | 30 +++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/behat/behat_mod_moodleoverflow.php b/tests/behat/behat_mod_moodleoverflow.php index a3ed3728cc..2d129a760d 100644 --- a/tests/behat/behat_mod_moodleoverflow.php +++ b/tests/behat/behat_mod_moodleoverflow.php @@ -120,20 +120,21 @@ public function i_add_a_moodleoverflow_discussion_with_posts_from_different_user /** * Logs in as a user and navigates in a course to dedicated moodleoverflow discussion. * - * @Given /^I navigate as "(?P[^"]*)" to "(?P[^"]*)" "(?P[^"]*)" "(?P[^"]*)"$/ - * @param string $username The username to log in as - * @param string $coursefullname The full name of the course - * @param string $moodleoverflowname The name of the moodleoverflow activity - * @param string $discussionname The name of the discussion + * @Given /^I navigate as "(?P[^"]*)" to "(?P[^"]*)" "(?P[^"]*)" "(?P[^"]*)"$/ + * + * @param string $user The username to log in as + * @param string $course The full name of the course + * @param string $moodleoverflow The name of the moodleoverflow activity + * @param string $discussion The name of the discussion * @return void * @throws Exception */ - public function i_navigate_as_user_to_the_discussion(string $username, string $coursefullname, string $moodleoverflowname, - string $discussionname): void { - $this->execute('behat_auth::i_log_in_as', $username); - $this->execute('behat_navigation::i_am_on_course_homepage', $coursefullname); - $this->execute('behat_general::click_link', $this->escape($moodleoverflowname)); - $this->execute('behat_general::click_link', $this->escape($discussionname)); + public function i_navigate_as_user_to_the_discussion(string $user, string $course, string $moodleoverflow, + string $discussion): void { + $this->execute('behat_auth::i_log_in_as', $user); + $this->execute('behat_navigation::i_am_on_course_homepage', $course); + $this->execute('behat_general::click_link', $this->escape($moodleoverflow)); + $this->execute('behat_general::click_link', $this->escape($discussion)); } /** @@ -148,7 +149,7 @@ public function i_try_to_delete_moodleoverflow_post(string $postmessage): void { // Find the div containing the post message and click the delete link within it. $this->execute('behat_general::i_click_on', [ "//div[contains(@class, 'moodleoverflowpost')][contains(., '" . $this->escape($postmessage) . "')]//a[text()='Delete']", - "xpath_element" + "xpath_element", ]); $this->execute('behat_general::i_click_on', ['Continue', 'button']); } @@ -164,8 +165,9 @@ public function i_try_to_delete_moodleoverflow_post(string $postmessage): void { */ public function i_comment_moodleoverflow_post_with_message(string $postmessage, string $replymessage): void { $this->execute('behat_general::i_click_on', [ - "//div[contains(@class, 'moodleoverflowpost')][contains(., '" . $this->escape($postmessage) . "')]//a[text()='Comment']", - "xpath_element" + "//div[contains(@class, 'moodleoverflowpost')][contains(., '" . $this->escape($postmessage) . + "')]//a[text()='Comment']", + "xpath_element", ]); $table = new TableNode([ ['Message', $replymessage], From 20c78e98e7b94cafcc9ac9a1804750940fc9701d Mon Sep 17 00:00:00 2001 From: tamaro Date: Thu, 31 Jul 2025 23:59:08 +0200 Subject: [PATCH 10/12] fix failing behat test --- classes/post/post.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/classes/post/post.php b/classes/post/post.php index 2e14fef937..942d5ddbd8 100644 --- a/classes/post/post.php +++ b/classes/post/post.php @@ -260,7 +260,6 @@ public function moodleoverflow_add_new_post() { // Add post to the database. $this->id = $DB->insert_record('moodleoverflow_posts', $this->build_db_object()); - $this->moodleoverflow_add_attachment(); // Save draft files to permanent file area. $context = \context_module::instance($this->get_coursemodule()->id); @@ -269,6 +268,9 @@ public function moodleoverflow_add_new_post() { $this->id, mod_moodleoverflow_post_form::editor_options($context, $this->id), $this->message); $DB->update_record('moodleoverflow_posts', $this->build_db_object()); + // Update the attachments. This happens after the DB update call, as this function changes the DB record as well. + $this->moodleoverflow_add_attachment(); + if ($this->reviewed) { // Update the discussion. $DB->set_field('moodleoverflow_discussions', 'timemodified', $this->modified, ['id' => $this->discussion]); @@ -390,8 +392,7 @@ public function moodleoverflow_edit_post($time, $postmessage, $messageformat, $f $context = \context_module::instance($this->get_coursemodule()->id); $draftid = file_get_submitted_draft_itemid('introeditor'); $this->message = file_save_draft_area_files($draftid, $context->id, 'mod_moodleoverflow', 'post', - $this->id, mod_moodleoverflow_post_form::editor_options($context, $this->id), $this->message); - $DB->update_record('moodleoverflow_posts', $this->build_db_object()); + $this->id, mod_moodleoverflow_post_form::editor_options($context, $this->id), $postmessage); // Update the record in the database. $DB->update_record('moodleoverflow_posts', $this->build_db_object()); From a667dffa31841dfea50707019e9a1a960f9aed48 Mon Sep 17 00:00:00 2001 From: tamaro Date: Fri, 1 Aug 2025 10:51:04 +0200 Subject: [PATCH 11/12] add login for right file saving --- tests/discussion_test.php | 3 +++ tests/post_test.php | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/tests/discussion_test.php b/tests/discussion_test.php index e5ee1c6c31..a69926e95e 100644 --- a/tests/discussion_test.php +++ b/tests/discussion_test.php @@ -91,6 +91,9 @@ public function test_create_discussion(): void { $prepost->formattachments = ''; $prepost->modulecontext = $this->modulecontext; + // Log in as the teacher. + $this->setUser($this->teacher); + // Build a new discussion object. $discussion = discussion::construct_without_id($this->course->id, $this->moodleoverflow->id, 'Discussion Topic', 0, $this->teacher->id, $time, $time, $this->teacher->id); diff --git a/tests/post_test.php b/tests/post_test.php index 8b16739bbd..ff6729482a 100644 --- a/tests/post_test.php +++ b/tests/post_test.php @@ -85,6 +85,10 @@ public function tearDown(): void { */ public function test_create_post(): void { global $DB; + + // Log in as the teacher. + $this->setUser($this->teacher); + // Build a new post object. $time = time(); $message = 'a unique message'; @@ -103,6 +107,9 @@ public function test_create_post(): void { public function test_edit_post(): void { global $DB; + // Log in as the teacher. + $this->setUser($this->teacher); + // The post and the attachment should exist. $numberofattachments = count($DB->get_records('files', ['itemid' => $this->post->get_id()])); $this->assertEquals(2, $numberofattachments); // One Attachment is saved twice in 'files'. From fe9b6877bdd6c29dd7c47cc7e7fdba6d3703ab7d Mon Sep 17 00:00:00 2001 From: tamaro Date: Fri, 1 Aug 2025 11:12:53 +0200 Subject: [PATCH 12/12] ready for moodle 5.0 --- CHANGES.md | 7 +++++-- version.php | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e490f2344c..de63c4d35d 100755 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,12 @@ CHANGELOG ========= -5.0 (2025-06-24) +v5.0-r1 (2025-08-01) ------------------ - +- Fixes Issues [#216](https://github.com/learnweb/moodle-mod_moodleoverflow/issues/216), + [#211](https://github.com/learnweb/moodle-mod_moodleoverflow/issues/211), + [#202](https://github.com/learnweb/moodle-mod_moodleoverflow/issues/202) +- Adaption to Moodle 5.0 4.5.1 (2025-05-19) ------------------ diff --git a/version.php b/version.php index 15e7ad3556..aacba4b21e 100644 --- a/version.php +++ b/version.php @@ -26,10 +26,10 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2025072500; +$plugin->version = 2025080100; $plugin->requires = 2022112819; // Require Moodle 4.1. $plugin->supported = [401, 500]; $plugin->component = 'mod_moodleoverflow'; -$plugin->maturity = MATURITY_RC; -$plugin->release = 'v5.0-rc1'; +$plugin->maturity = MATURITY_STABLE; +$plugin->release = 'v5.0-r1'; $plugin->dependencies = [];