weekly release 4.0dev
[moodle.git] / mod / forum / rsslib.php
CommitLineData
1adbd2c3 1<?php
8adcb49f 2
8f685009
SH
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
13d1c9ed
JF
19 * This file adds support to rss feeds generation
20 *
01030f1b 21 * @package mod_forum
13d1c9ed
JF
22 * @category rss
23 * @copyright 2001 Eloy Lafuente (stronk7) http://contiento.com
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 */
fcce139a 26
54eb02a4
AD
27/* Include the core RSS lib */
28require_once($CFG->libdir.'/rsslib.php');
29
fcce139a
AD
30/**
31 * Returns the path to the cached rss feed contents. Creates/updates the cache if necessary.
13d1c9ed
JF
32 * @param stdClass $context the context
33 * @param array $args the arguments received in the url
fcce139a 34 * @return string the full path to the cached RSS feed directory. Null if there is a problem.
8f685009 35 */
274f9840 36function forum_rss_get_feed($context, $args) {
7ea78d9f 37 global $CFG, $DB, $USER;
8f685009 38
fcce139a 39 $status = true;
8adcb49f 40
fcce139a
AD
41 //are RSS feeds enabled?
42 if (empty($CFG->forum_enablerssfeeds)) {
43 debugging('DISABLED (module configuration)');
44 return null;
45 }
8adcb49f 46
e65ce4c1 47 $forumid = clean_param($args[3], PARAM_INT);
4df53223 48 $cm = get_coursemodule_from_instance('forum', $forumid, 0, false, MUST_EXIST);
17ec4bf0 49 $modcontext = context_module::instance($cm->id);
4df53223 50
af89cdd4
DP
51 //context id from db should match the submitted one
52 if ($context->id != $modcontext->id || !has_capability('mod/forum:viewdiscussion', $modcontext)) {
53 return null;
4df53223
AD
54 }
55
fcce139a 56 $forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
43b92251 57 if (!rss_enabled_for_mod('forum', $forum)) {
fcce139a 58 return null;
8adcb49f 59 }
60
fcce139a 61 //the sql that will retreive the data for the feed and be hashed to get the cache filename
5fe65fd4 62 list($sql, $params) = forum_rss_get_sql($forum, $cm);
83da3d28 63
17ec4bf0 64 // Hash the sql to get the cache file name.
5fe65fd4 65 $filename = rss_get_file_name($forum, $sql, $params);
43b92251 66 $cachedfilepath = rss_get_file_full_name('mod_forum', $filename);
fcce139a
AD
67
68 //Is the cache out of date?
69 $cachedfilelastmodified = 0;
70 if (file_exists($cachedfilepath)) {
71 $cachedfilelastmodified = filemtime($cachedfilepath);
72 }
941294ef 73 // Used to determine if we need to generate a new RSS feed.
54eb02a4
AD
74 $dontrecheckcutoff = time() - 60; // Sixty seconds ago.
75
76 // If it hasn't been generated we need to create it.
77 // Otherwise, if it has been > 60 seconds since we last updated, check for new items.
941294ef
MN
78 if (($cachedfilelastmodified == 0) || (($dontrecheckcutoff > $cachedfilelastmodified) &&
79 forum_rss_newstuff($forum, $cm, $cachedfilelastmodified))) {
80 // Need to regenerate the cached version.
5fe65fd4 81 $result = forum_rss_feed_contents($forum, $sql, $params, $modcontext);
941294ef 82 $status = rss_save_file('mod_forum', $filename, $result);
83da3d28 83 }
84
fcce139a
AD
85 //return the path to the cached version
86 return $cachedfilepath;
87}
83da3d28 88
fcce139a
AD
89/**
90 * Given a forum object, deletes all cached RSS files associated with it.
91 *
13d1c9ed 92 * @param stdClass $forum
fcce139a
AD
93 */
94function forum_rss_delete_file($forum) {
43b92251 95 rss_delete_file('mod_forum', $forum);
fcce139a
AD
96}
97
98///////////////////////////////////////////////////////
99//Utility functions
100
101/**
102 * If there is new stuff in the forum since $time this returns true
103 * Otherwise it returns false.
104 *
13d1c9ed
JF
105 * @param stdClass $forum the forum object
106 * @param stdClass $cm Course Module object
107 * @param int $time check for items since this epoch timestamp
108 * @return bool True for new items
fcce139a
AD
109 */
110function forum_rss_newstuff($forum, $cm, $time) {
111 global $DB;
112
5fe65fd4 113 list($sql, $params) = forum_rss_get_sql($forum, $cm, $time);
fcce139a 114
941294ef 115 return $DB->record_exists_sql($sql, $params);
fcce139a
AD
116}
117
13d1c9ed
JF
118/**
119 * Determines which type of SQL query is required, one for posts or one for discussions, and returns the appropriate query
120 *
121 * @param stdClass $forum the forum object
122 * @param stdClass $cm Course Module object
123 * @param int $time check for items since this epoch timestamp
124 * @return string the SQL query to be used to get the Discussion/Post details from the forum table of the database
125 */
fcce139a 126function forum_rss_get_sql($forum, $cm, $time=0) {
5fe65fd4
MN
127 if ($forum->rsstype == 1) { // Discussion RSS
128 return forum_rss_feed_discussions_sql($forum, $cm, $time);
129 } else { // Post RSS
130 return forum_rss_feed_posts_sql($forum, $cm, $time);
6069e206 131 }
fcce139a 132}
8f0cd6ef 133
13d1c9ed
JF
134/**
135 * Generates the SQL query used to get the Discussion details from the forum table of the database
136 *
137 * @param stdClass $forum the forum object
138 * @param stdClass $cm Course Module object
139 * @param int $newsince check for items since this epoch timestamp
140 * @return string the SQL query to be used to get the Discussion details from the forum table of the database
141 */
fcce139a
AD
142function forum_rss_feed_discussions_sql($forum, $cm, $newsince=0) {
143 global $CFG, $DB, $USER;
144
145 $timelimit = '';
146
147 $modcontext = null;
148
098f4337 149 $now = floor(time() / 60) * 60; // DB Cache Friendly.
5fe65fd4 150 $params = array();
fcce139a 151
17ec4bf0 152 $modcontext = context_module::instance($cm->id);
fcce139a
AD
153
154 if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
155 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
156 $timelimit = " AND ((d.timestart <= :now1 AND (d.timeend = 0 OR d.timeend > :now2))";
157 $params['now1'] = $now;
158 $params['now2'] = $now;
159 if (isloggedin()) {
160 $timelimit .= " OR d.userid = :userid";
161 $params['userid'] = $USER->id;
8adcb49f 162 }
fcce139a 163 $timelimit .= ")";
8adcb49f 164 }
8adcb49f 165 }
166
5fe65fd4 167 // Do we only want new posts?
fcce139a 168 if ($newsince) {
5fe65fd4
MN
169 $params['newsince'] = $newsince;
170 $newsince = " AND p.modified > :newsince";
fcce139a
AD
171 } else {
172 $newsince = '';
173 }
8adcb49f 174
5fe65fd4
MN
175 // Get group enforcing SQL.
176 $groupmode = groups_get_activity_groupmode($cm);
fcce139a 177 $currentgroup = groups_get_activity_group($cm);
5fe65fd4 178 list($groupselect, $groupparams) = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
8adcb49f 179
5fe65fd4
MN
180 // Add the groupparams to the params array.
181 $params = array_merge($params, $groupparams);
8adcb49f 182
fcce139a 183 $forumsort = "d.timemodified DESC";
af89cdd4 184 $postdata = "p.id AS postid, p.subject, p.created as postcreated, p.modified, p.discussion, p.userid, p.message as postmessage, p.messageformat AS postformat, p.messagetrust AS posttrust";
5e72715e 185 $userfieldsapi = \core_user\fields::for_userpic();
558cc1b8 186 $userpicturefields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;
fcce139a 187
5450ed43
AG
188 $sql = "SELECT $postdata, d.id as discussionid, d.name as discussionname, d.timemodified, d.usermodified, d.groupid,
189 d.timestart, d.timeend, $userpicturefields
fcce139a
AD
190 FROM {forum_discussions} d
191 JOIN {forum_posts} p ON p.discussion = d.id
192 JOIN {user} u ON p.userid = u.id
82b50d31 193 WHERE d.forum = {$forum->id} AND p.parent = 0 AND p.deleted <> 1
fcce139a
AD
194 $timelimit $groupselect $newsince
195 ORDER BY $forumsort";
5fe65fd4 196 return array($sql, $params);
fcce139a
AD
197}
198
13d1c9ed
JF
199/**
200 * Generates the SQL query used to get the Post details from the forum table of the database
201 *
202 * @param stdClass $forum the forum object
203 * @param stdClass $cm Course Module object
204 * @param int $newsince check for items since this epoch timestamp
205 * @return string the SQL query to be used to get the Post details from the forum table of the database
206 */
fcce139a 207function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) {
bc4c7337
AN
208 global $USER;
209
17ec4bf0 210 $modcontext = context_module::instance($cm->id);
46d39cf3 211
5fe65fd4
MN
212 // Get group enforcement SQL.
213 $groupmode = groups_get_activity_groupmode($cm);
fcce139a 214 $currentgroup = groups_get_activity_group($cm);
5fe65fd4 215 $params = array();
fcce139a 216
5fe65fd4 217 list($groupselect, $groupparams) = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
fcce139a 218
5fe65fd4
MN
219 // Add the groupparams to the params array.
220 $params = array_merge($params, $groupparams);
6069e206 221
5fe65fd4 222 // Do we only want new posts?
fcce139a 223 if ($newsince) {
5fe65fd4
MN
224 $params['newsince'] = $newsince;
225 $newsince = " AND p.modified > :newsince";
fcce139a
AD
226 } else {
227 $newsince = '';
228 }
ec41cb3c 229
bc4c7337
AN
230 $canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $modcontext);
231 if (!$canseeprivatereplies) {
232 $privatewhere = ' AND (p.privatereplyto = :currentuser1 OR p.userid = :currentuser2 OR p.privatereplyto = 0)';
233 $params['currentuser1'] = $USER->id;
234 $params['currentuser2'] = $USER->id;
235 } else {
236 $privatewhere = '';
237 }
238
5e72715e 239 $userfieldsapi = \core_user\fields::for_name();
558cc1b8 240 $usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
fcce139a
AD
241 $sql = "SELECT p.id AS postid,
242 d.id AS discussionid,
243 d.name AS discussionname,
8f6e47d9
AD
244 d.groupid,
245 d.timestart,
246 d.timeend,
fcce139a 247 u.id AS userid,
5450ed43 248 $usernamefields,
fcce139a
AD
249 p.subject AS postsubject,
250 p.message AS postmessage,
251 p.created AS postcreated,
252 p.messageformat AS postformat,
8f6e47d9
AD
253 p.messagetrust AS posttrust,
254 p.parent as postparent
fcce139a
AD
255 FROM {forum_discussions} d,
256 {forum_posts} p,
257 {user} u
258 WHERE d.forum = {$forum->id} AND
82b50d31 259 p.discussion = d.id AND p.deleted <> 1 AND
fcce139a 260 u.id = p.userid $newsince
bc4c7337 261 $privatewhere
fcce139a
AD
262 $groupselect
263 ORDER BY p.created desc";
264
5fe65fd4 265 return array($sql, $params);
fcce139a
AD
266}
267
13d1c9ed
JF
268/**
269 * Retrieve the correct SQL snippet for group-only forums
270 *
271 * @param stdClass $cm Course Module object
272 * @param int $groupmode the mode in which the forum's groups are operating
273 * @param bool $currentgroup true if the user is from the a group enabled on the forum
274 * @param stdClass $modcontext The context instance of the forum module
275 * @return string SQL Query for group details of the forum
276 */
fcce139a
AD
277function forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext=null) {
278 $groupselect = '';
5fe65fd4 279 $params = array();
fcce139a
AD
280
281 if ($groupmode) {
282 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
283 if ($currentgroup) {
284 $groupselect = "AND (d.groupid = :groupid OR d.groupid = -1)";
285 $params['groupid'] = $currentgroup;
286 }
287 } else {
5fe65fd4 288 // Separate groups without access all.
fcce139a
AD
289 if ($currentgroup) {
290 $groupselect = "AND (d.groupid = :groupid OR d.groupid = -1)";
291 $params['groupid'] = $currentgroup;
292 } else {
293 $groupselect = "AND d.groupid = -1";
8adcb49f 294 }
295 }
8adcb49f 296 }
8f0cd6ef 297
5fe65fd4 298 return array($groupselect, $params);
fcce139a 299}
8adcb49f 300
fcce139a
AD
301/**
302 * This function return the XML rss contents about the forum
303 * It returns false if something is wrong
304 *
13d1c9ed 305 * @param stdClass $forum the forum object
5fe65fd4
MN
306 * @param string $sql the SQL used to retrieve the contents from the database
307 * @param array $params the SQL parameters used
af89cdd4 308 * @param object $context the context this forum relates to
13d1c9ed
JF
309 * @return bool|string false if the contents is empty, otherwise the contents of the feed is returned
310 *
311 * @Todo MDL-31129 implement post attachment handling
fcce139a 312 */
7ea78d9f 313
5fe65fd4 314function forum_rss_feed_contents($forum, $sql, $params, $context) {
7ea78d9f
ARN
315 global $CFG, $DB, $USER;
316
fcce139a
AD
317 $status = true;
318
fcce139a
AD
319 $recs = $DB->get_recordset_sql($sql, $params, 0, $forum->rssarticles);
320
321 //set a flag. Are we displaying discussions or posts?
322 $isdiscussion = true;
323 if (!empty($forum->rsstype) && $forum->rsstype!=1) {
cc771939 324 $isdiscussion = false;
fcce139a 325 }
318f2100 326
7ea78d9f
ARN
327 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
328 print_error('invalidcoursemodule');
329 }
7ea78d9f 330
39790bd8 331 $formatoptions = new stdClass();
fcce139a
AD
332 $items = array();
333 foreach ($recs as $rec) {
39790bd8 334 $item = new stdClass();
7ea78d9f 335
8f6e47d9
AD
336 $discussion = new stdClass();
337 $discussion->id = $rec->discussionid;
338 $discussion->groupid = $rec->groupid;
339 $discussion->timestart = $rec->timestart;
340 $discussion->timeend = $rec->timeend;
341
342 $post = null;
343 if (!$isdiscussion) {
344 $post = new stdClass();
345 $post->id = $rec->postid;
346 $post->parent = $rec->postparent;
347 $post->userid = $rec->userid;
348 }
349
350 if ($isdiscussion && !forum_user_can_see_discussion($forum, $discussion, $context)) {
17ec4bf0 351 // This is a discussion which the user has no permission to view
7ea78d9f 352 $item->title = get_string('forumsubjecthidden', 'forum');
17ec4bf0 353 $message = get_string('forumbodyhidden', 'forum');
7ea78d9f 354 $item->author = get_string('forumauthorhidden', 'forum');
8f6e47d9 355 } else if (!$isdiscussion && !forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
3e95e09b
AN
356 if (forum_user_can_see_post($forum, $discussion, $post, $USER, $cm, false)) {
357 // This is a post which the user has no permission to view.
358 $item->title = get_string('forumsubjecthidden', 'forum');
359 $message = get_string('forumbodyhidden', 'forum');
360 $item->author = get_string('forumauthorhidden', 'forum');
361 } else {
362 // This is a post which has been deleted.
363 $item->title = get_string('privacy:request:delete:post:subject', 'mod_forum');
364 $message = get_string('privacy:request:delete:post:subject', 'mod_forum');
365 $item->author = get_string('forumauthorhidden', 'forum');
366 }
7c810d07 367 } else {
7ea78d9f
ARN
368 // The user must have permission to view
369 if ($isdiscussion && !empty($rec->discussionname)) {
370 $item->title = format_string($rec->discussionname);
371 } else if (!empty($rec->postsubject)) {
372 $item->title = format_string($rec->postsubject);
373 } else {
374 //we should have an item title by now but if we dont somehow then substitute something somewhat meaningful
375 $item->title = format_string($forum->name.' '.userdate($rec->postcreated,get_string('strftimedatetimeshort', 'langconfig')));
376 }
5450ed43 377 $item->author = fullname($rec);
17ec4bf0
AG
378 $message = file_rewrite_pluginfile_urls($rec->postmessage, 'pluginfile.php', $context->id,
379 'mod_forum', 'post', $rec->postid);
7ea78d9f 380 $formatoptions->trusted = $rec->posttrust;
7c810d07 381 }
7ea78d9f 382
fcce139a
AD
383 if ($isdiscussion) {
384 $item->link = $CFG->wwwroot."/mod/forum/discuss.php?d=".$rec->discussionid;
385 } else {
8f0cd6ef 386 $item->link = $CFG->wwwroot."/mod/forum/discuss.php?d=".$rec->discussionid."&parent=".$rec->postid;
fcce139a 387 }
410a24c0 388
fcce139a 389 $formatoptions->trusted = $rec->posttrust;
af89cdd4 390 $item->description = format_text($message, $rec->postformat, $formatoptions, $forum->course);
410a24c0 391
13d1c9ed 392 //TODO: MDL-31129 implement post attachment handling
fcce139a 393 /*if (!$isdiscussion) {
318f2100 394 $post_file_area_name = str_replace('//', '/', "$forum->course/$CFG->moddata/forum/$forum->id/$rec->postid");
410a24c0 395 $post_files = get_directory_list("$CFG->dataroot/$post_file_area_name");
4e445355 396
397 if (!empty($post_files)) {
410a24c0 398 $item->attachments = array();
410a24c0 399 }
fcce139a 400 }*/
17ec4bf0 401 $item->pubdate = $rec->postcreated;
46d39cf3 402
fcce139a
AD
403 $items[] = $item;
404 }
405 $recs->close();
410a24c0 406
941294ef
MN
407 // Create the RSS header.
408 $header = rss_standard_header(strip_tags(format_string($forum->name,true)),
409 $CFG->wwwroot."/mod/forum/view.php?f=".$forum->id,
410 format_string($forum->intro,true)); // TODO: fix format
411 // Now all the RSS items, if there are any.
412 $articles = '';
fcce139a 413 if (!empty($items)) {
941294ef 414 $articles = rss_add_items($items);
fcce139a 415 }
941294ef
MN
416 // Create the RSS footer.
417 $footer = rss_standard_footer();
fcce139a 418
941294ef 419 return $header . $articles . $footer;
fcce139a 420}