From 26e22be478261b46c658f0b14f77a79f511e5183 Mon Sep 17 00:00:00 2001 From: tamaro Date: Mon, 1 Dec 2025 15:13:06 +0100 Subject: [PATCH 1/5] add helper functions to post class --- classes/post/post.php | 56 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/classes/post/post.php b/classes/post/post.php index 0d4f97c188..91341df4a4 100644 --- a/classes/post/post.php +++ b/classes/post/post.php @@ -26,8 +26,12 @@ namespace mod_moodleoverflow\post; use coding_exception; +use context_module; +use core\output\html_writer; use core_user\fields; use dml_exception; +use mod_moodleoverflow\anonymous; +use mod_moodleoverflow\capabilities; use mod_moodleoverflow\event\post_deleted; use mod_moodleoverflow\ratings; use mod_moodleoverflow\readtracking; @@ -550,6 +554,58 @@ public function moodleoverflow_get_attachments(): array { return $attachments; } + /** + * Get a link to the users profile. + * Returns a html link embedded in the users name. + * @return moodle_url + * @throws moodle_exception + */ + public function get_userlink(): string { + global $USER, $DB; + $this->existence_check(); + + $courseid = $this->get_discussion()->get_courseid(); + $modulecontext = context_module::instance($this->get_coursemodule()->id); + $userid = $this->get_userid(); + + if (anonymous::is_post_anonymous($this->get_discussion()->get_db_object(), $this->get_moodleoverflow(), $userid)) { + if ($userid == $USER->id) { + $fullname = get_string('anonym_you', 'mod_moodleoverflow'); + $profilelink = new moodle_url('/user/view.php', ['id' => $userid, 'course' => $courseid]); + return html_writer::link($profilelink, $fullname); + } else { + $usermapping = anonymous::get_userid_mapping($this->get_moodleoverflow(), $this->get_discussionid()); + return $usermapping[$userid]; + } + } + $user = $DB->get_record('user', ['id' => $userid]); + $fullname = fullname($user, capabilities::has('moodle/site:viewfullnames', $modulecontext)); + $profilelink = new moodle_url('/user/view.php', ['id' => $userid, 'course' => $courseid]); + return html_writer::link($profilelink, $fullname); + } + + /** + * Returns the post message in a formatted way ready to display. + * @return string + * @throws moodle_exception + */ + public function get_message_formatted(): string { + $context = context_module::instance($this->get_coursemodule()->id); + $message = file_rewrite_pluginfile_urls( + $this->message, + 'pluginfile.php', + $context->id, + 'mod_moodleoverflow', + 'post', + $this->get_id(), + ['includetoken' => true] + ); + $options = new stdClass(); + $options->para = true; + $options->context = $context; + return format_text($message, $this->messageformat, $options); + } + // Getter. /** From e5fdce0278f02faf612fa7c7f2392094b47b7ea5 Mon Sep 17 00:00:00 2001 From: tamaro Date: Mon, 1 Dec 2025 15:14:40 +0100 Subject: [PATCH 2/5] simplify function in post class --- classes/post/post.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/classes/post/post.php b/classes/post/post.php index 91341df4a4..0879c985fc 100644 --- a/classes/post/post.php +++ b/classes/post/post.php @@ -763,14 +763,13 @@ public function moodleoverflow_get_post_ratings(): object { $discussionid = $this->get_discussion()->get_id(); $postratings = ratings::moodleoverflow_get_ratings_by_discussion($discussionid, $this->id); - $ratingsobject = new stdClass(); - $ratingsobject->upvotes = $postratings->upvotes; - $ratingsobject->downvotes = $postratings->downvotes; - $ratingsobject->votesdifference = $postratings->upvotes - $postratings->downvotes; - $ratingsobject->markedhelpful = $postratings->ishelpful; - $ratingsobject->markedsolution = $postratings->issolved; - - return $ratingsobject; + return (object) [ + 'upvotes' => $postratings->upvotes, + 'downvotes' => $postratings->downvotes, + 'votesdifference' => $postratings->upvotes - $postratings->downvotes, + 'markedhelpful' => $postratings->ishelpful, + 'markedsolution' => $postratings->issolved, + ]; } /** From 1e47368423db1a3cc895ae4479aa560935615bf3 Mon Sep 17 00:00:00 2001 From: tamaro Date: Mon, 1 Dec 2025 15:16:29 +0100 Subject: [PATCH 3/5] display original post when replying --- classes/post/post_control.php | 22 ++++++++++++ post.php | 1 + renderer.php | 11 ++++++ templates/post_original.mustache | 62 ++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 templates/post_original.mustache diff --git a/classes/post/post_control.php b/classes/post/post_control.php index 9e087b5984..9c20f3c6d0 100644 --- a/classes/post/post_control.php +++ b/classes/post/post_control.php @@ -27,6 +27,7 @@ // Import namespace from the locallib, needs a check later which namespaces are really needed. use coding_exception; +use context_module; use core\notification; use dml_exception; use html_writer; @@ -671,6 +672,27 @@ public function build_postform(array $pageparams): mod_moodleoverflow_post_form return $mformpost; } + /** + * Display the original post when a user replies to it. + * + * @throws moodle_exception|dml_exception + */ + public function display_original_post(): string { + global $PAGE, $DB; + if ($this->interaction == 'reply') { + $post = post::from_record($DB->get_record('moodleoverflow_posts', ['id' => $this->info->relatedpost->get_id()])); + $data = (object) [ + 'postid' => $post->get_id(), + 'postcontent' => $post->get_message_formatted(), + 'attachments' => $post->moodleoverflow_get_attachments(), + 'byname' => $post->get_userlink(), + 'byshortdate' => userdate($post->modified, get_string('strftimedatetimeshort', 'core_langconfig')), + ]; + return $PAGE->get_renderer('mod_moodleoverflow')->render_post_original($data); + } + return ''; + } + // Helper functions. // Error handling functions. diff --git a/post.php b/post.php index 2e2e42d386..6d48971b1a 100644 --- a/post.php +++ b/post.php @@ -168,5 +168,6 @@ // Display all. echo $OUTPUT->header(); +echo $postcontrol->display_original_post(); $mformpost->display(); echo $OUTPUT->footer(); diff --git a/renderer.php b/renderer.php index 293f010f17..943bfe5e52 100644 --- a/renderer.php +++ b/renderer.php @@ -79,6 +79,17 @@ public function render_post($data) { return $this->render_from_template('mod_moodleoverflow/post', $data); } + /** + * Renders a simplified version of any post. Used to display the post a reply is referring to. + * + * @param object $data The submitted variables. + * + * @return bool|string + */ + public function render_post_original(object $data): bool|string { + return $this->render_from_template('mod_moodleoverflow/post_original', $data); + } + /** * Display a moodleoverflow post in the relevant context. * diff --git a/templates/post_original.mustache b/templates/post_original.mustache new file mode 100644 index 0000000000..0480c31c65 --- /dev/null +++ b/templates/post_original.mustache @@ -0,0 +1,62 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template mod_moodleoverflow/post_original + + Moodleoverflow post original template. + In the post.php this template is used to show the post that a new answer post is referring to. + It's a simpler version of the post template. + + Example context (json): + { + } +}} + +{{! Start the post. Mark it read or unread. }} +

Reply to discussion

+
+
+
+
+ {{{ postcontent }}} +
+
+ {{#attachments}} + {{#image}} + +
+ {{/image}} + {{^image}} + + {{{icon}}} + + + {{filename}} + + {{/image}} +
+ {{/attachments}} +
+ +
+
+
From 1c7104273790f7d73be2c4d068603b6742e30465 Mon Sep 17 00:00:00 2001 From: tamaro Date: Tue, 2 Dec 2025 08:57:49 +0100 Subject: [PATCH 4/5] add js to expand/collapse original post --- amd/build/show_post.min.js | 11 ++++++ amd/build/show_post.min.js.map | 1 + amd/src/show_post.js | 62 ++++++++++++++++++++++++++++++++ classes/post/post_control.php | 1 + lang/en/moodleoverflow.php | 3 ++ styles.css | 19 ++++++++++ templates/post_original.mustache | 52 ++++++++++++++++----------- 7 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 amd/build/show_post.min.js create mode 100644 amd/build/show_post.min.js.map create mode 100644 amd/src/show_post.js diff --git a/amd/build/show_post.min.js b/amd/build/show_post.min.js new file mode 100644 index 0000000000..ffa000f9af --- /dev/null +++ b/amd/build/show_post.min.js @@ -0,0 +1,11 @@ +define("mod_moodleoverflow/show_post",["exports","core/str","core/prefetch"],function(_exports,_str,_prefetch){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){(0,_prefetch.prefetchStrings)("moodleoverflow",["showpost_expand","showpost_collapse"]),postElement.setAttribute("expanded","false"),postElement.style.maxHeight="0px",addEventListener()}; +/** + * JavaScript for + * + * @module mod_moodleoverflow/show_post + * @copyright 2025 Tamaro Walter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +const showpostButton=document.getElementById("moodleoverflow_showpost"),postElement=document.getElementById("moodleoverflow_original_post"),Selectors_actions={showpostbutton:'[data-action="mod_moodleoverflow/showpost_button"]'};const addEventListener=()=>{document.addEventListener("click",async e=>{e.target.closest(Selectors_actions.showpostbutton)&&("true"===postElement.getAttribute("expanded")?(showpostButton.textContent=await(0,_str.getString)("showpost_expand","moodleoverflow"),postElement.style.maxHeight="0px",postElement.setAttribute("expanded","false")):(showpostButton.textContent=await(0,_str.getString)("showpost_collapse","moodleoverflow"),postElement.style.maxHeight=`${postElement.scrollHeight}px`,postElement.setAttribute("expanded","true")))})}}); + +//# sourceMappingURL=show_post.min.js.map \ No newline at end of file diff --git a/amd/build/show_post.min.js.map b/amd/build/show_post.min.js.map new file mode 100644 index 0000000000..e5a054aa7c --- /dev/null +++ b/amd/build/show_post.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"show_post.min.js","sources":["../src/show_post.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport {getString} from \"core/str\";\nimport {prefetchStrings} from 'core/prefetch';\n/**\n * JavaScript for\n *\n * @module mod_moodleoverflow/show_post\n * @copyright 2025 Tamaro Walter\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst showpostButton = document.getElementById('moodleoverflow_showpost');\nconst postElement = document.getElementById('moodleoverflow_original_post');\n\nconst Selectors = {\n actions: {\n showpostbutton: '[data-action=\"mod_moodleoverflow/showpost_button\"]',\n },\n};\n\n/**\n * Init function.\n */\nexport function init() {\n prefetchStrings('moodleoverflow', ['showpost_expand', 'showpost_collapse',]);\n postElement.setAttribute('expanded', 'false');\n postElement.style.maxHeight = '0px';\n addEventListener();\n}\n\n/**\n * Event listener.\n */\nconst addEventListener = () => {\n document.addEventListener('click', async e => {\n if (e.target.closest(Selectors.actions.showpostbutton)) {\n if (postElement.getAttribute('expanded') === 'true') {\n showpostButton.textContent = await getString('showpost_expand', 'moodleoverflow');\n postElement.style.maxHeight = '0px';\n postElement.setAttribute('expanded', 'false');\n } else {\n showpostButton.textContent = await getString('showpost_collapse', 'moodleoverflow');\n postElement.style.maxHeight = `${postElement.scrollHeight}px`;\n postElement.setAttribute('expanded', 'true');\n }\n }\n });\n};\n"],"names":["prefetchStrings","postElement","setAttribute","style","maxHeight","addEventListener","showpostButton","document","getElementById","Selectors","showpostbutton","async","e","target","closest","getAttribute","textContent","getString","scrollHeight"],"mappings":"qLAqCO,YACH,EAAAA,UAAAA,iBAAgB,iBAAkB,CAAC,kBAAmB,sBACtDC,YAAYC,aAAa,WAAY,SACrCD,YAAYE,MAAMC,UAAY,MAC9BC,kBACJ;;;;;;;;AAjBA,MAAMC,eAAiBC,SAASC,eAAe,2BACzCP,YAAcM,SAASC,eAAe,gCAEtCC,kBACO,CACLC,eAAgB,sDAiBxB,MAAML,iBAAmBA,KACrBE,SAASF,iBAAiB,QAASM,UAC3BC,EAAEC,OAAOC,QAAQL,kBAAkBC,kBACU,SAAzCT,YAAYc,aAAa,aACzBT,eAAeU,kBAAoB,EAAAC,gBAAU,kBAAmB,kBAChEhB,YAAYE,MAAMC,UAAY,MAC9BH,YAAYC,aAAa,WAAY,WAErCI,eAAeU,kBAAqB,EAAAC,gBAAU,oBAAqB,kBACnEhB,YAAYE,MAAMC,UAAY,GAAGH,YAAYiB,iBAC7CjB,YAAYC,aAAa,WAAY,YAInD"} \ No newline at end of file diff --git a/amd/src/show_post.js b/amd/src/show_post.js new file mode 100644 index 0000000000..87dd2e7039 --- /dev/null +++ b/amd/src/show_post.js @@ -0,0 +1,62 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +import {getString} from "core/str"; +import {prefetchStrings} from 'core/prefetch'; +/** + * JavaScript for + * + * @module mod_moodleoverflow/show_post + * @copyright 2025 Tamaro Walter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +const showpostButton = document.getElementById('moodleoverflow_showpost'); +const postElement = document.getElementById('moodleoverflow_original_post'); + +const Selectors = { + actions: { + showpostbutton: '[data-action="mod_moodleoverflow/showpost_button"]', + }, +}; + +/** + * Init function. + */ +export function init() { + prefetchStrings('moodleoverflow', ['showpost_expand', 'showpost_collapse',]); + postElement.setAttribute('expanded', 'false'); + postElement.style.maxHeight = '0px'; + addEventListener(); +} + +/** + * Event listener. + */ +const addEventListener = () => { + document.addEventListener('click', async e => { + if (e.target.closest(Selectors.actions.showpostbutton)) { + if (postElement.getAttribute('expanded') === 'true') { + showpostButton.textContent = await getString('showpost_expand', 'moodleoverflow'); + postElement.style.maxHeight = '0px'; + postElement.setAttribute('expanded', 'false'); + } else { + showpostButton.textContent = await getString('showpost_collapse', 'moodleoverflow'); + postElement.style.maxHeight = `${postElement.scrollHeight}px`; + postElement.setAttribute('expanded', 'true'); + } + } + }); +}; diff --git a/classes/post/post_control.php b/classes/post/post_control.php index 9c20f3c6d0..0a77a02c68 100644 --- a/classes/post/post_control.php +++ b/classes/post/post_control.php @@ -680,6 +680,7 @@ public function build_postform(array $pageparams): mod_moodleoverflow_post_form public function display_original_post(): string { global $PAGE, $DB; if ($this->interaction == 'reply') { + $PAGE->requires->js_call_amd('mod_moodleoverflow/show_post', 'init'); $post = post::from_record($DB->get_record('moodleoverflow_posts', ['id' => $this->info->relatedpost->get_id()])); $data = (object) [ 'postid' => $post->get_id(), diff --git a/lang/en/moodleoverflow.php b/lang/en/moodleoverflow.php index 8bb130628d..8229795b4c 100644 --- a/lang/en/moodleoverflow.php +++ b/lang/en/moodleoverflow.php @@ -270,6 +270,7 @@ $string['nowtracking'] = '{$a->name} is now tracking \'{$a->moodleoverflow}\'.'; $string['oldpostdays'] = 'Read after days'; $string['original_post'] = 'Original post'; +$string['original_post_reply'] = 'You are adressing the post from {$a}:'; $string['parent'] = 'Show parent'; $string['pending_review'] = 'Pending review'; $string['pending_review_but_cannot_now'] = 'Pending review, but cannot be approved until {$a} after the creation of this post to allow the author a bit of time to edit it.'; @@ -369,6 +370,8 @@ $string['scalefactor_help'] = 'The user rating is divided by the scale factor to obtain each user\'s grade. If the resulting grade is greater than the maximum grade, the value is limited to the specified maximum grade'; $string['scalefactorerror'] = 'Scale factor must be a positive integer different than 0'; $string['seeuserstats'] = 'View user statistics'; +$string['showpost_collapse'] = 'collapse'; +$string['showpost_expand'] = 'expand'; $string['showuserstats'] = 'Show cumulative user statistics'; $string['smallmessage'] = '{$a->user} posted in {$a->moodleoverflowname}'; $string['starterrating'] = 'Helpful'; diff --git a/styles.css b/styles.css index d6f90bdb9f..f32e5b34c8 100644 --- a/styles.css +++ b/styles.css @@ -123,6 +123,25 @@ white-space: nowrap; } +#moodleoverflow_original_post { + overflow: hidden; + max-height: 0; + transition: max-height 0.4s ease; +} + +#moodleoverflow_showpost { + cursor: pointer; + background-color: #6a737b; + font-size: 0.8rem; +} + +.original-post-text { + margin-bottom: 5px; + overflow-wrap: break-word; + width: 100%; + word-wrap: break-word; +} + /* * The Index Page. */ diff --git a/templates/post_original.mustache b/templates/post_original.mustache index 0480c31c65..e18df59a4c 100644 --- a/templates/post_original.mustache +++ b/templates/post_original.mustache @@ -32,29 +32,39 @@ border" role="region" data-moodleoverflow-postid="{{postid}}">
-
- {{{ postcontent }}} + -
- {{#attachments}} - {{#image}} - +
+
+ {{{ postcontent }}} +
+
+ {{#attachments}} + {{#image}} + +
+ {{/image}} + {{^image}} + + {{{icon}}} + + + {{filename}} + + {{/image}}
- {{/image}} - {{^image}} - - {{{icon}}} - - - {{filename}} - - {{/image}} -
- {{/attachments}} -
-
From 2604efd439cb0364e57458616f1ae2a80db39da1 Mon Sep 17 00:00:00 2001 From: tamaro Date: Tue, 2 Dec 2025 10:25:49 +0100 Subject: [PATCH 5/5] add test --- tests/behat/reply_post.feature | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/behat/reply_post.feature diff --git a/tests/behat/reply_post.feature b/tests/behat/reply_post.feature new file mode 100644 index 0000000000..f9fd6f8a07 --- /dev/null +++ b/tests/behat/reply_post.feature @@ -0,0 +1,12 @@ +@mod @mod_moodleoverflow @javascript +Feature: Replying to posts in a moodleoverflow + + Scenario: While replying to a post I need to see the original post I'm answering to + Given I add a moodleoverflow discussion with posts from different users + When I navigate as "teacher1" to "C1" "Moodleoverflow 1" "Discussion 1" + And I click on "Answer" "link" + Then I should see "You are adressing the post from Tamaro Walter:" + When I click on "#moodleoverflow_showpost" "css_element" + Then I should see "Message from teacher" + When I click on "#moodleoverflow_showpost" "css_element" + Then I should not see "Message from teacher" \ No newline at end of file