MDL-32199 Ensure that forum RSS feeds don't leak posts and discussions
[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 *
21 * @package mod_forum
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
AD
26
27/**
28 * Returns the path to the cached rss feed contents. Creates/updates the cache if necessary.
13d1c9ed
JF
29 * @param stdClass $context the context
30 * @param array $args the arguments received in the url
fcce139a 31 * @return string the full path to the cached RSS feed directory. Null if there is a problem.
8f685009 32 */
274f9840 33function forum_rss_get_feed($context, $args) {
7ea78d9f 34 global $CFG, $DB, $USER;
8f685009 35
fcce139a 36 $status = true;
8adcb49f 37
fcce139a
AD
38 //are RSS feeds enabled?
39 if (empty($CFG->forum_enablerssfeeds)) {
40 debugging('DISABLED (module configuration)');
41 return null;
42 }
8adcb49f 43
e65ce4c1 44 $forumid = clean_param($args[3], PARAM_INT);
4df53223 45 $cm = get_coursemodule_from_instance('forum', $forumid, 0, false, MUST_EXIST);
af89cdd4 46 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
4df53223 47
af89cdd4
DP
48 //context id from db should match the submitted one
49 if ($context->id != $modcontext->id || !has_capability('mod/forum:viewdiscussion', $modcontext)) {
50 return null;
4df53223
AD
51 }
52
fcce139a 53 $forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
43b92251 54 if (!rss_enabled_for_mod('forum', $forum)) {
fcce139a 55 return null;
8adcb49f 56 }
57
fcce139a
AD
58 //the sql that will retreive the data for the feed and be hashed to get the cache filename
59 $sql = forum_rss_get_sql($forum, $cm);
83da3d28 60
fcce139a 61 //hash the sql to get the cache file name
7ea78d9f 62 $filename = rss_get_file_name($forum, $sql . $USER->id);
43b92251 63 $cachedfilepath = rss_get_file_full_name('mod_forum', $filename);
fcce139a
AD
64
65 //Is the cache out of date?
66 $cachedfilelastmodified = 0;
67 if (file_exists($cachedfilepath)) {
68 $cachedfilelastmodified = filemtime($cachedfilepath);
69 }
da8ae44e
AD
70 //if the cache is more than 60 seconds old and there's new stuff
71 $dontrecheckcutoff = time()-60;
72 if ( $dontrecheckcutoff > $cachedfilelastmodified && forum_rss_newstuff($forum, $cm, $cachedfilelastmodified)) {
fcce139a 73 //need to regenerate the cached version
af89cdd4 74 $result = forum_rss_feed_contents($forum, $sql, $modcontext);
fcce139a 75 if (!empty($result)) {
43b92251 76 $status = rss_save_file('mod_forum',$filename,$result);
83da3d28 77 }
78 }
79
fcce139a
AD
80 //return the path to the cached version
81 return $cachedfilepath;
82}
83da3d28 83
fcce139a
AD
84/**
85 * Given a forum object, deletes all cached RSS files associated with it.
86 *
13d1c9ed 87 * @param stdClass $forum
fcce139a
AD
88 */
89function forum_rss_delete_file($forum) {
43b92251 90 rss_delete_file('mod_forum', $forum);
fcce139a
AD
91}
92
93///////////////////////////////////////////////////////
94//Utility functions
95
96/**
97 * If there is new stuff in the forum since $time this returns true
98 * Otherwise it returns false.
99 *
13d1c9ed
JF
100 * @param stdClass $forum the forum object
101 * @param stdClass $cm Course Module object
102 * @param int $time check for items since this epoch timestamp
103 * @return bool True for new items
fcce139a
AD
104 */
105function forum_rss_newstuff($forum, $cm, $time) {
106 global $DB;
107
108 $sql = forum_rss_get_sql($forum, $cm, $time);
109
110 $recs = $DB->get_records_sql($sql, null, 0, 1);//limit of 1. If we get even 1 back we have new stuff
111 return ($recs && !empty($recs));
112}
113
13d1c9ed
JF
114/**
115 * Determines which type of SQL query is required, one for posts or one for discussions, and returns the appropriate query
116 *
117 * @param stdClass $forum the forum object
118 * @param stdClass $cm Course Module object
119 * @param int $time check for items since this epoch timestamp
120 * @return string the SQL query to be used to get the Discussion/Post details from the forum table of the database
121 */
fcce139a
AD
122function forum_rss_get_sql($forum, $cm, $time=0) {
123 $sql = null;
124
125 if (!empty($forum->rsstype)) {
126 if ($forum->rsstype == 1) { //Discussion RSS
127 $sql = forum_rss_feed_discussions_sql($forum, $cm, $time);
128 } else { //Post RSS
129 $sql = forum_rss_feed_posts_sql($forum, $cm, $time);
6069e206 130 }
6069e206 131 }
132
fcce139a
AD
133 return $sql;
134}
8f0cd6ef 135
13d1c9ed
JF
136/**
137 * Generates the SQL query used to get the Discussion details from the forum table of the database
138 *
139 * @param stdClass $forum the forum object
140 * @param stdClass $cm Course Module object
141 * @param int $newsince check for items since this epoch timestamp
142 * @return string the SQL query to be used to get the Discussion details from the forum table of the database
143 */
fcce139a
AD
144function forum_rss_feed_discussions_sql($forum, $cm, $newsince=0) {
145 global $CFG, $DB, $USER;
146
147 $timelimit = '';
148
149 $modcontext = null;
150
151 $now = round(time(), -2);
152 $params = array($cm->instance);
153
154 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
155
156 if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
157 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
158 $timelimit = " AND ((d.timestart <= :now1 AND (d.timeend = 0 OR d.timeend > :now2))";
159 $params['now1'] = $now;
160 $params['now2'] = $now;
161 if (isloggedin()) {
162 $timelimit .= " OR d.userid = :userid";
163 $params['userid'] = $USER->id;
8adcb49f 164 }
fcce139a 165 $timelimit .= ")";
8adcb49f 166 }
8adcb49f 167 }
168
fcce139a
AD
169 //do we only want new posts?
170 if ($newsince) {
171 $newsince = " AND p.modified > '$newsince'";
172 } else {
173 $newsince = '';
174 }
8adcb49f 175
fcce139a
AD
176 //get group enforcing SQL
177 $groupmode = groups_get_activity_groupmode($cm);
178 $currentgroup = groups_get_activity_group($cm);
179 $groupselect = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
8adcb49f 180
fcce139a
AD
181 if ($groupmode && $currentgroup) {
182 $params['groupid'] = $currentgroup;
183 }
8adcb49f 184
fcce139a 185 $forumsort = "d.timemodified DESC";
af89cdd4 186 $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";
fcce139a
AD
187
188 $sql = "SELECT $postdata, d.id as discussionid, d.name as discussionname, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
189 u.firstname as userfirstname, u.lastname as userlastname, u.email, u.picture, u.imagealt
190 FROM {forum_discussions} d
191 JOIN {forum_posts} p ON p.discussion = d.id
192 JOIN {user} u ON p.userid = u.id
193 WHERE d.forum = {$forum->id} AND p.parent = 0
194 $timelimit $groupselect $newsince
195 ORDER BY $forumsort";
196 return $sql;
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
AD
207function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) {
208 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
46d39cf3 209
fcce139a
AD
210 //get group enforcement SQL
211 $groupmode = groups_get_activity_groupmode($cm);
212 $currentgroup = groups_get_activity_group($cm);
213
214 $groupselect = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
215
216 if ($groupmode && $currentgroup) {
217 $params['groupid'] = $currentgroup;
218 }
6069e206 219
fcce139a
AD
220 //do we only want new posts?
221 if ($newsince) {
222 $newsince = " AND p.modified > '$newsince'";
223 } else {
224 $newsince = '';
225 }
ec41cb3c 226
fcce139a
AD
227 $sql = "SELECT p.id AS postid,
228 d.id AS discussionid,
229 d.name AS discussionname,
230 u.id AS userid,
231 u.firstname AS userfirstname,
232 u.lastname AS userlastname,
233 p.subject AS postsubject,
234 p.message AS postmessage,
235 p.created AS postcreated,
236 p.messageformat AS postformat,
237 p.messagetrust AS posttrust
238 FROM {forum_discussions} d,
239 {forum_posts} p,
240 {user} u
241 WHERE d.forum = {$forum->id} AND
242 p.discussion = d.id AND
243 u.id = p.userid $newsince
244 $groupselect
245 ORDER BY p.created desc";
246
247 return $sql;
248}
249
13d1c9ed
JF
250/**
251 * Retrieve the correct SQL snippet for group-only forums
252 *
253 * @param stdClass $cm Course Module object
254 * @param int $groupmode the mode in which the forum's groups are operating
255 * @param bool $currentgroup true if the user is from the a group enabled on the forum
256 * @param stdClass $modcontext The context instance of the forum module
257 * @return string SQL Query for group details of the forum
258 */
fcce139a
AD
259function forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext=null) {
260 $groupselect = '';
261
262 if ($groupmode) {
263 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
264 if ($currentgroup) {
265 $groupselect = "AND (d.groupid = :groupid OR d.groupid = -1)";
266 $params['groupid'] = $currentgroup;
267 }
268 } else {
269 //seprate groups without access all
270 if ($currentgroup) {
271 $groupselect = "AND (d.groupid = :groupid OR d.groupid = -1)";
272 $params['groupid'] = $currentgroup;
273 } else {
274 $groupselect = "AND d.groupid = -1";
8adcb49f 275 }
276 }
8adcb49f 277 }
8f0cd6ef 278
fcce139a
AD
279 return $groupselect;
280}
8adcb49f 281
fcce139a
AD
282/**
283 * This function return the XML rss contents about the forum
284 * It returns false if something is wrong
285 *
13d1c9ed
JF
286 * @param stdClass $forum the forum object
287 * @param string $sql The SQL used to retrieve the contents from the database
af89cdd4 288 * @param object $context the context this forum relates to
13d1c9ed
JF
289 * @return bool|string false if the contents is empty, otherwise the contents of the feed is returned
290 *
291 * @Todo MDL-31129 implement post attachment handling
fcce139a 292 */
7ea78d9f
ARN
293
294function forum_rss_feed_contents($forum, $sql) {
295 global $CFG, $DB, $USER;
296
6069e206 297
fcce139a
AD
298 $status = true;
299
300 $params = array();
301 //$params['forumid'] = $forum->id;
302 $recs = $DB->get_recordset_sql($sql, $params, 0, $forum->rssarticles);
303
304 //set a flag. Are we displaying discussions or posts?
305 $isdiscussion = true;
306 if (!empty($forum->rsstype) && $forum->rsstype!=1) {
cc771939 307 $isdiscussion = false;
fcce139a 308 }
318f2100 309
7ea78d9f
ARN
310 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
311 print_error('invalidcoursemodule');
312 }
313 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
314
39790bd8 315 $formatoptions = new stdClass();
fcce139a
AD
316 $items = array();
317 foreach ($recs as $rec) {
39790bd8
PS
318 $item = new stdClass();
319 $user = new stdClass();
7ea78d9f
ARN
320
321 if ($isdiscussion && !forum_user_can_see_discussion($forum, $rec->discussionid, $context)) {
322 // This is a discussion which the user can't has no permission to view
323 $item->title = get_string('forumsubjecthidden', 'forum');
324 $item->description = get_string('forumbodyhidden', 'forum');
325 $item->author = get_string('forumauthorhidden', 'forum');
326 } else if (!$isdiscussion && !forum_user_can_see_post($forum, $rec->discussionid, $rec->postid, $USER, $cm)) {
327 // This is a post which the user can't has no permission to view
328 $item->title = get_string('forumsubjecthidden', 'forum');
329 $item->description = get_string('forumbodyhidden', 'forum');
330 $item->author = get_string('forumauthorhidden', 'forum');
7c810d07 331 } else {
7ea78d9f
ARN
332 // The user must have permission to view
333 if ($isdiscussion && !empty($rec->discussionname)) {
334 $item->title = format_string($rec->discussionname);
335 } else if (!empty($rec->postsubject)) {
336 $item->title = format_string($rec->postsubject);
337 } else {
338 //we should have an item title by now but if we dont somehow then substitute something somewhat meaningful
339 $item->title = format_string($forum->name.' '.userdate($rec->postcreated,get_string('strftimedatetimeshort', 'langconfig')));
340 }
341 $user->firstname = $rec->userfirstname;
342 $user->lastname = $rec->userlastname;
343 $item->author = fullname($user);
344
345 $formatoptions->trusted = $rec->posttrust;
346 $item->description = format_text($rec->postmessage,$rec->postformat,$formatoptions,$forum->course);
7c810d07 347 }
7ea78d9f 348
fcce139a
AD
349 if ($isdiscussion) {
350 $item->link = $CFG->wwwroot."/mod/forum/discuss.php?d=".$rec->discussionid;
351 } else {
8f0cd6ef 352 $item->link = $CFG->wwwroot."/mod/forum/discuss.php?d=".$rec->discussionid."&parent=".$rec->postid;
fcce139a 353 }
410a24c0 354
7ea78d9f 355
fcce139a 356 $formatoptions->trusted = $rec->posttrust;
af89cdd4
DP
357 $message = file_rewrite_pluginfile_urls($rec->postmessage, 'pluginfile.php', $context->id,
358 'mod_forum', 'post', $rec->postid);
359 $item->description = format_text($message, $rec->postformat, $formatoptions, $forum->course);
410a24c0 360
13d1c9ed 361 //TODO: MDL-31129 implement post attachment handling
fcce139a 362 /*if (!$isdiscussion) {
318f2100 363 $post_file_area_name = str_replace('//', '/', "$forum->course/$CFG->moddata/forum/$forum->id/$rec->postid");
410a24c0 364 $post_files = get_directory_list("$CFG->dataroot/$post_file_area_name");
4e445355 365
366 if (!empty($post_files)) {
410a24c0 367 $item->attachments = array();
410a24c0 368 }
fcce139a 369 }*/
46d39cf3 370
fcce139a
AD
371 $items[] = $item;
372 }
373 $recs->close();
410a24c0 374
fcce139a
AD
375
376 if (!empty($items)) {
377 //First the RSS header
378 $header = rss_standard_header(strip_tags(format_string($forum->name,true)),
379 $CFG->wwwroot."/mod/forum/view.php?f=".$forum->id,
380 format_string($forum->intro,true)); // TODO: fix format
381 //Now all the rss items
382 if (!empty($header)) {
383 $articles = rss_add_items($items);
384 }
385 //Now the RSS footer
386 if (!empty($header) && !empty($articles)) {
387 $footer = rss_standard_footer();
8adcb49f 388 }
fcce139a
AD
389 //Now, if everything is ok, concatenate it
390 if (!empty($header) && !empty($articles) && !empty($footer)) {
391 $status = $header.$articles.$footer;
392 } else {
393 $status = false;
394 }
395 } else {
396 $status = false;
397 }
398
399 return $status;
400}