PageRenderTime 65ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 1ms

/mod/forum/lib.php

https://bitbucket.org/ceu/moodle_demo
PHP | 6976 lines | 5155 code | 992 blank | 829 comment | 1034 complexity | c8d63c1982e7b59eb17a68b8404fb675 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, LGPL-2.1

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

  1. <?php // $Id$
  2. require_once($CFG->libdir.'/filelib.php');
  3. /// CONSTANTS ///////////////////////////////////////////////////////////
  4. define('FORUM_MODE_FLATOLDEST', 1);
  5. define('FORUM_MODE_FLATNEWEST', -1);
  6. define('FORUM_MODE_THREADED', 2);
  7. define('FORUM_MODE_NESTED', 3);
  8. define('FORUM_FORCESUBSCRIBE', 1);
  9. define('FORUM_INITIALSUBSCRIBE', 2);
  10. define('FORUM_DISALLOWSUBSCRIBE',3);
  11. define('FORUM_TRACKING_OFF', 0);
  12. define('FORUM_TRACKING_OPTIONAL', 1);
  13. define('FORUM_TRACKING_ON', 2);
  14. define('FORUM_UNSET_POST_RATING', -999);
  15. define ('FORUM_AGGREGATE_NONE', 0); //no ratings
  16. define ('FORUM_AGGREGATE_AVG', 1);
  17. define ('FORUM_AGGREGATE_COUNT', 2);
  18. define ('FORUM_AGGREGATE_MAX', 3);
  19. define ('FORUM_AGGREGATE_MIN', 4);
  20. define ('FORUM_AGGREGATE_SUM', 5);
  21. /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
  22. /**
  23. * Given an object containing all the necessary data,
  24. * (defined by the form in mod.html) this function
  25. * will create a new instance and return the id number
  26. * of the new instance.
  27. * @param object $forum add forum instance (with magic quotes)
  28. * @return int intance id
  29. */
  30. function forum_add_instance($forum) {
  31. global $CFG;
  32. $forum->timemodified = time();
  33. if (empty($forum->assessed)) {
  34. $forum->assessed = 0;
  35. }
  36. if (empty($forum->ratingtime) or empty($forum->assessed)) {
  37. $forum->assesstimestart = 0;
  38. $forum->assesstimefinish = 0;
  39. }
  40. if (!$forum->id = insert_record('forum', $forum)) {
  41. return false;
  42. }
  43. if ($forum->type == 'single') { // Create related discussion.
  44. $discussion = new object();
  45. $discussion->course = $forum->course;
  46. $discussion->forum = $forum->id;
  47. $discussion->name = $forum->name;
  48. $discussion->intro = $forum->intro;
  49. $discussion->assessed = $forum->assessed;
  50. $discussion->format = $forum->type;
  51. $discussion->mailnow = false;
  52. $discussion->groupid = -1;
  53. if (! forum_add_discussion($discussion, $discussion->intro)) {
  54. error('Could not add the discussion for this forum');
  55. }
  56. }
  57. if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
  58. /// all users should be subscribed initially
  59. /// Note: forum_get_potential_subscribers should take the forum context,
  60. /// but that does not exist yet, becuase the forum is only half build at this
  61. /// stage. However, because the forum is brand new, we know that there are
  62. /// no role assignments or overrides in the forum context, so using the
  63. /// course context gives the same list of users.
  64. $users = forum_get_potential_subscribers(get_context_instance(CONTEXT_COURSE, $forum->course), 0, 'u.id, u.email', '');
  65. foreach ($users as $user) {
  66. forum_subscribe($user->id, $forum->id);
  67. }
  68. }
  69. $forum = stripslashes_recursive($forum);
  70. forum_grade_item_update($forum);
  71. return $forum->id;
  72. }
  73. /**
  74. * Given an object containing all the necessary data,
  75. * (defined by the form in mod.html) this function
  76. * will update an existing instance with new data.
  77. * @param object $forum forum instance (with magic quotes)
  78. * @return bool success
  79. */
  80. function forum_update_instance($forum) {
  81. global $USER;
  82. $forum->timemodified = time();
  83. $forum->id = $forum->instance;
  84. if (empty($forum->assessed)) {
  85. $forum->assessed = 0;
  86. }
  87. if (empty($forum->ratingtime) or empty($forum->assessed)) {
  88. $forum->assesstimestart = 0;
  89. $forum->assesstimefinish = 0;
  90. }
  91. $oldforum = get_record('forum', 'id', $forum->id);
  92. // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
  93. // if scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
  94. // 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
  95. if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
  96. forum_update_grades($forum); // recalculate grades for the forum
  97. }
  98. if ($forum->type == 'single') { // Update related discussion and post.
  99. if (! $discussion = get_record('forum_discussions', 'forum', $forum->id)) {
  100. if ($discussions = get_records('forum_discussions', 'forum', $forum->id, 'timemodified ASC')) {
  101. notify('Warning! There is more than one discussion in this forum - using the most recent');
  102. $discussion = array_pop($discussions);
  103. } else {
  104. // try to recover by creating initial discussion - MDL-16262
  105. $discussion = new object();
  106. $discussion->course = $forum->course;
  107. $discussion->forum = $forum->id;
  108. $discussion->name = $forum->name;
  109. $discussion->intro = $forum->intro;
  110. $discussion->assessed = $forum->assessed;
  111. $discussion->format = $forum->type;
  112. $discussion->mailnow = false;
  113. $discussion->groupid = -1;
  114. forum_add_discussion($discussion, $discussion->intro);
  115. if (! $discussion = get_record('forum_discussions', 'forum', $forum->id)) {
  116. error('Could not add the discussion for this forum');
  117. }
  118. }
  119. }
  120. if (! $post = get_record('forum_posts', 'id', $discussion->firstpost)) {
  121. error('Could not find the first post in this forum discussion');
  122. }
  123. $post->subject = $forum->name;
  124. $post->message = $forum->intro;
  125. $post->modified = $forum->timemodified;
  126. $post->userid = $USER->id; // MDL-18599, so that current teacher can take ownership of activities
  127. if (! update_record('forum_posts', ($post))) {
  128. error('Could not update the first post');
  129. }
  130. $discussion->name = $forum->name;
  131. if (! update_record('forum_discussions', ($discussion))) {
  132. error('Could not update the discussion');
  133. }
  134. }
  135. if (!update_record('forum', $forum)) {
  136. error('Can not update forum');
  137. }
  138. $forum = stripslashes_recursive($forum);
  139. forum_grade_item_update($forum);
  140. return true;
  141. }
  142. /**
  143. * Given an ID of an instance of this module,
  144. * this function will permanently delete the instance
  145. * and any data that depends on it.
  146. * @param int forum instance id
  147. * @return bool success
  148. */
  149. function forum_delete_instance($id) {
  150. if (!$forum = get_record('forum', 'id', $id)) {
  151. return false;
  152. }
  153. $result = true;
  154. if ($discussions = get_records('forum_discussions', 'forum', $forum->id)) {
  155. foreach ($discussions as $discussion) {
  156. if (!forum_delete_discussion($discussion, true)) {
  157. $result = false;
  158. }
  159. }
  160. }
  161. if (!delete_records('forum_subscriptions', 'forum', $forum->id)) {
  162. $result = false;
  163. }
  164. forum_tp_delete_read_records(-1, -1, -1, $forum->id);
  165. if (!delete_records('forum', 'id', $forum->id)) {
  166. $result = false;
  167. }
  168. forum_grade_item_delete($forum);
  169. return $result;
  170. }
  171. /**
  172. * Function to be run periodically according to the moodle cron
  173. * Finds all posts that have yet to be mailed out, and mails them
  174. * out to all subscribers
  175. * @return void
  176. */
  177. function forum_cron() {
  178. global $CFG, $USER;
  179. $cronuser = clone($USER);
  180. $site = get_site();
  181. // all users that are subscribed to any post that needs sending
  182. $users = array();
  183. // status arrays
  184. $mailcount = array();
  185. $errorcount = array();
  186. // caches
  187. $discussions = array();
  188. $forums = array();
  189. $courses = array();
  190. $coursemodules = array();
  191. $subscribedusers = array();
  192. // Posts older than 2 days will not be mailed. This is to avoid the problem where
  193. // cron has not been running for a long time, and then suddenly people are flooded
  194. // with mail from the past few weeks or months
  195. $timenow = time();
  196. $endtime = $timenow - $CFG->maxeditingtime;
  197. $starttime = $endtime - 48 * 3600; // Two days earlier
  198. if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
  199. // Mark them all now as being mailed. It's unlikely but possible there
  200. // might be an error later so that a post is NOT actually mailed out,
  201. // but since mail isn't crucial, we can accept this risk. Doing it now
  202. // prevents the risk of duplicated mails, which is a worse problem.
  203. if (!forum_mark_old_posts_as_mailed($endtime)) {
  204. mtrace('Errors occurred while trying to mark some posts as being mailed.');
  205. return false; // Don't continue trying to mail them, in case we are in a cron loop
  206. }
  207. // checking post validity, and adding users to loop through later
  208. foreach ($posts as $pid => $post) {
  209. $discussionid = $post->discussion;
  210. if (!isset($discussions[$discussionid])) {
  211. if ($discussion = get_record('forum_discussions', 'id', $post->discussion)) {
  212. $discussions[$discussionid] = $discussion;
  213. } else {
  214. mtrace('Could not find discussion '.$discussionid);
  215. unset($posts[$pid]);
  216. continue;
  217. }
  218. }
  219. $forumid = $discussions[$discussionid]->forum;
  220. if (!isset($forums[$forumid])) {
  221. if ($forum = get_record('forum', 'id', $forumid)) {
  222. $forums[$forumid] = $forum;
  223. } else {
  224. mtrace('Could not find forum '.$forumid);
  225. unset($posts[$pid]);
  226. continue;
  227. }
  228. }
  229. $courseid = $forums[$forumid]->course;
  230. if (!isset($courses[$courseid])) {
  231. if ($course = get_record('course', 'id', $courseid)) {
  232. $courses[$courseid] = $course;
  233. } else {
  234. mtrace('Could not find course '.$courseid);
  235. unset($posts[$pid]);
  236. continue;
  237. }
  238. }
  239. if (!isset($coursemodules[$forumid])) {
  240. if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
  241. $coursemodules[$forumid] = $cm;
  242. } else {
  243. mtrace('Could not course module for forum '.$forumid);
  244. unset($posts[$pid]);
  245. continue;
  246. }
  247. }
  248. // caching subscribed users of each forum
  249. if (!isset($subscribedusers[$forumid])) {
  250. $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
  251. if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext)) {
  252. foreach ($subusers as $postuser) {
  253. // do not try to mail users with stopped email
  254. if ($postuser->emailstop) {
  255. if (!empty($CFG->forum_logblocked)) {
  256. add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
  257. }
  258. continue;
  259. }
  260. // this user is subscribed to this forum
  261. $subscribedusers[$forumid][$postuser->id] = $postuser->id;
  262. // this user is a user we have to process later
  263. $users[$postuser->id] = $postuser;
  264. }
  265. unset($subusers); // release memory
  266. }
  267. }
  268. $mailcount[$pid] = 0;
  269. $errorcount[$pid] = 0;
  270. }
  271. }
  272. if ($users && $posts) {
  273. $urlinfo = parse_url($CFG->wwwroot);
  274. $hostname = $urlinfo['host'];
  275. foreach ($users as $userto) {
  276. @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
  277. // set this so that the capabilities are cached, and environment matches receiving user
  278. $USER = $userto;
  279. mtrace('Processing user '.$userto->id);
  280. // init caches
  281. $userto->viewfullnames = array();
  282. $userto->canpost = array();
  283. $userto->markposts = array();
  284. $userto->enrolledin = array();
  285. // reset the caches
  286. foreach ($coursemodules as $forumid=>$unused) {
  287. $coursemodules[$forumid]->cache = new object();
  288. $coursemodules[$forumid]->cache->caps = array();
  289. unset($coursemodules[$forumid]->uservisible);
  290. }
  291. foreach ($posts as $pid => $post) {
  292. // Set up the environment for the post, discussion, forum, course
  293. $discussion = $discussions[$post->discussion];
  294. $forum = $forums[$discussion->forum];
  295. $course = $courses[$forum->course];
  296. $cm =& $coursemodules[$forum->id];
  297. // Do some checks to see if we can bail out now
  298. if (!isset($subscribedusers[$forum->id][$userto->id])) {
  299. continue; // user does not subscribe to this forum
  300. }
  301. // Verify user is enrollend in course - if not do not send any email
  302. if (!isset($userto->enrolledin[$course->id])) {
  303. $userto->enrolledin[$course->id] = has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $course->id));
  304. }
  305. if (!$userto->enrolledin[$course->id]) {
  306. // oops - this user should not receive anything from this course
  307. continue;
  308. }
  309. // Don't send email if the forum is Q&A and the user has not posted
  310. if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id)) {
  311. mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
  312. continue;
  313. }
  314. // Get info about the sending user
  315. if (array_key_exists($post->userid, $users)) { // we might know him/her already
  316. $userfrom = $users[$post->userid];
  317. } else if ($userfrom = get_record('user', 'id', $post->userid)) {
  318. $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
  319. } else {
  320. mtrace('Could not find user '.$post->userid);
  321. continue;
  322. }
  323. // setup global $COURSE properly - needed for roles and languages
  324. course_setup($course); // More environment
  325. // Fill caches
  326. if (!isset($userto->viewfullnames[$forum->id])) {
  327. $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
  328. $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
  329. }
  330. if (!isset($userto->canpost[$discussion->id])) {
  331. $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
  332. $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
  333. }
  334. if (!isset($userfrom->groups[$forum->id])) {
  335. if (!isset($userfrom->groups)) {
  336. $userfrom->groups = array();
  337. $users[$userfrom->id]->groups = array();
  338. }
  339. $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
  340. $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
  341. }
  342. // Make sure groups allow this user to see this email
  343. if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
  344. if (!groups_group_exists($discussion->groupid)) { // Can't find group
  345. continue; // Be safe and don't send it to anyone
  346. }
  347. if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
  348. // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
  349. continue;
  350. }
  351. }
  352. // Make sure we're allowed to see it...
  353. if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
  354. mtrace('user '.$userto->id. ' can not see '.$post->id);
  355. continue;
  356. }
  357. // OK so we need to send the email.
  358. // Does the user want this post in a digest? If so postpone it for now.
  359. if ($userto->maildigest > 0) {
  360. // This user wants the mails to be in digest form
  361. $queue = new object();
  362. $queue->userid = $userto->id;
  363. $queue->discussionid = $discussion->id;
  364. $queue->postid = $post->id;
  365. $queue->timemodified = $post->created;
  366. if (!insert_record('forum_queue', $queue)) {
  367. mtrace("Error: mod/forum/cron.php: Could not queue for digest mail for id $post->id to user $userto->id ($userto->email) .. not trying again.");
  368. }
  369. continue;
  370. }
  371. // Prepare to actually send the post now, and build up the content
  372. $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
  373. $userfrom->customheaders = array ( // Headers to make emails easier to track
  374. 'Precedence: Bulk',
  375. 'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
  376. 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
  377. 'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
  378. 'X-Course-Id: '.$course->id,
  379. 'X-Course-Name: '.format_string($course->fullname, true)
  380. );
  381. if ($post->parent) { // This post is a reply, so add headers for threading (see MDL-22551)
  382. $userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
  383. $userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
  384. }
  385. $postsubject = "$course->shortname: ".format_string($post->subject,true);
  386. $posttext = forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto);
  387. $posthtml = forum_make_mail_html($course, $forum, $discussion, $post, $userfrom, $userto);
  388. // Send the post now!
  389. mtrace('Sending ', '');
  390. if (!$mailresult = email_to_user($userto, $userfrom, $postsubject, $posttext,
  391. $posthtml, '', '', $CFG->forum_replytouser)) {
  392. mtrace("Error: mod/forum/cron.php: Could not send out mail for id $post->id to user $userto->id".
  393. " ($userto->email) .. not trying again.");
  394. add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
  395. substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
  396. $errorcount[$post->id]++;
  397. } else if ($mailresult === 'emailstop') {
  398. // should not be reached anymore - see check above
  399. } else {
  400. $mailcount[$post->id]++;
  401. // Mark post as read if forum_usermarksread is set off
  402. if (!$CFG->forum_usermarksread) {
  403. $userto->markposts[$post->id] = $post->id;
  404. }
  405. }
  406. mtrace('post '.$post->id. ': '.$post->subject);
  407. }
  408. // mark processed posts as read
  409. forum_tp_mark_posts_read($userto, $userto->markposts);
  410. }
  411. }
  412. if ($posts) {
  413. foreach ($posts as $post) {
  414. mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
  415. if ($errorcount[$post->id]) {
  416. set_field("forum_posts", "mailed", "2", "id", "$post->id");
  417. }
  418. }
  419. }
  420. // release some memory
  421. unset($subscribedusers);
  422. unset($mailcount);
  423. unset($errorcount);
  424. $USER = clone($cronuser);
  425. course_setup(SITEID);
  426. $sitetimezone = $CFG->timezone;
  427. // Now see if there are any digest mails waiting to be sent, and if we should send them
  428. mtrace('Starting digest processing...');
  429. @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
  430. if (!isset($CFG->digestmailtimelast)) { // To catch the first time
  431. set_config('digestmailtimelast', 0);
  432. }
  433. $timenow = time();
  434. $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
  435. // Delete any really old ones (normally there shouldn't be any)
  436. $weekago = $timenow - (7 * 24 * 3600);
  437. delete_records_select('forum_queue', "timemodified < $weekago");
  438. mtrace ('Cleaned old digest records');
  439. if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
  440. mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
  441. $digestposts_rs = get_recordset_select('forum_queue', "timemodified < $digesttime");
  442. if (!rs_EOF($digestposts_rs)) {
  443. // We have work to do
  444. $usermailcount = 0;
  445. //caches - reuse the those filled before too
  446. $discussionposts = array();
  447. $userdiscussions = array();
  448. while ($digestpost = rs_fetch_next_record($digestposts_rs)) {
  449. if (!isset($users[$digestpost->userid])) {
  450. if ($user = get_record('user', 'id', $digestpost->userid)) {
  451. $users[$digestpost->userid] = $user;
  452. } else {
  453. continue;
  454. }
  455. }
  456. $postuser = $users[$digestpost->userid];
  457. if ($postuser->emailstop) {
  458. if (!empty($CFG->forum_logblocked)) {
  459. add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
  460. }
  461. continue;
  462. }
  463. if (!isset($posts[$digestpost->postid])) {
  464. if ($post = get_record('forum_posts', 'id', $digestpost->postid)) {
  465. $posts[$digestpost->postid] = $post;
  466. } else {
  467. continue;
  468. }
  469. }
  470. $discussionid = $digestpost->discussionid;
  471. if (!isset($discussions[$discussionid])) {
  472. if ($discussion = get_record('forum_discussions', 'id', $discussionid)) {
  473. $discussions[$discussionid] = $discussion;
  474. } else {
  475. continue;
  476. }
  477. }
  478. $forumid = $discussions[$discussionid]->forum;
  479. if (!isset($forums[$forumid])) {
  480. if ($forum = get_record('forum', 'id', $forumid)) {
  481. $forums[$forumid] = $forum;
  482. } else {
  483. continue;
  484. }
  485. }
  486. $courseid = $forums[$forumid]->course;
  487. if (!isset($courses[$courseid])) {
  488. if ($course = get_record('course', 'id', $courseid)) {
  489. $courses[$courseid] = $course;
  490. } else {
  491. continue;
  492. }
  493. }
  494. if (!isset($coursemodules[$forumid])) {
  495. if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
  496. $coursemodules[$forumid] = $cm;
  497. } else {
  498. continue;
  499. }
  500. }
  501. $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
  502. $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
  503. }
  504. rs_close($digestposts_rs); /// Finished iteration, let's close the resultset
  505. // Data collected, start sending out emails to each user
  506. foreach ($userdiscussions as $userid => $thesediscussions) {
  507. @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
  508. $USER = $cronuser;
  509. course_setup(SITEID); // reset cron user language, theme and timezone settings
  510. mtrace(get_string('processingdigest', 'forum', $userid), '... ');
  511. // First of all delete all the queue entries for this user
  512. delete_records_select('forum_queue', "userid = $userid AND timemodified < $digesttime");
  513. $userto = $users[$userid];
  514. // Override the language and timezone of the "current" user, so that
  515. // mail is customised for the receiver.
  516. $USER = $userto;
  517. course_setup(SITEID);
  518. // init caches
  519. $userto->viewfullnames = array();
  520. $userto->canpost = array();
  521. $userto->markposts = array();
  522. $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
  523. $headerdata = new object();
  524. $headerdata->sitename = format_string($site->fullname, true);
  525. $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
  526. $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
  527. $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
  528. $posthtml = "<head>";
  529. foreach ($CFG->stylesheets as $stylesheet) {
  530. $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
  531. }
  532. $posthtml .= "</head>\n<body id=\"email\">\n";
  533. $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
  534. foreach ($thesediscussions as $discussionid) {
  535. @set_time_limit(120); // to be reset for each post
  536. $discussion = $discussions[$discussionid];
  537. $forum = $forums[$discussion->forum];
  538. $course = $courses[$forum->course];
  539. $cm = $coursemodules[$forum->id];
  540. //override language
  541. course_setup($course);
  542. // Fill caches
  543. if (!isset($userto->viewfullnames[$forum->id])) {
  544. $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
  545. $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
  546. }
  547. if (!isset($userto->canpost[$discussion->id])) {
  548. $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
  549. $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
  550. }
  551. $strforums = get_string('forums', 'forum');
  552. $canunsubscribe = ! forum_is_forcesubscribed($forum);
  553. $canreply = $userto->canpost[$discussion->id];
  554. $posttext .= "\n \n";
  555. $posttext .= '=====================================================================';
  556. $posttext .= "\n \n";
  557. $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
  558. if ($discussion->name != $forum->name) {
  559. $posttext .= " -> ".format_string($discussion->name,true);
  560. }
  561. $posttext .= "\n";
  562. $posthtml .= "<p><font face=\"sans-serif\">".
  563. "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
  564. "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
  565. "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
  566. if ($discussion->name == $forum->name) {
  567. $posthtml .= "</font></p>";
  568. } else {
  569. $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
  570. }
  571. $posthtml .= '<p>';
  572. $postsarray = $discussionposts[$discussionid];
  573. sort($postsarray);
  574. foreach ($postsarray as $postid) {
  575. $post = $posts[$postid];
  576. if (array_key_exists($post->userid, $users)) { // we might know him/her already
  577. $userfrom = $users[$post->userid];
  578. } else if ($userfrom = get_record('user', 'id', $post->userid)) {
  579. $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
  580. } else {
  581. mtrace('Could not find user '.$post->userid);
  582. continue;
  583. }
  584. if (!isset($userfrom->groups[$forum->id])) {
  585. if (!isset($userfrom->groups)) {
  586. $userfrom->groups = array();
  587. $users[$userfrom->id]->groups = array();
  588. }
  589. $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
  590. $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
  591. }
  592. $userfrom->customheaders = array ("Precedence: Bulk");
  593. if ($userto->maildigest == 2) {
  594. // Subjects only
  595. $by = new object();
  596. $by->name = fullname($userfrom);
  597. $by->date = userdate($post->modified);
  598. $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
  599. $posttext .= "\n---------------------------------------------------------------------";
  600. $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
  601. $posthtml .= '<div><a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'#p'.$post->id.'">'.format_string($post->subject,true).'</a> '.get_string("bynameondate", "forum", $by).'</div>';
  602. } else {
  603. // The full treatment
  604. $posttext .= forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto, true);
  605. $posthtml .= forum_make_mail_post($course, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
  606. // Create an array of postid's for this user to mark as read.
  607. if (!$CFG->forum_usermarksread) {
  608. $userto->markposts[$post->id] = $post->id;
  609. }
  610. }
  611. }
  612. if ($canunsubscribe) {
  613. $posthtml .= "\n<div class='mdl-right'><font size=\"1\"><a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">".get_string("unsubscribe", "forum")."</a></font></div>";
  614. } else {
  615. $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
  616. }
  617. $posthtml .= '<hr size="1" noshade="noshade" /></p>';
  618. }
  619. $posthtml .= '</body>';
  620. if ($userto->mailformat != 1) {
  621. // This user DOESN'T want to receive HTML
  622. $posthtml = '';
  623. }
  624. if (!$mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml,
  625. '', '', $CFG->forum_replytouser)) {
  626. mtrace("ERROR!");
  627. echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
  628. add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
  629. } else if ($mailresult === 'emailstop') {
  630. // should not happen anymore - see check above
  631. } else {
  632. mtrace("success.");
  633. $usermailcount++;
  634. // Mark post as read if forum_usermarksread is set off
  635. forum_tp_mark_posts_read($userto, $userto->markposts);
  636. }
  637. }
  638. }
  639. /// We have finishied all digest emails, update $CFG->digestmailtimelast
  640. set_config('digestmailtimelast', $timenow);
  641. }
  642. $USER = $cronuser;
  643. course_setup(SITEID); // reset cron user language, theme and timezone settings
  644. if (!empty($usermailcount)) {
  645. mtrace(get_string('digestsentusers', 'forum', $usermailcount));
  646. }
  647. if (!empty($CFG->forum_lastreadclean)) {
  648. $timenow = time();
  649. if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
  650. set_config('forum_lastreadclean', $timenow);
  651. mtrace('Removing old forum read tracking info...');
  652. forum_tp_clean_read_records();
  653. }
  654. } else {
  655. set_config('forum_lastreadclean', time());
  656. }
  657. return true;
  658. }
  659. /**
  660. * Builds and returns the body of the email notification in plain text.
  661. *
  662. * @param object $course
  663. * @param object $forum
  664. * @param object $discussion
  665. * @param object $post
  666. * @param object $userfrom
  667. * @param object $userto
  668. * @param boolean $bare
  669. * @return string The email body in plain text format.
  670. */
  671. function forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
  672. global $CFG, $USER;
  673. if (!isset($userto->viewfullnames[$forum->id])) {
  674. if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
  675. error('Course Module ID was incorrect');
  676. }
  677. $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
  678. $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
  679. } else {
  680. $viewfullnames = $userto->viewfullnames[$forum->id];
  681. }
  682. if (!isset($userto->canpost[$discussion->id])) {
  683. $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
  684. $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
  685. } else {
  686. $canreply = $userto->canpost[$discussion->id];
  687. }
  688. $by = New stdClass;
  689. $by->name = fullname($userfrom, $viewfullnames);
  690. $by->date = userdate($post->modified, "", $userto->timezone);
  691. $strbynameondate = get_string('bynameondate', 'forum', $by);
  692. $strforums = get_string('forums', 'forum');
  693. $canunsubscribe = ! forum_is_forcesubscribed($forum);
  694. $posttext = '';
  695. if (!$bare) {
  696. $posttext = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
  697. if ($discussion->name != $forum->name) {
  698. $posttext .= " -> ".format_string($discussion->name,true);
  699. }
  700. }
  701. $posttext .= "\n---------------------------------------------------------------------\n";
  702. $posttext .= format_string($post->subject,true);
  703. if ($bare) {
  704. $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
  705. }
  706. $posttext .= "\n".$strbynameondate."\n";
  707. $posttext .= "---------------------------------------------------------------------\n";
  708. $posttext .= format_text_email(trusttext_strip($post->message), $post->format);
  709. $posttext .= "\n\n";
  710. if ($post->attachment) {
  711. $post->course = $course->id;
  712. $post->forum = $forum->id;
  713. $posttext .= forum_print_attachments($post, "text");
  714. }
  715. if (!$bare && $canreply) {
  716. $posttext .= "---------------------------------------------------------------------\n";
  717. $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
  718. $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
  719. }
  720. if (!$bare && $canunsubscribe) {
  721. $posttext .= "\n---------------------------------------------------------------------\n";
  722. $posttext .= get_string("unsubscribe", "forum");
  723. $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
  724. }
  725. return $posttext;
  726. }
  727. /**
  728. * Builds and returns the body of the email notification in html format.
  729. *
  730. * @param object $course
  731. * @param object $forum
  732. * @param object $discussion
  733. * @param object $post
  734. * @param object $userfrom
  735. * @param object $userto
  736. * @return string The email text in HTML format
  737. */
  738. function forum_make_mail_html($course, $forum, $discussion, $post, $userfrom, $userto) {
  739. global $CFG;
  740. if ($userto->mailformat != 1) { // Needs to be HTML
  741. return '';
  742. }
  743. if (!isset($userto->canpost[$discussion->id])) {
  744. $canreply = forum_user_can_post($forum, $discussion, $userto);
  745. } else {
  746. $canreply = $userto->canpost[$discussion->id];
  747. }
  748. $strforums = get_string('forums', 'forum');
  749. $canunsubscribe = ! forum_is_forcesubscribed($forum);
  750. $posthtml = '<head>';
  751. foreach ($CFG->stylesheets as $stylesheet) {
  752. $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
  753. }
  754. $posthtml .= '</head>';
  755. $posthtml .= "\n<body id=\"email\">\n\n";
  756. $posthtml .= '<div class="navbar">'.
  757. '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
  758. '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
  759. '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
  760. if ($discussion->name == $forum->name) {
  761. $posthtml .= '</div>';
  762. } else {
  763. $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
  764. format_string($discussion->name,true).'</a></div>';
  765. }
  766. $posthtml .= forum_make_mail_post($course, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
  767. if ($canunsubscribe) {
  768. $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
  769. <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
  770. <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
  771. }
  772. $posthtml .= '</body>';
  773. return $posthtml;
  774. }
  775. /**
  776. *
  777. * @param object $course
  778. * @param object $user
  779. * @param object $mod TODO this is not used in this function, refactor
  780. * @param object $forum
  781. * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
  782. */
  783. function forum_user_outline($course, $user, $mod, $forum) {
  784. global $CFG;
  785. require_once("$CFG->libdir/gradelib.php");
  786. $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
  787. if (empty($grades->items[0]->grades)) {
  788. $grade = false;
  789. } else {
  790. $grade = reset($grades->items[0]->grades);
  791. }
  792. $count = forum_count_user_posts($forum->id, $user->id);
  793. if ($count && $count->postcount > 0) {
  794. $result = new object();
  795. $result->info = get_string("numposts", "forum", $count->postcount);
  796. $result->time = $count->lastpost;
  797. if ($grade) {
  798. $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
  799. }
  800. return $result;
  801. } else if ($grade) {
  802. $result = new object();
  803. $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
  804. //datesubmitted == time created. dategraded == time modified or time overridden
  805. //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
  806. if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
  807. $result->time = $grade->dategraded;
  808. } else {
  809. $result->time = $grade->datesubmitted;
  810. }
  811. return $result;
  812. }
  813. return NULL;
  814. }
  815. /**
  816. *
  817. */
  818. function forum_user_complete($course, $user, $mod, $forum) {
  819. global $CFG,$USER;
  820. require_once("$CFG->libdir/gradelib.php");
  821. $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
  822. if (!empty($grades->items[0]->grades)) {
  823. $grade = reset($grades->items[0]->grades);
  824. echo '<p>'.get_string('grade').': '.$grade->str_long_grade.'</p>';
  825. if ($grade->str_feedback) {
  826. echo '<p>'.get_string('feedback').': '.$grade->str_feedback.'</p>';
  827. }
  828. }
  829. if ($posts = forum_get_user_posts($forum->id, $user->id)) {
  830. if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
  831. error('Course Module ID was incorrect');
  832. }
  833. $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
  834. // preload all user ratings for these discussions - one query only and minimal memory
  835. $cm->cache->ratings = array();
  836. $cm->cache->myratings = array();
  837. if ($postratings = forum_get_all_user_ratings($user->id, $discussions)) {
  838. foreach ($postratings as $pr) {
  839. if (!isset($cm->cache->ratings[$pr->postid])) {
  840. $cm->cache->ratings[$pr->postid] = array();
  841. }
  842. $cm->cache->ratings[$pr->postid][$pr->id] = $pr->rating;
  843. if ($pr->userid == $USER->id) {
  844. $cm->cache->myratings[$pr->postid] = $pr->rating;
  845. }
  846. }
  847. unset($postratings);
  848. }
  849. foreach ($posts as $post) {
  850. if (!isset($discussions[$post->discussion])) {
  851. continue;
  852. }
  853. $discussion = $discussions[$post->discussion];
  854. $ratings = null;
  855. if ($forum->assessed) {
  856. if ($scale = make_grades_menu($forum->scale)) {
  857. $ratings =new object();
  858. $ratings->scale = $scale;
  859. $ratings->assesstimestart = $forum->assesstimestart;
  860. $ratings->assesstimefinish = $forum->assesstimefinish;
  861. $ratings->allow = false;
  862. }
  863. }
  864. forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false, $ratings);
  865. }
  866. } else {
  867. echo "<p>".get_string("noposts", "forum")."</p>";
  868. }
  869. }
  870. /**
  871. *
  872. */
  873. function forum_print_overview($courses,&$htmlarray) {
  874. global $USER, $CFG;
  875. //$LIKE = sql_ilike();//no longer using like in queries. MDL-20578
  876. if (empty($courses) || !is_array($courses) || count($courses) == 0) {
  877. return array();
  878. }
  879. if (!$forums = get_all_instances_in_courses('forum',$courses)) {
  880. return;
  881. }
  882. // get all forum logs in ONE query (much better!)
  883. $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {$CFG->prefix}log l "
  884. ." JOIN {$CFG->prefix}course_modules cm ON cm.id = cmid "
  885. ." WHERE (";
  886. foreach ($courses as $course) {
  887. $sql .= '(l.course = '.$course->id.' AND l.time > '.$course->lastaccess.') OR ';
  888. }
  889. $sql = substr($sql,0,-3); // take off the last OR
  890. $sql .= ") AND l.module = 'forum' AND action = 'add post' "
  891. ." AND userid != ".$USER->id." GROUP BY cmid,l.course,instance";
  892. if (!$new = get_records_sql($sql)) {
  893. $new = array(); // avoid warnings
  894. }
  895. // also get all forum tracking stuff ONCE.
  896. $trackingforums = array();
  897. foreach ($forums as $forum) {
  898. if (forum_tp_can_track_forums($forum)) {
  899. $trackingforums[$forum->id] = $forum;
  900. }
  901. }
  902. if (count($trackingforums) > 0) {
  903. $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
  904. $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
  905. ' FROM '.$CFG->prefix.'forum_posts p '.
  906. ' JOIN '.$CFG->prefix.'forum_discussions d ON p.discussion = d.id '.
  907. ' LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$USER->id.' WHERE (';
  908. foreach ($trackingforums as $track) {
  909. $sql .= '(d.forum = '.$track->id.' AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = '.get_current_group($track->course).')) OR ';
  910. }
  911. $sql = substr($sql,0,-3); // take off the last OR
  912. $sql .= ') AND p.modified >= '.$cutoffdate.' AND r.id is NULL GROUP BY d.forum,d.course';
  913. if (!$unread = get_records_sql($sql)) {
  914. $unread = array();
  915. }
  916. } else {
  917. $unread = array();
  918. }
  919. if (empty($unread) and empty($new)) {
  920. return;
  921. }
  922. $strforum = get_string('modulename','forum');
  923. $strnumunread = get_string('overviewnumunread','forum');
  924. $strnumpostssince = get_string('overviewnumpostssince','forum');
  925. foreach ($forums as $forum) {
  926. $str = '';
  927. $count = 0;
  928. $thisunread = 0;
  929. $showunread = false;
  930. // either we have something from logs, or trackposts, or nothing.
  931. if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
  932. $count = $new[$forum->id]->count;
  933. }
  934. if (array_key_exists($forum->id,$unread)) {
  935. $thisunread = $unread[$forum->id]->count;
  936. $showunread = true;
  937. }
  938. if ($count > 0 || $thisunread > 0) {
  939. $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
  940. $forum->name.'</a></div>';
  941. $str .= '<div class="info">';
  942. $str .= $count.' '.$strnumpostssince;
  943. if (!empty($showunread)) {
  944. $str .= '<br />'.$thisunread .' '.$strnumunread;
  945. }
  946. $str .= '</div></div>';
  947. }
  948. if (!empty($str)) {
  949. if (!array_key_exists($forum->course,$htmlarray)) {
  950. $htmlarray[$forum->course] = array();
  951. }
  952. if (!array_key_exists('forum',$htmlarray[$forum->course])) {
  953. $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
  954. }
  955. $htmlarray[$forum->course]['forum'] .= $str;
  956. }
  957. }
  958. }
  959. /**
  960. * Given a course and a date, prints a summary of all the new
  961. * messages posted in the course since that date
  962. * @param object $course
  963. * @param bool $viewfullnames capability
  964. * @param int $timestart
  965. * @return bool success
  966. */
  967. function forum_print_recent_activity($course, $viewfullnames, $timestart) {
  968. global $CFG, $USER;
  969. // do not use log table if possible, it may be…

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