See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Steps definitions related with the forum activity. 19 * 20 * @package mod_forum 21 * @category test 22 * @copyright 2013 David MonllaĆ³ 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. 27 28 require_once (__DIR__ . '/../../../../lib/behat/behat_base.php'); 29 30 use Behat\Gherkin\Node\TableNode as TableNode; 31 /** 32 * Forum-related steps definitions. 33 * 34 * @package mod_forum 35 * @category test 36 * @copyright 2013 David MonllaĆ³ 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class behat_mod_forum extends behat_base { 40 41 /** 42 * Adds a topic to the forum specified by it's name. Useful for the Announcements and blog-style forums. 43 * 44 * @Given /^I add a new topic to "(?P<forum_name_string>(?:[^"]|\\")*)" forum with:$/ 45 * @param string $forumname 46 * @param TableNode $table 47 */ 48 public function i_add_a_new_topic_to_forum_with($forumname, TableNode $table) { 49 $this->add_new_discussion($forumname, $table, get_string('addanewtopic', 'forum')); 50 } 51 52 /** 53 * Adds a Q&A discussion to the Q&A-type forum specified by it's name with the provided table data. 54 * 55 * @Given /^I add a new question to "(?P<forum_name_string>(?:[^"]|\\")*)" forum with:$/ 56 * @param string $forumname 57 * @param TableNode $table 58 */ 59 public function i_add_a_new_question_to_forum_with($forumname, TableNode $table) { 60 $this->add_new_discussion($forumname, $table, get_string('addanewquestion', 'forum')); 61 } 62 63 /** 64 * Adds a discussion to the forum specified by it's name with the provided table data (usually Subject and Message). The step begins from the forum's course page. 65 * 66 * @Given /^I add a new discussion to "(?P<forum_name_string>(?:[^"]|\\")*)" forum with:$/ 67 * @param string $forumname 68 * @param TableNode $table 69 */ 70 public function i_add_a_forum_discussion_to_forum_with($forumname, TableNode $table) { 71 $this->add_new_discussion($forumname, $table, get_string('addanewdiscussion', 'forum')); 72 } 73 74 /** 75 * Adds a discussion to the forum specified by it's name with the provided table data (usually Subject and Message). 76 * The step begins from the forum's course page. 77 * 78 * @Given /^I add a new discussion to "(?P<forum_name_string>(?:[^"]|\\")*)" forum inline with:$/ 79 * @param string $forumname 80 * @param TableNode $table 81 */ 82 public function i_add_a_forum_discussion_to_forum_inline_with($forumname, TableNode $table) { 83 $this->add_new_discussion_inline($forumname, $table, get_string('addanewdiscussion', 'forum')); 84 } 85 86 /** 87 * Adds a reply to the specified post of the specified forum. The step begins from the forum's page or from the forum's course page. 88 * 89 * @Given /^I reply "(?P<post_subject_string>(?:[^"]|\\")*)" post from "(?P<forum_name_string>(?:[^"]|\\")*)" forum with:$/ 90 * @param string $postname The subject of the post 91 * @param string $forumname The forum name 92 * @param TableNode $table 93 */ 94 public function i_reply_post_from_forum_with($postsubject, $forumname, TableNode $table) { 95 96 // Navigate to forum. 97 $this->goto_main_post_reply($postsubject); 98 99 // Fill form and post. 100 $this->execute('behat_forms::i_set_the_following_fields_to_these_values', $table); 101 102 $this->execute('behat_forms::press_button', get_string('posttoforum', 'forum')); 103 $this->execute('behat_general::i_wait_to_be_redirected'); 104 } 105 106 /** 107 * Inpage Reply - adds a reply to the specified post of the specified forum. The step begins from the forum's page or from the forum's course page. 108 * 109 * @Given /^I reply "(?P<post_subject_string>(?:[^"]|\\")*)" post from "(?P<forum_name_string>(?:[^"]|\\")*)" forum using an inpage reply with:$/ 110 * @param string $postsubject The subject of the post 111 * @param string $forumname The forum name 112 * @param TableNode $table 113 */ 114 public function i_reply_post_from_forum_using_an_inpage_reply_with($postsubject, $forumname, TableNode $table) { 115 // Navigate to forum. 116 $this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($forumname), 'forum activity']); 117 $this->execute('behat_general::click_link', $this->escape($postsubject)); 118 $this->execute('behat_general::click_link', get_string('reply', 'forum')); 119 120 // Fill form and post. 121 $this->execute('behat_forms::i_set_the_following_fields_to_these_values', $table); 122 123 $this->execute('behat_forms::press_button', get_string('posttoforum', 'mod_forum')); 124 } 125 126 /** 127 * Navigates to a particular discussion page 128 * 129 * @Given /^I navigate to post "(?P<post_subject_string>(?:[^"]|\\")*)" in "(?P<forum_name_string>(?:[^"]|\\")*)" forum$/ 130 * @param string $postsubject The subject of the post 131 * @param string $forumname The forum name 132 */ 133 public function i_navigate_to_post_in_forum($postsubject, $forumname) { 134 // Navigate to forum discussion. 135 $this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($forumname), 'forum activity']); 136 $this->execute('behat_general::click_link', $this->escape($postsubject)); 137 } 138 139 /** 140 * Opens up the action menu for the discussion 141 * 142 * @Given /^I click on "(?P<post_subject_string>(?:[^"]|\\")*)" action menu$/ 143 * @param string $discussion The subject of the discussion 144 */ 145 public function i_click_on_action_menu($discussion) { 146 $this->execute('behat_general::i_click_on_in_the', [ 147 "[data-container='discussion-tools'] [data-toggle='dropdown']", "css_element", 148 "//tr[contains(concat(' ', normalize-space(@class), ' '), ' discussion ') and contains(.,'$discussion')]", 149 "xpath_element" 150 ]); 151 } 152 153 /** 154 * Creates new discussions within forums of a given course. 155 * 156 * @Given the following forum discussions exist in course :coursename: 157 * @param string $coursename The full name of the course where the forums exist. 158 * @param TableNode $discussionsdata The discussion posts to be created. 159 */ 160 public function the_following_forum_discussions_exist(string $coursename, TableNode $discussionsdata) { 161 global $DB; 162 163 $courseid = $this->get_course_id($coursename); 164 $forumgenerator = behat_util::get_data_generator()->get_plugin_generator('mod_forum'); 165 166 // Add the discussions to the relevant forum. 167 foreach ($discussionsdata->getHash() as $discussioninfo) { 168 $discussioninfo['course'] = $courseid; 169 $discussioninfo['forum'] = $this->get_forum_id($courseid, $discussioninfo['forum']); 170 $discussioninfo['userid'] = $this->get_user_id($discussioninfo['user']); 171 172 // Prepare data for any attachments. 173 if (!empty($discussioninfo['attachments']) || !empty($discussioninfo['inlineattachments'])) { 174 $discussioninfo['attachment'] = 1; 175 $cm = get_coursemodule_from_instance('forum', $discussioninfo['forum']); 176 } 177 178 // Prepare data for groups if needed. 179 if (!empty($discussioninfo['group'])) { 180 $discussioninfo['groupid'] = $this->get_group_id($courseid, $discussioninfo['group']); 181 unset($discussioninfo['group']); 182 } 183 184 // Create the discussion post. 185 $discussion = $forumgenerator->create_discussion($discussioninfo); 186 $postid = $DB->get_field('forum_posts', 'id', ['discussion' => $discussion->id]); 187 188 // Override the creation and modified timestamps as required. 189 if (!empty($discussioninfo['created']) || !empty($discussioninfo['modified'])) { 190 $discussiondata = [ 191 'id' => $discussion->id, 192 'timemodified' => empty($discussioninfo['modified']) ? $discussioninfo['created'] : $discussioninfo['modified'], 193 ]; 194 195 $DB->update_record('forum_discussions', $discussiondata); 196 197 $postdata = [ 198 'id' => $postid, 199 'modified' => empty($discussioninfo['modified']) ? $discussioninfo['created'] : $discussioninfo['modified'], 200 ]; 201 202 if (!empty($discussioninfo['created'])) { 203 $postdata['created'] = $discussioninfo['created']; 204 } 205 206 $DB->update_record('forum_posts', $postdata); 207 } 208 209 // Create attachments to the discussion post if required. 210 if (!empty($discussioninfo['attachments'])) { 211 $attachments = array_map('trim', explode(',', $discussioninfo['attachments'])); 212 $this->create_post_attachments($postid, $discussioninfo['userid'], $attachments, $cm, 'attachment'); 213 } 214 215 // Create inline attachments to the discussion post if required. 216 if (!empty($discussioninfo['inlineattachments'])) { 217 $inlineattachments = array_map('trim', explode(',', $discussioninfo['inlineattachments'])); 218 $this->create_post_attachments($postid, $discussioninfo['userid'], $inlineattachments, $cm, 'post'); 219 } 220 } 221 } 222 223 /** 224 * Creates replies to discussions within forums of a given course. 225 * 226 * @Given the following forum replies exist in course :coursename: 227 * @param string $coursename The full name of the course where the forums exist. 228 * @param TableNode $repliesdata The reply posts to be created. 229 */ 230 public function the_following_forum_replies_exist(string $coursename, TableNode $repliesdata) { 231 global $DB; 232 233 $courseid = $this->get_course_id($coursename); 234 $forumgenerator = behat_util::get_data_generator()->get_plugin_generator('mod_forum'); 235 236 // Add the replies to the relevant discussions. 237 foreach ($repliesdata->getHash() as $replyinfo) { 238 $replyinfo['course'] = $courseid; 239 $replyinfo['forum'] = $this->get_forum_id($courseid, $replyinfo['forum']); 240 $replyinfo['userid'] = $this->get_user_id($replyinfo['user']); 241 242 [ 243 'discussionid' => $replyinfo['discussion'], 244 'parentid' => $replyinfo['parent'], 245 ] = $this->get_base_discussion($replyinfo['forum'], $replyinfo['discussion']); 246 247 // Prepare data for any attachments. 248 if (!empty($replyinfo['attachments']) || !empty($replyinfo['inlineattachments'])) { 249 $replyinfo['attachment'] = 1; 250 $cm = get_coursemodule_from_instance('forum', $replyinfo['forum']); 251 } 252 253 // Get the user id of the user to whom the reply is private. 254 if (!empty($replyinfo['privatereplyto'])) { 255 $replyinfo['privatereplyto'] = $this->get_user_id($replyinfo['privatereplyto']); 256 } 257 258 // Create the reply post. 259 $reply = $forumgenerator->create_post($replyinfo); 260 261 // Create attachments to the post if required. 262 if (!empty($replyinfo['attachments'])) { 263 $attachments = array_map('trim', explode(',', $replyinfo['attachments'])); 264 $this->create_post_attachments($reply->id, $replyinfo['userid'], $attachments, $cm, 'attachment'); 265 } 266 267 // Create inline attachments to the post if required. 268 if (!empty($replyinfo['inlineattachments'])) { 269 $inlineattachments = array_map('trim', explode(',', $replyinfo['inlineattachments'])); 270 $this->create_post_attachments($reply->id, $replyinfo['userid'], $inlineattachments, $cm, 'post'); 271 } 272 } 273 } 274 275 /** 276 * Checks if the user can subscribe to the forum. 277 * 278 * @Given /^I can subscribe to this forum$/ 279 */ 280 public function i_can_subscribe_to_this_forum() { 281 if ($this->running_javascript()) { 282 $this->execute('behat_general::i_click_on', [get_string('actionsmenu'), 'link']); 283 } 284 285 $this->execute('behat_general::assert_page_contains_text', [get_string('subscribe', 'mod_forum')]); 286 287 if ($this->running_javascript()) { 288 $this->execute('behat_general::i_click_on', [get_string('actionsmenu'), 'link']); 289 } 290 } 291 292 /** 293 * Checks if the user can unsubscribe from the forum. 294 * 295 * @Given /^I can unsubscribe from this forum$/ 296 */ 297 public function i_can_unsubscribe_from_this_forum() { 298 if ($this->running_javascript()) { 299 $this->execute('behat_general::i_click_on', [get_string('actionsmenu'), 'link']); 300 } 301 302 $this->execute('behat_general::assert_page_contains_text', [get_string('unsubscribe', 'mod_forum')]); 303 304 if ($this->running_javascript()) { 305 $this->execute('behat_general::i_click_on', [get_string('actionsmenu'), 'link']); 306 } 307 } 308 309 /** 310 * Subscribes to the forum. 311 * 312 * @Given /^I subscribe to this forum$/ 313 */ 314 public function i_subscribe_to_this_forum() { 315 if ($this->running_javascript()) { 316 $this->execute('behat_general::i_click_on', [get_string('actionsmenu'), 'link']); 317 } 318 319 $this->execute('behat_general::click_link', [get_string('subscribe', 'mod_forum')]); 320 } 321 322 /** 323 * Unsubscribes from the forum. 324 * 325 * @Given /^I unsubscribe from this forum$/ 326 */ 327 public function i_unsubscribe_from_this_forum() { 328 if ($this->running_javascript()) { 329 $this->execute('behat_general::i_click_on', [get_string('actionsmenu'), 'link']); 330 } 331 332 $this->execute('behat_general::click_link', [get_string('unsubscribe', 'mod_forum')]); 333 } 334 335 /** 336 * Fetch user ID from its username. 337 * 338 * @param string $username The username. 339 * @return int The user ID. 340 * @throws Exception 341 */ 342 protected function get_user_id($username) { 343 global $DB; 344 345 if (!$userid = $DB->get_field('user', 'id', ['username' => $username])) { 346 throw new Exception("A user with username '{$username}' does not exist"); 347 } 348 return $userid; 349 } 350 351 /** 352 * Fetch course ID using course name. 353 * 354 * @param string $coursename The name of the course. 355 * @return int The course ID. 356 * @throws Exception 357 */ 358 protected function get_course_id(string $coursename): int { 359 global $DB; 360 361 if (!$courseid = $DB->get_field('course', 'id', ['fullname' => $coursename])) { 362 throw new Exception("A course with name '{$coursename}' does not exist"); 363 } 364 365 return $courseid; 366 } 367 368 /** 369 * Fetch forum ID using forum name. 370 * 371 * @param int $courseid The course ID the forum exists within. 372 * @param string $forumname The name of the forum. 373 * @return int The forum ID. 374 * @throws Exception 375 */ 376 protected function get_forum_id(int $courseid, string $forumname): int { 377 global $DB; 378 379 $conditions = [ 380 'course' => $courseid, 381 'name' => $forumname, 382 ]; 383 384 if (!$forumid = $DB->get_field('forum', 'id', $conditions)) { 385 throw new Exception("A forum with name '{$forumname}' does not exist in the provided course"); 386 } 387 388 return $forumid; 389 } 390 391 /** 392 * Fetch Group ID using group name. 393 * 394 * @param int $courseid The course ID the forum exists within. 395 * @param string $groupname The short name of the group. 396 * @return int The group ID. 397 * @throws Exception 398 */ 399 protected function get_group_id(int $courseid, string $groupname): int { 400 global $DB; 401 402 if ($groupname === 'All participants') { 403 return -1; 404 } 405 406 $conditions = [ 407 'courseid' => $courseid, 408 'idnumber' => $groupname, 409 ]; 410 411 if (!$groupid = $DB->get_field('groups', 'id', $conditions)) { 412 throw new Exception("A group with name '{$groupname}' does not exist in the provided course"); 413 } 414 415 return $groupid; 416 } 417 418 /** 419 * Fetch discussion ID and first post ID by discussion name. 420 * 421 * @param int $forumid The forum ID where the discussion resides. 422 * @param string $name The name of the discussion. 423 * @return array The discussion ID and first post ID. 424 * @throws dml_exception If the discussion name is not unique within the forum (or doesn't exist). 425 */ 426 protected function get_base_discussion(int $forumid, string $name): array { 427 global $DB; 428 429 $conditions = [ 430 'name' => $name, 431 'forum' => $forumid, 432 ]; 433 434 $result = $DB->get_record("forum_discussions", $conditions, 'id, firstpost', MUST_EXIST); 435 436 return [ 437 'discussionid' => $result->id, 438 'parentid' => $result->firstpost, 439 ]; 440 } 441 442 /** 443 * Create one or more attached or inline attachments to a forum post. 444 * 445 * @param int $postid The ID of the forum post. 446 * @param int $userid The user ID creating the attachment. 447 * @param array $attachmentnames Names of all attachments to be created. 448 * @param stdClass $cm The context module of the forum. 449 * @param string $filearea The file area being written to, eg 'attachment' or 'post' (inline). 450 */ 451 protected function create_post_attachments(int $postid, int $userid, array $attachmentnames, stdClass $cm, string $filearea): void { 452 $filestorage = get_file_storage(); 453 454 foreach ($attachmentnames as $attachmentname) { 455 $filestorage->create_file_from_string( 456 [ 457 'contextid' => context_module::instance($cm->id)->id, 458 'component' => 'mod_forum', 459 'filearea' => $filearea, 460 'itemid' => $postid, 461 'filepath' => '/', 462 'filename' => $attachmentname, 463 'userid' => $userid, 464 ], 465 "File content {$attachmentname}" 466 ); 467 } 468 } 469 470 /** 471 * Returns the steps list to add a new discussion to a forum. 472 * 473 * Abstracts add a new topic and add a new discussion, as depending 474 * on the forum type the button string changes. 475 * 476 * @param string $forumname 477 * @param TableNode $table 478 * @param string $buttonstr 479 */ 480 protected function add_new_discussion($forumname, TableNode $table, $buttonstr) { 481 // Navigate to forum. 482 $this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($forumname), 'forum activity']); 483 $this->execute('behat_general::click_link', $buttonstr); 484 $this->execute('behat_forms::press_button', get_string('showadvancededitor')); 485 486 $this->fill_new_discussion_form($table); 487 } 488 489 /** 490 * Returns the steps list to add a new discussion to a forum inline. 491 * 492 * Abstracts add a new topic and add a new discussion, as depending 493 * on the forum type the button string changes. 494 * 495 * @param string $forumname 496 * @param TableNode $table 497 * @param string $buttonstr 498 */ 499 protected function add_new_discussion_inline($forumname, TableNode $table, $buttonstr) { 500 // Navigate to forum. 501 $this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($forumname), 'forum activity']); 502 $this->execute('behat_general::click_link', $buttonstr); 503 $this->fill_new_discussion_form($table); 504 } 505 506 /** 507 * Fill in the forum's post form and submit. It assumes you've already navigated and enabled the form for view. 508 * 509 * @param TableNode $table 510 * @throws coding_exception 511 */ 512 protected function fill_new_discussion_form(TableNode $table) { 513 // Fill form and post. 514 $this->execute('behat_forms::i_set_the_following_fields_to_these_values', $table); 515 $this->execute('behat_forms::press_button', get_string('posttoforum', 'forum')); 516 $this->execute('behat_general::i_wait_to_be_redirected'); 517 } 518 519 /** 520 * Go to the default reply to post page. 521 * This is used instead of navigating through 4-5 different steps and to solve issues where JS would be required to click 522 * on the advanced button 523 * 524 * @param $postsubject 525 * @throws coding_exception 526 * @throws dml_exception 527 * @throws moodle_exception 528 */ 529 protected function goto_main_post_reply($postsubject) { 530 global $DB; 531 $post = $DB->get_record("forum_posts", array("subject" => $postsubject), 'id', MUST_EXIST); 532 $url = new moodle_url('/mod/forum/post.php', ['reply' => $post->id]); 533 $this->execute('behat_general::i_visit', [$url]); 534 } 535 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body