PageRenderTime 63ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/mod/forum/lib.php

https://bitbucket.org/moodle/moodle
PHP | 7037 lines | 6600 code | 185 blank | 252 comment | 133 complexity | 4eae533791952e05c0740e5a66b1c09d MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0

Large files files are truncated, but you can click here to view the full file

  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. * @package mod_forum
  18. * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
  19. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  20. */
  21. defined('MOODLE_INTERNAL') || die();
  22. /** Include required files */
  23. require_once(__DIR__ . '/deprecatedlib.php');
  24. require_once($CFG->libdir.'/filelib.php');
  25. /// CONSTANTS ///////////////////////////////////////////////////////////
  26. define('FORUM_MODE_FLATOLDEST', 1);
  27. define('FORUM_MODE_FLATNEWEST', -1);
  28. define('FORUM_MODE_THREADED', 2);
  29. define('FORUM_MODE_NESTED', 3);
  30. define('FORUM_MODE_NESTED_V2', 4);
  31. define('FORUM_CHOOSESUBSCRIBE', 0);
  32. define('FORUM_FORCESUBSCRIBE', 1);
  33. define('FORUM_INITIALSUBSCRIBE', 2);
  34. define('FORUM_DISALLOWSUBSCRIBE',3);
  35. /**
  36. * FORUM_TRACKING_OFF - Tracking is not available for this forum.
  37. */
  38. define('FORUM_TRACKING_OFF', 0);
  39. /**
  40. * FORUM_TRACKING_OPTIONAL - Tracking is based on user preference.
  41. */
  42. define('FORUM_TRACKING_OPTIONAL', 1);
  43. /**
  44. * FORUM_TRACKING_FORCED - Tracking is on, regardless of user setting.
  45. * Treated as FORUM_TRACKING_OPTIONAL if $CFG->forum_allowforcedreadtracking is off.
  46. */
  47. define('FORUM_TRACKING_FORCED', 2);
  48. define('FORUM_MAILED_PENDING', 0);
  49. define('FORUM_MAILED_SUCCESS', 1);
  50. define('FORUM_MAILED_ERROR', 2);
  51. if (!defined('FORUM_CRON_USER_CACHE')) {
  52. /** Defines how many full user records are cached in forum cron. */
  53. define('FORUM_CRON_USER_CACHE', 5000);
  54. }
  55. /**
  56. * FORUM_POSTS_ALL_USER_GROUPS - All the posts in groups where the user is enrolled.
  57. */
  58. define('FORUM_POSTS_ALL_USER_GROUPS', -2);
  59. define('FORUM_DISCUSSION_PINNED', 1);
  60. define('FORUM_DISCUSSION_UNPINNED', 0);
  61. /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
  62. /**
  63. * Given an object containing all the necessary data,
  64. * (defined by the form in mod_form.php) this function
  65. * will create a new instance and return the id number
  66. * of the new instance.
  67. *
  68. * @param stdClass $forum add forum instance
  69. * @param mod_forum_mod_form $mform
  70. * @return int intance id
  71. */
  72. function forum_add_instance($forum, $mform = null) {
  73. global $CFG, $DB;
  74. require_once($CFG->dirroot.'/mod/forum/locallib.php');
  75. $forum->timemodified = time();
  76. if (empty($forum->assessed)) {
  77. $forum->assessed = 0;
  78. }
  79. if (empty($forum->ratingtime) or empty($forum->assessed)) {
  80. $forum->assesstimestart = 0;
  81. $forum->assesstimefinish = 0;
  82. }
  83. $forum->id = $DB->insert_record('forum', $forum);
  84. $modcontext = context_module::instance($forum->coursemodule);
  85. if ($forum->type == 'single') { // Create related discussion.
  86. $discussion = new stdClass();
  87. $discussion->course = $forum->course;
  88. $discussion->forum = $forum->id;
  89. $discussion->name = $forum->name;
  90. $discussion->assessed = $forum->assessed;
  91. $discussion->message = $forum->intro;
  92. $discussion->messageformat = $forum->introformat;
  93. $discussion->messagetrust = trusttext_trusted(context_course::instance($forum->course));
  94. $discussion->mailnow = false;
  95. $discussion->groupid = -1;
  96. $message = '';
  97. $discussion->id = forum_add_discussion($discussion, null, $message);
  98. if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
  99. // Ugly hack - we need to copy the files somehow.
  100. $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
  101. $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
  102. $options = array('subdirs'=>true); // Use the same options as intro field!
  103. $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
  104. $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
  105. }
  106. }
  107. forum_update_calendar($forum, $forum->coursemodule);
  108. forum_grade_item_update($forum);
  109. $completiontimeexpected = !empty($forum->completionexpected) ? $forum->completionexpected : null;
  110. \core_completion\api::update_completion_date_event($forum->coursemodule, 'forum', $forum->id, $completiontimeexpected);
  111. return $forum->id;
  112. }
  113. /**
  114. * Handle changes following the creation of a forum instance.
  115. * This function is typically called by the course_module_created observer.
  116. *
  117. * @param object $context the forum context
  118. * @param stdClass $forum The forum object
  119. * @return void
  120. */
  121. function forum_instance_created($context, $forum) {
  122. if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
  123. $users = \mod_forum\subscriptions::get_potential_subscribers($context, 0, 'u.id, u.email');
  124. foreach ($users as $user) {
  125. \mod_forum\subscriptions::subscribe_user($user->id, $forum, $context);
  126. }
  127. }
  128. }
  129. /**
  130. * Given an object containing all the necessary data,
  131. * (defined by the form in mod_form.php) this function
  132. * will update an existing instance with new data.
  133. *
  134. * @global object
  135. * @param object $forum forum instance (with magic quotes)
  136. * @return bool success
  137. */
  138. function forum_update_instance($forum, $mform) {
  139. global $CFG, $DB, $OUTPUT, $USER;
  140. require_once($CFG->dirroot.'/mod/forum/locallib.php');
  141. $forum->timemodified = time();
  142. $forum->id = $forum->instance;
  143. if (empty($forum->assessed)) {
  144. $forum->assessed = 0;
  145. }
  146. if (empty($forum->ratingtime) or empty($forum->assessed)) {
  147. $forum->assesstimestart = 0;
  148. $forum->assesstimefinish = 0;
  149. }
  150. $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
  151. // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
  152. // if scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
  153. // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
  154. $updategrades = false;
  155. if ($oldforum->assessed <> $forum->assessed) {
  156. // Whether this forum is rated.
  157. $updategrades = true;
  158. }
  159. if ($oldforum->scale <> $forum->scale) {
  160. // The scale currently in use.
  161. $updategrades = true;
  162. }
  163. if (empty($oldforum->grade_forum) || $oldforum->grade_forum <> $forum->grade_forum) {
  164. // The whole forum grading.
  165. $updategrades = true;
  166. }
  167. if ($updategrades) {
  168. forum_update_grades($forum); // Recalculate grades for the forum.
  169. }
  170. if ($forum->type == 'single') { // Update related discussion and post.
  171. $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
  172. if (!empty($discussions)) {
  173. if (count($discussions) > 1) {
  174. echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
  175. }
  176. $discussion = array_pop($discussions);
  177. } else {
  178. // try to recover by creating initial discussion - MDL-16262
  179. $discussion = new stdClass();
  180. $discussion->course = $forum->course;
  181. $discussion->forum = $forum->id;
  182. $discussion->name = $forum->name;
  183. $discussion->assessed = $forum->assessed;
  184. $discussion->message = $forum->intro;
  185. $discussion->messageformat = $forum->introformat;
  186. $discussion->messagetrust = true;
  187. $discussion->mailnow = false;
  188. $discussion->groupid = -1;
  189. $message = '';
  190. forum_add_discussion($discussion, null, $message);
  191. if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
  192. print_error('cannotadd', 'forum');
  193. }
  194. }
  195. if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
  196. print_error('cannotfindfirstpost', 'forum');
  197. }
  198. $cm = get_coursemodule_from_instance('forum', $forum->id);
  199. $modcontext = context_module::instance($cm->id, MUST_EXIST);
  200. $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
  201. $post->subject = $forum->name;
  202. $post->message = $forum->intro;
  203. $post->messageformat = $forum->introformat;
  204. $post->messagetrust = trusttext_trusted($modcontext);
  205. $post->modified = $forum->timemodified;
  206. $post->userid = $USER->id; // MDL-18599, so that current teacher can take ownership of activities.
  207. if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
  208. // Ugly hack - we need to copy the files somehow.
  209. $options = array('subdirs'=>true); // Use the same options as intro field!
  210. $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
  211. }
  212. \mod_forum\local\entities\post::add_message_counts($post);
  213. $DB->update_record('forum_posts', $post);
  214. $discussion->name = $forum->name;
  215. $DB->update_record('forum_discussions', $discussion);
  216. }
  217. $DB->update_record('forum', $forum);
  218. $modcontext = context_module::instance($forum->coursemodule);
  219. if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
  220. $users = \mod_forum\subscriptions::get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
  221. foreach ($users as $user) {
  222. \mod_forum\subscriptions::subscribe_user($user->id, $forum, $modcontext);
  223. }
  224. }
  225. forum_update_calendar($forum, $forum->coursemodule);
  226. forum_grade_item_update($forum);
  227. $completiontimeexpected = !empty($forum->completionexpected) ? $forum->completionexpected : null;
  228. \core_completion\api::update_completion_date_event($forum->coursemodule, 'forum', $forum->id, $completiontimeexpected);
  229. return true;
  230. }
  231. /**
  232. * Given an ID of an instance of this module,
  233. * this function will permanently delete the instance
  234. * and any data that depends on it.
  235. *
  236. * @global object
  237. * @param int $id forum instance id
  238. * @return bool success
  239. */
  240. function forum_delete_instance($id) {
  241. global $DB;
  242. if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
  243. return false;
  244. }
  245. if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
  246. return false;
  247. }
  248. if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
  249. return false;
  250. }
  251. $context = context_module::instance($cm->id);
  252. // now get rid of all files
  253. $fs = get_file_storage();
  254. $fs->delete_area_files($context->id);
  255. $result = true;
  256. \core_completion\api::update_completion_date_event($cm->id, 'forum', $forum->id, null);
  257. // Delete digest and subscription preferences.
  258. $DB->delete_records('forum_digests', array('forum' => $forum->id));
  259. $DB->delete_records('forum_subscriptions', array('forum'=>$forum->id));
  260. $DB->delete_records('forum_discussion_subs', array('forum' => $forum->id));
  261. if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
  262. foreach ($discussions as $discussion) {
  263. if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
  264. $result = false;
  265. }
  266. }
  267. }
  268. forum_tp_delete_read_records(-1, -1, -1, $forum->id);
  269. forum_grade_item_delete($forum);
  270. // We must delete the module record after we delete the grade item.
  271. if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
  272. $result = false;
  273. }
  274. return $result;
  275. }
  276. /**
  277. * Indicates API features that the forum supports.
  278. *
  279. * @uses FEATURE_GROUPS
  280. * @uses FEATURE_GROUPINGS
  281. * @uses FEATURE_MOD_INTRO
  282. * @uses FEATURE_COMPLETION_TRACKS_VIEWS
  283. * @uses FEATURE_COMPLETION_HAS_RULES
  284. * @uses FEATURE_GRADE_HAS_GRADE
  285. * @uses FEATURE_GRADE_OUTCOMES
  286. * @param string $feature
  287. * @return mixed True if yes (some features may use other values)
  288. */
  289. function forum_supports($feature) {
  290. switch($feature) {
  291. case FEATURE_GROUPS: return true;
  292. case FEATURE_GROUPINGS: return true;
  293. case FEATURE_MOD_INTRO: return true;
  294. case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
  295. case FEATURE_COMPLETION_HAS_RULES: return true;
  296. case FEATURE_GRADE_HAS_GRADE: return true;
  297. case FEATURE_GRADE_OUTCOMES: return true;
  298. case FEATURE_RATE: return true;
  299. case FEATURE_BACKUP_MOODLE2: return true;
  300. case FEATURE_SHOW_DESCRIPTION: return true;
  301. case FEATURE_PLAGIARISM: return true;
  302. case FEATURE_ADVANCED_GRADING: return true;
  303. default: return null;
  304. }
  305. }
  306. /**
  307. * Create a message-id string to use in the custom headers of forum notification emails
  308. *
  309. * message-id is used by email clients to identify emails and to nest conversations
  310. *
  311. * @param int $postid The ID of the forum post we are notifying the user about
  312. * @param int $usertoid The ID of the user being notified
  313. * @return string A unique message-id
  314. */
  315. function forum_get_email_message_id($postid, $usertoid) {
  316. return generate_email_messageid(hash('sha256', $postid . 'to' . $usertoid));
  317. }
  318. /**
  319. *
  320. * @param object $course
  321. * @param object $user
  322. * @param object $mod TODO this is not used in this function, refactor
  323. * @param object $forum
  324. * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
  325. */
  326. function forum_user_outline($course, $user, $mod, $forum) {
  327. global $CFG;
  328. require_once("$CFG->libdir/gradelib.php");
  329. $gradeinfo = '';
  330. $gradetime = 0;
  331. $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
  332. if (!empty($grades->items[0]->grades)) {
  333. // Item 0 is the rating.
  334. $grade = reset($grades->items[0]->grades);
  335. $gradetime = max($gradetime, grade_get_date_for_user_grade($grade, $user));
  336. if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
  337. $gradeinfo .= get_string('gradeforrating', 'forum', $grade) . html_writer::empty_tag('br');
  338. } else {
  339. $gradeinfo .= get_string('gradeforratinghidden', 'forum') . html_writer::empty_tag('br');
  340. }
  341. }
  342. // Item 1 is the whole-forum grade.
  343. if (!empty($grades->items[1]->grades)) {
  344. $grade = reset($grades->items[1]->grades);
  345. $gradetime = max($gradetime, grade_get_date_for_user_grade($grade, $user));
  346. if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
  347. $gradeinfo .= get_string('gradeforwholeforum', 'forum', $grade) . html_writer::empty_tag('br');
  348. } else {
  349. $gradeinfo .= get_string('gradeforwholeforumhidden', 'forum') . html_writer::empty_tag('br');
  350. }
  351. }
  352. $count = forum_count_user_posts($forum->id, $user->id);
  353. if ($count && $count->postcount > 0) {
  354. $info = get_string("numposts", "forum", $count->postcount);
  355. $time = $count->lastpost;
  356. if ($gradeinfo) {
  357. $info .= ', ' . $gradeinfo;
  358. $time = max($time, $gradetime);
  359. }
  360. return (object) [
  361. 'info' => $info,
  362. 'time' => $time,
  363. ];
  364. } else if ($gradeinfo) {
  365. return (object) [
  366. 'info' => $gradeinfo,
  367. 'time' => $gradetime,
  368. ];
  369. }
  370. return null;
  371. }
  372. /**
  373. * @global object
  374. * @global object
  375. * @param object $coure
  376. * @param object $user
  377. * @param object $mod
  378. * @param object $forum
  379. */
  380. function forum_user_complete($course, $user, $mod, $forum) {
  381. global $CFG, $USER;
  382. require_once("$CFG->libdir/gradelib.php");
  383. $getgradeinfo = function($grades, string $type) use ($course): string {
  384. global $OUTPUT;
  385. if (empty($grades)) {
  386. return '';
  387. }
  388. $result = '';
  389. $grade = reset($grades);
  390. if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
  391. $result .= $OUTPUT->container(get_string("gradefor{$type}", "forum", $grade));
  392. if ($grade->str_feedback) {
  393. $result .= $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
  394. }
  395. } else {
  396. $result .= $OUTPUT->container(get_string("gradefor{$type}hidden", "forum"));
  397. }
  398. return $result;
  399. };
  400. $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
  401. // Item 0 is the rating.
  402. if (!empty($grades->items[0]->grades)) {
  403. echo $getgradeinfo($grades->items[0]->grades, 'rating');
  404. }
  405. // Item 1 is the whole-forum grade.
  406. if (!empty($grades->items[1]->grades)) {
  407. echo $getgradeinfo($grades->items[1]->grades, 'wholeforum');
  408. }
  409. if ($posts = forum_get_user_posts($forum->id, $user->id)) {
  410. if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
  411. print_error('invalidcoursemodule');
  412. }
  413. $context = context_module::instance($cm->id);
  414. $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
  415. $posts = array_filter($posts, function($post) use ($discussions) {
  416. return isset($discussions[$post->discussion]);
  417. });
  418. $entityfactory = mod_forum\local\container::get_entity_factory();
  419. $rendererfactory = mod_forum\local\container::get_renderer_factory();
  420. $postrenderer = $rendererfactory->get_posts_renderer();
  421. echo $postrenderer->render(
  422. $USER,
  423. [$forum->id => $entityfactory->get_forum_from_stdclass($forum, $context, $cm, $course)],
  424. array_map(function($discussion) use ($entityfactory) {
  425. return $entityfactory->get_discussion_from_stdclass($discussion);
  426. }, $discussions),
  427. array_map(function($post) use ($entityfactory) {
  428. return $entityfactory->get_post_from_stdclass($post);
  429. }, $posts)
  430. );
  431. } else {
  432. echo "<p>".get_string("noposts", "forum")."</p>";
  433. }
  434. }
  435. /**
  436. * @deprecated since Moodle 3.3, when the block_course_overview block was removed.
  437. */
  438. function forum_filter_user_groups_discussions() {
  439. throw new coding_exception('forum_filter_user_groups_discussions() can not be used any more and is obsolete.');
  440. }
  441. /**
  442. * Returns whether the discussion group is visible by the current user or not.
  443. *
  444. * @since Moodle 2.8, 2.7.1, 2.6.4
  445. * @param cm_info $cm The discussion course module
  446. * @param int $discussiongroupid The discussion groupid
  447. * @return bool
  448. */
  449. function forum_is_user_group_discussion(cm_info $cm, $discussiongroupid) {
  450. if ($discussiongroupid == -1 || $cm->effectivegroupmode != SEPARATEGROUPS) {
  451. return true;
  452. }
  453. if (isguestuser()) {
  454. return false;
  455. }
  456. if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id)) ||
  457. in_array($discussiongroupid, $cm->get_modinfo()->get_groups($cm->groupingid))) {
  458. return true;
  459. }
  460. return false;
  461. }
  462. /**
  463. * @deprecated since Moodle 3.3, when the block_course_overview block was removed.
  464. */
  465. function forum_print_overview() {
  466. throw new coding_exception('forum_print_overview() can not be used any more and is obsolete.');
  467. }
  468. /**
  469. * Given a course and a date, prints a summary of all the new
  470. * messages posted in the course since that date
  471. *
  472. * @global object
  473. * @global object
  474. * @global object
  475. * @uses CONTEXT_MODULE
  476. * @uses VISIBLEGROUPS
  477. * @param object $course
  478. * @param bool $viewfullnames capability
  479. * @param int $timestart
  480. * @return bool success
  481. */
  482. function forum_print_recent_activity($course, $viewfullnames, $timestart) {
  483. global $USER, $DB, $OUTPUT;
  484. // do not use log table if possible, it may be huge and is expensive to join with other tables
  485. $userfieldsapi = \core_user\fields::for_userpic();
  486. $allnamefields = $userfieldsapi->get_sql('u', false, '', 'duserid', false)->selects;
  487. if (!$posts = $DB->get_records_sql("SELECT p.*,
  488. f.course, f.type AS forumtype, f.name AS forumname, f.intro, f.introformat, f.duedate,
  489. f.cutoffdate, f.assessed AS forumassessed, f.assesstimestart, f.assesstimefinish,
  490. f.scale, f.grade_forum, f.maxbytes, f.maxattachments, f.forcesubscribe,
  491. f.trackingtype, f.rsstype, f.rssarticles, f.timemodified, f.warnafter, f.blockafter,
  492. f.blockperiod, f.completiondiscussions, f.completionreplies, f.completionposts,
  493. f.displaywordcount, f.lockdiscussionafter, f.grade_forum_notify,
  494. d.name AS discussionname, d.firstpost, d.userid AS discussionstarter,
  495. d.assessed AS discussionassessed, d.timemodified, d.usermodified, d.forum, d.groupid,
  496. d.timestart, d.timeend, d.pinned, d.timelocked,
  497. $allnamefields
  498. FROM {forum_posts} p
  499. JOIN {forum_discussions} d ON d.id = p.discussion
  500. JOIN {forum} f ON f.id = d.forum
  501. JOIN {user} u ON u.id = p.userid
  502. WHERE p.created > ? AND f.course = ? AND p.deleted <> 1
  503. ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
  504. return false;
  505. }
  506. $modinfo = get_fast_modinfo($course);
  507. $strftimerecent = get_string('strftimerecent');
  508. $managerfactory = mod_forum\local\container::get_manager_factory();
  509. $entityfactory = mod_forum\local\container::get_entity_factory();
  510. $discussions = [];
  511. $capmanagers = [];
  512. $printposts = [];
  513. foreach ($posts as $post) {
  514. if (!isset($modinfo->instances['forum'][$post->forum])) {
  515. // not visible
  516. continue;
  517. }
  518. $cm = $modinfo->instances['forum'][$post->forum];
  519. if (!$cm->uservisible) {
  520. continue;
  521. }
  522. // Get the discussion. Cache if not yet available.
  523. if (!isset($discussions[$post->discussion])) {
  524. // Build the discussion record object from the post data.
  525. $discussionrecord = (object)[
  526. 'id' => $post->discussion,
  527. 'course' => $post->course,
  528. 'forum' => $post->forum,
  529. 'name' => $post->discussionname,
  530. 'firstpost' => $post->firstpost,
  531. 'userid' => $post->discussionstarter,
  532. 'groupid' => $post->groupid,
  533. 'assessed' => $post->discussionassessed,
  534. 'timemodified' => $post->timemodified,
  535. 'usermodified' => $post->usermodified,
  536. 'timestart' => $post->timestart,
  537. 'timeend' => $post->timeend,
  538. 'pinned' => $post->pinned,
  539. 'timelocked' => $post->timelocked
  540. ];
  541. // Build the discussion entity from the factory and cache it.
  542. $discussions[$post->discussion] = $entityfactory->get_discussion_from_stdclass($discussionrecord);
  543. }
  544. $discussionentity = $discussions[$post->discussion];
  545. // Get the capability manager. Cache if not yet available.
  546. if (!isset($capmanagers[$post->forum])) {
  547. $context = context_module::instance($cm->id);
  548. $coursemodule = $cm->get_course_module_record();
  549. // Build the forum record object from the post data.
  550. $forumrecord = (object)[
  551. 'id' => $post->forum,
  552. 'course' => $post->course,
  553. 'type' => $post->forumtype,
  554. 'name' => $post->forumname,
  555. 'intro' => $post->intro,
  556. 'introformat' => $post->introformat,
  557. 'duedate' => $post->duedate,
  558. 'cutoffdate' => $post->cutoffdate,
  559. 'assessed' => $post->forumassessed,
  560. 'assesstimestart' => $post->assesstimestart,
  561. 'assesstimefinish' => $post->assesstimefinish,
  562. 'scale' => $post->scale,
  563. 'grade_forum' => $post->grade_forum,
  564. 'maxbytes' => $post->maxbytes,
  565. 'maxattachments' => $post->maxattachments,
  566. 'forcesubscribe' => $post->forcesubscribe,
  567. 'trackingtype' => $post->trackingtype,
  568. 'rsstype' => $post->rsstype,
  569. 'rssarticles' => $post->rssarticles,
  570. 'timemodified' => $post->timemodified,
  571. 'warnafter' => $post->warnafter,
  572. 'blockafter' => $post->blockafter,
  573. 'blockperiod' => $post->blockperiod,
  574. 'completiondiscussions' => $post->completiondiscussions,
  575. 'completionreplies' => $post->completionreplies,
  576. 'completionposts' => $post->completionposts,
  577. 'displaywordcount' => $post->displaywordcount,
  578. 'lockdiscussionafter' => $post->lockdiscussionafter,
  579. 'grade_forum_notify' => $post->grade_forum_notify
  580. ];
  581. // Build the forum entity from the factory.
  582. $forumentity = $entityfactory->get_forum_from_stdclass($forumrecord, $context, $coursemodule, $course);
  583. // Get the capability manager of this forum and cache it.
  584. $capmanagers[$post->forum] = $managerfactory->get_capability_manager($forumentity);
  585. }
  586. $capabilitymanager = $capmanagers[$post->forum];
  587. // Get the post entity.
  588. $postentity = $entityfactory->get_post_from_stdclass($post);
  589. // Check if the user can view the post.
  590. if ($capabilitymanager->can_view_post($USER, $discussionentity, $postentity)) {
  591. $printposts[] = $post;
  592. }
  593. }
  594. unset($posts);
  595. if (!$printposts) {
  596. return false;
  597. }
  598. echo $OUTPUT->heading(get_string('newforumposts', 'forum') . ':', 6);
  599. $list = html_writer::start_tag('ul', ['class' => 'unlist']);
  600. foreach ($printposts as $post) {
  601. $subjectclass = empty($post->parent) ? ' bold' : '';
  602. $authorhidden = forum_is_author_hidden($post, (object) ['type' => $post->forumtype]);
  603. $list .= html_writer::start_tag('li');
  604. $list .= html_writer::start_div('head');
  605. $list .= html_writer::div(userdate_htmltime($post->modified, $strftimerecent), 'date');
  606. if (!$authorhidden) {
  607. $list .= html_writer::div(fullname($post, $viewfullnames), 'name');
  608. }
  609. $list .= html_writer::end_div(); // Head.
  610. $list .= html_writer::start_div('info' . $subjectclass);
  611. $discussionurl = new moodle_url('/mod/forum/discuss.php', ['d' => $post->discussion]);
  612. if (!empty($post->parent)) {
  613. $discussionurl->param('parent', $post->parent);
  614. $discussionurl->set_anchor('p'. $post->id);
  615. }
  616. $post->subject = break_up_long_words(format_string($post->subject, true));
  617. $list .= html_writer::link($discussionurl, $post->subject, ['rel' => 'bookmark']);
  618. $list .= html_writer::end_div(); // Info.
  619. $list .= html_writer::end_tag('li');
  620. }
  621. $list .= html_writer::end_tag('ul');
  622. echo $list;
  623. return true;
  624. }
  625. /**
  626. * Update activity grades.
  627. *
  628. * @param object $forum
  629. * @param int $userid specific user only, 0 means all
  630. */
  631. function forum_update_grades($forum, $userid = 0): void {
  632. global $CFG, $DB;
  633. require_once($CFG->libdir.'/gradelib.php');
  634. $ratings = null;
  635. if ($forum->assessed) {
  636. require_once($CFG->dirroot.'/rating/lib.php');
  637. $cm = get_coursemodule_from_instance('forum', $forum->id);
  638. $rm = new rating_manager();
  639. $ratings = $rm->get_user_grades((object) [
  640. 'component' => 'mod_forum',
  641. 'ratingarea' => 'post',
  642. 'contextid' => \context_module::instance($cm->id)->id,
  643. 'modulename' => 'forum',
  644. 'moduleid ' => $forum->id,
  645. 'userid' => $userid,
  646. 'aggregationmethod' => $forum->assessed,
  647. 'scaleid' => $forum->scale,
  648. 'itemtable' => 'forum_posts',
  649. 'itemtableusercolumn' => 'userid',
  650. ]);
  651. }
  652. $forumgrades = null;
  653. if ($forum->grade_forum) {
  654. $sql = <<<EOF
  655. SELECT
  656. g.userid,
  657. 0 as datesubmitted,
  658. g.grade as rawgrade,
  659. g.timemodified as dategraded
  660. FROM {forum} f
  661. JOIN {forum_grades} g ON g.forum = f.id
  662. WHERE f.id = :forumid
  663. EOF;
  664. $params = [
  665. 'forumid' => $forum->id,
  666. ];
  667. if ($userid) {
  668. $sql .= " AND g.userid = :userid";
  669. $params['userid'] = $userid;
  670. }
  671. $forumgrades = [];
  672. if ($grades = $DB->get_recordset_sql($sql, $params)) {
  673. foreach ($grades as $userid => $grade) {
  674. if ($grade->rawgrade != -1) {
  675. $forumgrades[$userid] = $grade;
  676. }
  677. }
  678. $grades->close();
  679. }
  680. }
  681. forum_grade_item_update($forum, $ratings, $forumgrades);
  682. }
  683. /**
  684. * Create/update grade items for given forum.
  685. *
  686. * @param stdClass $forum Forum object with extra cmidnumber
  687. * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
  688. */
  689. function forum_grade_item_update($forum, $ratings = null, $forumgrades = null): void {
  690. global $CFG;
  691. require_once("{$CFG->libdir}/gradelib.php");
  692. // Update the rating.
  693. $item = [
  694. 'itemname' => get_string('gradeitemnameforrating', 'forum', $forum),
  695. 'idnumber' => $forum->cmidnumber,
  696. ];
  697. if (!$forum->assessed || $forum->scale == 0) {
  698. $item['gradetype'] = GRADE_TYPE_NONE;
  699. } else if ($forum->scale > 0) {
  700. $item['gradetype'] = GRADE_TYPE_VALUE;
  701. $item['grademax'] = $forum->scale;
  702. $item['grademin'] = 0;
  703. } else if ($forum->scale < 0) {
  704. $item['gradetype'] = GRADE_TYPE_SCALE;
  705. $item['scaleid'] = -$forum->scale;
  706. }
  707. if ($ratings === 'reset') {
  708. $item['reset'] = true;
  709. $ratings = null;
  710. }
  711. // Itemnumber 0 is the rating.
  712. grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $ratings, $item);
  713. // Whole forum grade.
  714. $item = [
  715. 'itemname' => get_string('gradeitemnameforwholeforum', 'forum', $forum),
  716. // Note: We do not need to store the idnumber here.
  717. ];
  718. if (!$forum->grade_forum) {
  719. $item['gradetype'] = GRADE_TYPE_NONE;
  720. } else if ($forum->grade_forum > 0) {
  721. $item['gradetype'] = GRADE_TYPE_VALUE;
  722. $item['grademax'] = $forum->grade_forum;
  723. $item['grademin'] = 0;
  724. } else if ($forum->grade_forum < 0) {
  725. $item['gradetype'] = GRADE_TYPE_SCALE;
  726. $item['scaleid'] = $forum->grade_forum * -1;
  727. }
  728. if ($forumgrades === 'reset') {
  729. $item['reset'] = true;
  730. $forumgrades = null;
  731. }
  732. // Itemnumber 1 is the whole forum grade.
  733. grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 1, $forumgrades, $item);
  734. }
  735. /**
  736. * Delete grade item for given forum.
  737. *
  738. * @param stdClass $forum Forum object
  739. */
  740. function forum_grade_item_delete($forum) {
  741. global $CFG;
  742. require_once($CFG->libdir.'/gradelib.php');
  743. grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, null, ['deleted' => 1]);
  744. grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 1, null, ['deleted' => 1]);
  745. }
  746. /**
  747. * Checks if scale is being used by any instance of forum.
  748. *
  749. * This is used to find out if scale used anywhere.
  750. *
  751. * @param $scaleid int
  752. * @return boolean True if the scale is used by any forum
  753. */
  754. function forum_scale_used_anywhere(int $scaleid): bool {
  755. global $DB;
  756. if (empty($scaleid)) {
  757. return false;
  758. }
  759. return $DB->record_exists_select('forum', "scale = ? and assessed > 0", [$scaleid * -1]);
  760. }
  761. // SQL FUNCTIONS ///////////////////////////////////////////////////////////
  762. /**
  763. * Gets a post with all info ready for forum_print_post
  764. * Most of these joins are just to get the forum id
  765. *
  766. * @global object
  767. * @global object
  768. * @param int $postid
  769. * @return mixed array of posts or false
  770. */
  771. function forum_get_post_full($postid) {
  772. global $CFG, $DB;
  773. $userfieldsapi = \core_user\fields::for_name();
  774. $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
  775. return $DB->get_record_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
  776. FROM {forum_posts} p
  777. JOIN {forum_discussions} d ON p.discussion = d.id
  778. LEFT JOIN {user} u ON p.userid = u.id
  779. WHERE p.id = ?", array($postid));
  780. }
  781. /**
  782. * Gets all posts in discussion including top parent.
  783. *
  784. * @param int $discussionid The Discussion to fetch.
  785. * @param string $sort The sorting to apply.
  786. * @param bool $tracking Whether the user tracks this forum.
  787. * @return array The posts in the discussion.
  788. */
  789. function forum_get_all_discussion_posts($discussionid, $sort, $tracking = false) {
  790. global $CFG, $DB, $USER;
  791. $tr_sel = "";
  792. $tr_join = "";
  793. $params = array();
  794. if ($tracking) {
  795. $tr_sel = ", fr.id AS postread";
  796. $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
  797. $params[] = $USER->id;
  798. }
  799. $userfieldsapi = \core_user\fields::for_name();
  800. $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
  801. $params[] = $discussionid;
  802. if (!$posts = $DB->get_records_sql("SELECT p.*, $allnames, u.email, u.picture, u.imagealt $tr_sel
  803. FROM {forum_posts} p
  804. LEFT JOIN {user} u ON p.userid = u.id
  805. $tr_join
  806. WHERE p.discussion = ?
  807. ORDER BY $sort", $params)) {
  808. return array();
  809. }
  810. foreach ($posts as $pid=>$p) {
  811. if ($tracking) {
  812. if (forum_tp_is_post_old($p)) {
  813. $posts[$pid]->postread = true;
  814. }
  815. }
  816. if (!$p->parent) {
  817. continue;
  818. }
  819. if (!isset($posts[$p->parent])) {
  820. continue; // parent does not exist??
  821. }
  822. if (!isset($posts[$p->parent]->children)) {
  823. $posts[$p->parent]->children = array();
  824. }
  825. $posts[$p->parent]->children[$pid] =& $posts[$pid];
  826. }
  827. // Start with the last child of the first post.
  828. $post = &$posts[reset($posts)->id];
  829. $lastpost = false;
  830. while (!$lastpost) {
  831. if (!isset($post->children)) {
  832. $post->lastpost = true;
  833. $lastpost = true;
  834. } else {
  835. // Go to the last child of this post.
  836. $post = &$posts[end($post->children)->id];
  837. }
  838. }
  839. return $posts;
  840. }
  841. /**
  842. * An array of forum objects that the user is allowed to read/search through.
  843. *
  844. * @global object
  845. * @global object
  846. * @global object
  847. * @param int $userid
  848. * @param int $courseid if 0, we look for forums throughout the whole site.
  849. * @return array of forum objects, or false if no matches
  850. * Forum objects have the following attributes:
  851. * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
  852. * viewhiddentimedposts
  853. */
  854. function forum_get_readable_forums($userid, $courseid=0) {
  855. global $CFG, $DB, $USER;
  856. require_once($CFG->dirroot.'/course/lib.php');
  857. if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
  858. print_error('notinstalled', 'forum');
  859. }
  860. if ($courseid) {
  861. $courses = $DB->get_records('course', array('id' => $courseid));
  862. } else {
  863. // If no course is specified, then the user can see SITE + his courses.
  864. $courses1 = $DB->get_records('course', array('id' => SITEID));
  865. $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
  866. $courses = array_merge($courses1, $courses2);
  867. }
  868. if (!$courses) {
  869. return array();
  870. }
  871. $readableforums = array();
  872. foreach ($courses as $course) {
  873. $modinfo = get_fast_modinfo($course);
  874. if (empty($modinfo->instances['forum'])) {
  875. // hmm, no forums?
  876. continue;
  877. }
  878. $courseforums = $DB->get_records('forum', array('course' => $course->id));
  879. foreach ($modinfo->instances['forum'] as $forumid => $cm) {
  880. if (!$cm->uservisible or !isset($courseforums[$forumid])) {
  881. continue;
  882. }
  883. $context = context_module::instance($cm->id);
  884. $forum = $courseforums[$forumid];
  885. $forum->context = $context;
  886. $forum->cm = $cm;
  887. if (!has_capability('mod/forum:viewdiscussion', $context)) {
  888. continue;
  889. }
  890. /// group access
  891. if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
  892. $forum->onlygroups = $modinfo->get_groups($cm->groupingid);
  893. $forum->onlygroups[] = -1;
  894. }
  895. /// hidden timed discussions
  896. $forum->viewhiddentimedposts = true;
  897. if (!empty($CFG->forum_enabletimedposts)) {
  898. if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
  899. $forum->viewhiddentimedposts = false;
  900. }
  901. }
  902. /// qanda access
  903. if ($forum->type == 'qanda'
  904. && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
  905. // We need to check whether the user has posted in the qanda forum.
  906. $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
  907. // the user is allowed to see in this forum.
  908. if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
  909. foreach ($discussionspostedin as $d) {
  910. $forum->onlydiscussions[] = $d->id;
  911. }
  912. }
  913. }
  914. $readableforums[$forum->id] = $forum;
  915. }
  916. unset($modinfo);
  917. } // End foreach $courses
  918. return $readableforums;
  919. }
  920. /**
  921. * Returns a list of posts found using an array of search terms.
  922. *
  923. * @global object
  924. * @global object
  925. * @global object
  926. * @param array $searchterms array of search terms, e.g. word +word -word
  927. * @param int $courseid if 0, we search through the whole site
  928. * @param int $limitfrom
  929. * @param int $limitnum
  930. * @param int &$totalcount
  931. * @param string $extrasql
  932. * @return array|bool Array of posts found or false
  933. */
  934. function forum_search_posts($searchterms, $courseid, $limitfrom, $limitnum,
  935. &$totalcount, $extrasql='') {
  936. global $CFG, $DB, $USER;
  937. require_once($CFG->libdir.'/searchlib.php');
  938. $forums = forum_get_readable_forums($USER->id, $courseid);
  939. if (count($forums) == 0) {
  940. $totalcount = 0;
  941. return false;
  942. }
  943. $now = floor(time() / 60) * 60; // DB Cache Friendly.
  944. $fullaccess = array();
  945. $where = array();
  946. $params = array();
  947. foreach ($forums as $forumid => $forum) {
  948. $select = array();
  949. if (!$forum->viewhiddentimedposts) {
  950. $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
  951. $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
  952. }
  953. $cm = $forum->cm;
  954. $context = $forum->context;
  955. if ($forum->type == 'qanda'
  956. && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
  957. if (!empty($forum->onlydiscussions)) {
  958. list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
  959. $params = array_merge($params, $discussionid_params);
  960. $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
  961. } else {
  962. $select[] = "p.parent = 0";
  963. }
  964. }
  965. if (!empty($forum->onlygroups)) {
  966. list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
  967. $params = array_merge($params, $groupid_params);
  968. $select[] = "d.groupid $groupid_sql";
  969. }
  970. if ($select) {
  971. $selects = implode(" AND ", $select);
  972. $where[] = "(d.forum = :forum{$forumid} AND $selects)";
  973. $params['forum'.$forumid] = $forumid;
  974. } else {
  975. $fullaccess[] = $forumid;
  976. }
  977. }
  978. if ($fullaccess) {
  979. list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
  980. $params = array_merge($params, $fullid_params);
  981. $where[] = "(d.forum $fullid_sql)";
  982. }
  983. $favjoin = "";
  984. if (in_array('starredonly:on', $searchterms)) {
  985. $usercontext = context_user::instance($USER->id);
  986. $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
  987. list($favjoin, $favparams) = $ufservice->get_join_sql_by_type('mod_forum', 'discussions',
  988. "favourited", "d.id");
  989. $searchterms = array_values(array_diff($searchterms, array('starredonly:on')));
  990. $params = array_merge($params, $favparams);
  991. $extrasql .= " AND favourited.itemid IS NOT NULL AND favourited.itemid != 0";
  992. }
  993. $selectdiscussion = "(".implode(" OR ", $where).")";
  994. $messagesearch = '';
  995. $searchstring = '';
  996. // Need to concat these back together for parser to work.
  997. foreach($searchterms as $searchterm){
  998. if ($searchstring != '') {
  999. $searchstring .= ' ';
  1000. }
  1001. $searchstring .= $searchterm;
  1002. }
  1003. // We need to allow quoted strings for the search. The quotes *should* be stripped
  1004. // by the parser, but this should be examined carefully for security implications.
  1005. $searchstring = str_replace("\\\"","\"",$searchstring);
  1006. $parser = new search_parser();
  1007. $lexer = new search_lexer($parser);
  1008. if ($lexer->parse($searchstring)) {
  1009. $parsearray = $parser->get_parsed_array();
  1010. $tagjoins = '';
  1011. $tagfields = [];
  1012. $tagfieldcount = 0;
  1013. if ($parsearray) {
  1014. foreach ($parsearray as $token) {
  1015. if ($token->getType() == TOKEN_TAGS) {
  1016. for ($i = 0; $i <= substr_count($token->getValue(), ','); $i++) {
  1017. // Queries can only have a limited number of joins so set a limit sensible users won't exceed.
  1018. if ($tagfieldcount > 10) {
  1019. continue;
  1020. }
  1021. $tagjoins .= " LEFT JOIN {tag_instance} ti_$tagfieldcount
  1022. ON p.id = ti_$tagfieldcount.itemid
  1023. AND ti_$tagfieldcount.component = 'mod_forum'
  1024. AND ti_$tagfieldcount.itemtype = 'forum_posts'";
  1025. $tagjoins .= " LEFT JOIN {tag} t_$tagfieldcount ON t_$tagfieldcount.id = ti_$tagfieldcount.tagid";
  1026. $tagfields[] = "t_$tagfieldcount.rawname";
  1027. $tagfieldcount++;
  1028. }
  1029. }
  1030. }
  1031. list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
  1032. 'p.userid', 'u.id', 'u.firstname',
  1033. 'u.lastname', 'p.modified', 'd.forum',
  1034. $tagfields);
  1035. $params = ($msparams ? array_merge($params, $msparams) : $params);
  1036. }
  1037. }
  1038. $fromsql = "{forum_posts} p
  1039. INNER JOIN {forum_discussions} d ON d.id = p.discussion
  1040. INNER JOIN {user} u ON u.id = p.userid $tagjoins $favjoin";
  1041. $selectsql = ($messagesearch ? $messagesearch . " AND " : "").
  1042. " p.discussion = d.id
  1043. AND p.userid = u.id
  1044. AND $selectdiscussion
  1045. $extrasql";
  1046. $countsql = "SELECT COUNT(*)
  1047. FROM $fromsql
  1048. WHERE $selectsql";
  1049. $userfieldsapi = \core_user\fields::for_name();
  1050. $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
  1051. $searchsql = "SELECT p.*,
  1052. d.forum,
  1053. $allnames,
  1054. u.email,
  1055. u.picture,
  1056. u.imagealt
  1057. FROM $fromsql
  1058. WHERE $selectsql
  1059. ORDER BY p.modified DESC";
  1060. $totalcount = $DB->count_records_sql($countsql, $params);
  1061. return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
  1062. }
  1063. /**
  1064. * Get all the posts for a user in a forum suitable for forum_print_post
  1065. *
  1066. * @global object
  1067. * @global object
  1068. * @uses CONTEXT_MODULE
  1069. * @return array
  1070. */
  1071. function forum_get_user_posts($forumid, $userid) {
  1072. global $CFG, $DB;
  1073. $timedsql = "";
  1074. $params = array($forumid, $userid);
  1075. if (!empty($CFG->forum_enabletimedposts)) {
  1076. $cm = get_coursemodule_from_instance('forum', $forumid);
  1077. if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
  1078. $now = time();
  1079. $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
  1080. $params[] = $now;
  1081. $params[] = $now;
  1082. }
  1083. }
  1084. $userfieldsapi = \core_user\fields::for_name();
  1085. $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
  1086. return $DB->get_records_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
  1087. FROM {forum} f
  1088. JOIN {forum_discussions} d ON d.forum = f.id
  1089. JOIN {forum_posts} p ON p.discussion = d.id
  1090. JOIN {user} u ON u.id = p.userid
  1091. WHERE f.id = ?
  1092. AND p.userid = ?
  1093. $timedsql
  1094. ORDER BY p.modified ASC", $params);
  1095. }
  1096. /**
  1097. * Get all the discussions user participated in
  1098. *
  1099. * @global object
  1100. * @global object
  1101. * @uses CONTEXT_MODULE
  1102. * @param int $forumid
  1103. * @param int $userid
  1104. * @return array Array or false
  1105. */
  1106. function forum_get_user_involved_discussions($forumid, $userid) {
  1107. global $CFG, $DB;
  1108. $timedsql = "";
  1109. $params = array($forumid, $userid);
  1110. if (!empty($CFG->forum_enabletimedposts)) {
  1111. $cm = get_coursemodule_from_instance('forum', $forumid);
  1112. if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
  1113. $now = time();
  1114. $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
  1115. $params[] = $now;
  1116. $params[] = $now;
  1117. }
  1118. }
  1119. return $DB->get_records_sql("SELECT DISTINCT d.*
  1120. FROM {forum} f
  1121. JOIN {forum_discussions} d ON d.forum = f.id
  1122. JOIN {forum_posts} p ON p.discussion = d.id
  1123. WHERE f.id = ?
  1124. AND p.userid = ?
  1125. $timedsql", $params);
  1126. }
  1127. /**
  1128. * Get all the posts for a user in a forum suitable for forum_print_post
  1129. *
  1130. * @global object
  1131. * @global object
  1132. * @param int $forumid
  1133. * @param int $userid
  1134. * @return array of counts or false
  1135. */
  1136. function forum_count_user_posts($forumid, $userid) {
  1137. global $CFG, $DB;
  1138. $timedsql = "";
  1139. $params = array($forumid, $userid);
  1140. if (!empty($CFG->forum_enabletimedposts)) {
  1141. $cm = get_coursemodule_from_instance('forum', $forumid);
  1142. if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
  1143. $now = time();
  1144. $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
  1145. $params[] = $now;
  1146. $params[] = $now;
  1147. }
  1148. }
  1149. return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
  1150. FROM {forum} f
  1151. JOIN {forum_discussions} d ON d.foru

Large files files are truncated, but you can click here to view the full file