MDL-36741 mod_forum: fixed SQL that was generated when either timed posts or groups...
[moodle.git] / mod / forum / rsslib.php
1 <?php
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/>.
18 /**
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  */
27 /**
28  * Returns the path to the cached rss feed contents. Creates/updates the cache if necessary.
29  * @param stdClass $context the context
30  * @param array    $args    the arguments received in the url
31  * @return string the full path to the cached RSS feed directory. Null if there is a problem.
32  */
33 function forum_rss_get_feed($context, $args) {
34     global $CFG, $DB, $USER;
36     $status = true;
38     //are RSS feeds enabled?
39     if (empty($CFG->forum_enablerssfeeds)) {
40         debugging('DISABLED (module configuration)');
41         return null;
42     }
44     $forumid  = clean_param($args[3], PARAM_INT);
45     $cm = get_coursemodule_from_instance('forum', $forumid, 0, false, MUST_EXIST);
46     $modcontext = context_module::instance($cm->id);
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;
51     }
53     $forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
54     if (!rss_enabled_for_mod('forum', $forum)) {
55         return null;
56     }
58     //the sql that will retreive the data for the feed and be hashed to get the cache filename
59     list($sql, $params) = forum_rss_get_sql($forum, $cm);
61     // Hash the sql to get the cache file name.
62     $filename = rss_get_file_name($forum, $sql, $params);
63     $cachedfilepath = rss_get_file_full_name('mod_forum', $filename);
65     //Is the cache out of date?
66     $cachedfilelastmodified = 0;
67     if (file_exists($cachedfilepath)) {
68         $cachedfilelastmodified = filemtime($cachedfilepath);
69     }
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)) {
73         //need to regenerate the cached version
74         $result = forum_rss_feed_contents($forum, $sql, $params, $modcontext);
75         if (!empty($result)) {
76             $status = rss_save_file('mod_forum',$filename,$result);
77         }
78     }
80     //return the path to the cached version
81     return $cachedfilepath;
82 }
84 /**
85  * Given a forum object, deletes all cached RSS files associated with it.
86  *
87  * @param stdClass $forum
88  */
89 function forum_rss_delete_file($forum) {
90     rss_delete_file('mod_forum', $forum);
91 }
93 ///////////////////////////////////////////////////////
94 //Utility functions
96 /**
97  * If there is new stuff in the forum since $time this returns true
98  * Otherwise it returns false.
99  *
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
104  */
105 function forum_rss_newstuff($forum, $cm, $time) {
106     global $DB;
108     list($sql, $params) = forum_rss_get_sql($forum, $cm, $time);
109     if ($DB->count_records_sql($sql, $params) > 0) {
110         return true;
111     }
113     return false;
116 /**
117  * Determines which type of SQL query is required, one for posts or one for discussions, and returns the appropriate query
118  *
119  * @param stdClass $forum the forum object
120  * @param stdClass $cm    Course Module object
121  * @param int      $time  check for items since this epoch timestamp
122  * @return string the SQL query to be used to get the Discussion/Post details from the forum table of the database
123  */
124 function forum_rss_get_sql($forum, $cm, $time=0) {
125     if ($forum->rsstype == 1) { // Discussion RSS
126         return forum_rss_feed_discussions_sql($forum, $cm, $time);
127     } else { // Post RSS
128         return forum_rss_feed_posts_sql($forum, $cm, $time);
129     }
132 /**
133  * Generates the SQL query used to get the Discussion details from the forum table of the database
134  *
135  * @param stdClass $forum     the forum object
136  * @param stdClass $cm        Course Module object
137  * @param int      $newsince  check for items since this epoch timestamp
138  * @return string the SQL query to be used to get the Discussion details from the forum table of the database
139  */
140 function forum_rss_feed_discussions_sql($forum, $cm, $newsince=0) {
141     global $CFG, $DB, $USER;
143     $timelimit = '';
145     $modcontext = null;
147     $now = round(time(), -2);
148     $params = array();
150     $modcontext = context_module::instance($cm->id);
152     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
153         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
154             $timelimit = " AND ((d.timestart <= :now1 AND (d.timeend = 0 OR d.timeend > :now2))";
155             $params['now1'] = $now;
156             $params['now2'] = $now;
157             if (isloggedin()) {
158                 $timelimit .= " OR d.userid = :userid";
159                 $params['userid'] = $USER->id;
160             }
161             $timelimit .= ")";
162         }
163     }
165     // Do we only want new posts?
166     if ($newsince) {
167         $params['newsince'] = $newsince;
168         $newsince = " AND p.modified > :newsince";
169     } else {
170         $newsince = '';
171     }
173     // Get group enforcing SQL.
174     $groupmode = groups_get_activity_groupmode($cm);
175     $currentgroup = groups_get_activity_group($cm);
176     list($groupselect, $groupparams) = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
178     // Add the groupparams to the params array.
179     $params = array_merge($params, $groupparams);
181     $forumsort = "d.timemodified DESC";
182     $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";
184     $sql = "SELECT $postdata, d.id as discussionid, d.name as discussionname, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
185                    u.firstname as userfirstname, u.lastname as userlastname, u.email, u.picture, u.imagealt
186               FROM {forum_discussions} d
187                    JOIN {forum_posts} p ON p.discussion = d.id
188                    JOIN {user} u ON p.userid = u.id
189              WHERE d.forum = {$forum->id} AND p.parent = 0
190                    $timelimit $groupselect $newsince
191           ORDER BY $forumsort";
192     return array($sql, $params);
195 /**
196  * Generates the SQL query used to get the Post details from the forum table of the database
197  *
198  * @param stdClass $forum     the forum object
199  * @param stdClass $cm        Course Module object
200  * @param int      $newsince  check for items since this epoch timestamp
201  * @return string the SQL query to be used to get the Post details from the forum table of the database
202  */
203 function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) {
204     $modcontext = context_module::instance($cm->id);
206     // Get group enforcement SQL.
207     $groupmode = groups_get_activity_groupmode($cm);
208     $currentgroup = groups_get_activity_group($cm);
209     $params = array();
211     list($groupselect, $groupparams) = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
213     // Add the groupparams to the params array.
214     $params = array_merge($params, $groupparams);
216     // Do we only want new posts?
217     if ($newsince) {
218         $params['newsince'] = $newsince;
219         $newsince = " AND p.modified > :newsince";
220     } else {
221         $newsince = '';
222     }
224     $sql = "SELECT p.id AS postid,
225                  d.id AS discussionid,
226                  d.name AS discussionname,
227                  u.id AS userid,
228                  u.firstname AS userfirstname,
229                  u.lastname AS userlastname,
230                  p.subject AS postsubject,
231                  p.message AS postmessage,
232                  p.created AS postcreated,
233                  p.messageformat AS postformat,
234                  p.messagetrust AS posttrust
235             FROM {forum_discussions} d,
236                {forum_posts} p,
237                {user} u
238             WHERE d.forum = {$forum->id} AND
239                 p.discussion = d.id AND
240                 u.id = p.userid $newsince
241                 $groupselect
242             ORDER BY p.created desc";
244     return array($sql, $params);
247 /**
248  * Retrieve the correct SQL snippet for group-only forums
249  *
250  * @param stdClass $cm           Course Module object
251  * @param int      $groupmode    the mode in which the forum's groups are operating
252  * @param bool     $currentgroup true if the user is from the a group enabled on the forum
253  * @param stdClass $modcontext   The context instance of the forum module
254  * @return string SQL Query for group details of the forum
255  */
256 function forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext=null) {
257     $groupselect = '';
258     $params = array();
260     if ($groupmode) {
261         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
262             if ($currentgroup) {
263                 $groupselect = "AND (d.groupid = :groupid OR d.groupid = -1)";
264                 $params['groupid'] = $currentgroup;
265             }
266         } else {
267             // Separate groups without access all.
268             if ($currentgroup) {
269                 $groupselect = "AND (d.groupid = :groupid OR d.groupid = -1)";
270                 $params['groupid'] = $currentgroup;
271             } else {
272                 $groupselect = "AND d.groupid = -1";
273             }
274         }
275     }
277     return array($groupselect, $params);
280 /**
281  * This function return the XML rss contents about the forum
282  * It returns false if something is wrong
283  *
284  * @param stdClass $forum the forum object
285  * @param string $sql the SQL used to retrieve the contents from the database
286  * @param array $params the SQL parameters used
287  * @param object $context the context this forum relates to
288  * @return bool|string false if the contents is empty, otherwise the contents of the feed is returned
289  *
290  * @Todo MDL-31129 implement post attachment handling
291  */
293 function forum_rss_feed_contents($forum, $sql, $params, $context) {
294     global $CFG, $DB, $USER;
296     $status = true;
298     $recs = $DB->get_recordset_sql($sql, $params, 0, $forum->rssarticles);
300     //set a flag. Are we displaying discussions or posts?
301     $isdiscussion = true;
302     if (!empty($forum->rsstype) && $forum->rsstype!=1) {
303         $isdiscussion = false;
304     }
306     if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
307         print_error('invalidcoursemodule');
308     }
310     $formatoptions = new stdClass();
311     $items = array();
312     foreach ($recs as $rec) {
313             $item = new stdClass();
314             $user = new stdClass();
316             if ($isdiscussion && !forum_user_can_see_discussion($forum, $rec->discussionid, $context)) {
317                 // This is a discussion which the user has no permission to view
318                 $item->title = get_string('forumsubjecthidden', 'forum');
319                 $message = get_string('forumbodyhidden', 'forum');
320                 $item->author = get_string('forumauthorhidden', 'forum');
321             } else if (!$isdiscussion && !forum_user_can_see_post($forum, $rec->discussionid, $rec->postid, $USER, $cm)) {
322                 // This is a post which the user has no permission to view
323                 $item->title = get_string('forumsubjecthidden', 'forum');
324                 $message = get_string('forumbodyhidden', 'forum');
325                 $item->author = get_string('forumauthorhidden', 'forum');
326             } else {
327                 // The user must have permission to view
328                 if ($isdiscussion && !empty($rec->discussionname)) {
329                     $item->title = format_string($rec->discussionname);
330                 } else if (!empty($rec->postsubject)) {
331                     $item->title = format_string($rec->postsubject);
332                 } else {
333                     //we should have an item title by now but if we dont somehow then substitute something somewhat meaningful
334                     $item->title = format_string($forum->name.' '.userdate($rec->postcreated,get_string('strftimedatetimeshort', 'langconfig')));
335                 }
336                 $user->firstname = $rec->userfirstname;
337                 $user->lastname = $rec->userlastname;
338                 $item->author = fullname($user);
339                 $message = file_rewrite_pluginfile_urls($rec->postmessage, 'pluginfile.php', $context->id,
340                         'mod_forum', 'post', $rec->postid);
341                 $formatoptions->trusted = $rec->posttrust;
342             }
344             if ($isdiscussion) {
345                 $item->link = $CFG->wwwroot."/mod/forum/discuss.php?d=".$rec->discussionid;
346             } else {
347                 $item->link = $CFG->wwwroot."/mod/forum/discuss.php?d=".$rec->discussionid."&parent=".$rec->postid;
348             }
350             $formatoptions->trusted = $rec->posttrust;
351             $item->description = format_text($message, $rec->postformat, $formatoptions, $forum->course);
353             //TODO: MDL-31129 implement post attachment handling
354             /*if (!$isdiscussion) {
355                 $post_file_area_name = str_replace('//', '/', "$forum->course/$CFG->moddata/forum/$forum->id/$rec->postid");
356                 $post_files = get_directory_list("$CFG->dataroot/$post_file_area_name");
358                 if (!empty($post_files)) {
359                     $item->attachments = array();
360                 }
361             }*/
362             $item->pubdate = $rec->postcreated;
364             $items[] = $item;
365         }
366     $recs->close();
369     if (!empty($items)) {
370         //First the RSS header
371         $header = rss_standard_header(strip_tags(format_string($forum->name,true)),
372                                       $CFG->wwwroot."/mod/forum/view.php?f=".$forum->id,
373                                       format_string($forum->intro,true)); // TODO: fix format
374         //Now all the rss items
375         if (!empty($header)) {
376             $articles = rss_add_items($items);
377         }
378         //Now the RSS footer
379         if (!empty($header) && !empty($articles)) {
380             $footer = rss_standard_footer();
381         }
382         //Now, if everything is ok, concatenate it
383         if (!empty($header) && !empty($articles) && !empty($footer)) {
384             $status = $header.$articles.$footer;
385         } else {
386             $status = false;
387         }
388     } else {
389         $status = false;
390     }
392     return $status;