86bf31b7e929edff55138d4857d326ea07b87f88
[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     $sql = forum_rss_get_sql($forum, $cm);
61     // Hash the sql to get the cache file name.
62     // If the forum is Q and A then we need to cache the files per user. This can
63     // have a large impact on performance, so we want to only do it on this type of forum.
64     if ($forum->type == 'qanda') {
65         $filename = rss_get_file_name($forum, $sql . $USER->id);
66     } else {
67         $filename = rss_get_file_name($forum, $sql);
68     }
69     $cachedfilepath = rss_get_file_full_name('mod_forum', $filename);
71     //Is the cache out of date?
72     $cachedfilelastmodified = 0;
73     if (file_exists($cachedfilepath)) {
74         $cachedfilelastmodified = filemtime($cachedfilepath);
75     }
76     //if the cache is more than 60 seconds old and there's new stuff
77     $dontrecheckcutoff = time()-60;
78     if ( $dontrecheckcutoff > $cachedfilelastmodified && forum_rss_newstuff($forum, $cm, $cachedfilelastmodified)) {
79         //need to regenerate the cached version
80         $result = forum_rss_feed_contents($forum, $sql, $modcontext);
81         if (!empty($result)) {
82             $status = rss_save_file('mod_forum',$filename,$result);
83         }
84     }
86     //return the path to the cached version
87     return $cachedfilepath;
88 }
90 /**
91  * Given a forum object, deletes all cached RSS files associated with it.
92  *
93  * @param stdClass $forum
94  */
95 function forum_rss_delete_file($forum) {
96     rss_delete_file('mod_forum', $forum);
97 }
99 ///////////////////////////////////////////////////////
100 //Utility functions
102 /**
103  * If there is new stuff in the forum since $time this returns true
104  * Otherwise it returns false.
105  *
106  * @param stdClass $forum the forum object
107  * @param stdClass $cm    Course Module object
108  * @param int      $time  check for items since this epoch timestamp
109  * @return bool True for new items
110  */
111 function forum_rss_newstuff($forum, $cm, $time) {
112     global $DB;
114     $sql = forum_rss_get_sql($forum, $cm, $time);
116     $recs = $DB->get_records_sql($sql, null, 0, 1);//limit of 1. If we get even 1 back we have new stuff
117     return ($recs && !empty($recs));
120 /**
121  * Determines which type of SQL query is required, one for posts or one for discussions, and returns the appropriate query
122  *
123  * @param stdClass $forum the forum object
124  * @param stdClass $cm    Course Module object
125  * @param int      $time  check for items since this epoch timestamp
126  * @return string the SQL query to be used to get the Discussion/Post details from the forum table of the database
127  */
128 function forum_rss_get_sql($forum, $cm, $time=0) {
129     $sql = null;
131     if (!empty($forum->rsstype)) {
132         if ($forum->rsstype == 1) {    //Discussion RSS
133             $sql = forum_rss_feed_discussions_sql($forum, $cm, $time);
134         } else {                //Post RSS
135             $sql = forum_rss_feed_posts_sql($forum, $cm, $time);
136         }
137     }
139     return $sql;
142 /**
143  * Generates the SQL query used to get the Discussion details from the forum table of the database
144  *
145  * @param stdClass $forum     the forum object
146  * @param stdClass $cm        Course Module object
147  * @param int      $newsince  check for items since this epoch timestamp
148  * @return string the SQL query to be used to get the Discussion details from the forum table of the database
149  */
150 function forum_rss_feed_discussions_sql($forum, $cm, $newsince=0) {
151     global $CFG, $DB, $USER;
153     $timelimit = '';
155     $modcontext = null;
157     $now = round(time(), -2);
158     $params = array($cm->instance);
160     $modcontext = context_module::instance($cm->id);
162     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
163         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
164             $timelimit = " AND ((d.timestart <= :now1 AND (d.timeend = 0 OR d.timeend > :now2))";
165             $params['now1'] = $now;
166             $params['now2'] = $now;
167             if (isloggedin()) {
168                 $timelimit .= " OR d.userid = :userid";
169                 $params['userid'] = $USER->id;
170             }
171             $timelimit .= ")";
172         }
173     }
175     //do we only want new posts?
176     if ($newsince) {
177         $newsince = " AND p.modified > '$newsince'";
178     } else {
179         $newsince = '';
180     }
182     //get group enforcing SQL
183     $groupmode    = groups_get_activity_groupmode($cm);
184     $currentgroup = groups_get_activity_group($cm);
185     $groupselect = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
187     if ($groupmode && $currentgroup) {
188         $params['groupid'] = $currentgroup;
189     }
191     $forumsort = "d.timemodified DESC";
192     $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";
194     $sql = "SELECT $postdata, d.id as discussionid, d.name as discussionname, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
195                    u.firstname as userfirstname, u.lastname as userlastname, u.email, u.picture, u.imagealt
196               FROM {forum_discussions} d
197                    JOIN {forum_posts} p ON p.discussion = d.id
198                    JOIN {user} u ON p.userid = u.id
199              WHERE d.forum = {$forum->id} AND p.parent = 0
200                    $timelimit $groupselect $newsince
201           ORDER BY $forumsort";
202     return $sql;
205 /**
206  * Generates the SQL query used to get the Post details from the forum table of the database
207  *
208  * @param stdClass $forum     the forum object
209  * @param stdClass $cm        Course Module object
210  * @param int      $newsince  check for items since this epoch timestamp
211  * @return string the SQL query to be used to get the Post details from the forum table of the database
212  */
213 function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) {
214     $modcontext = context_module::instance($cm->id);
216     //get group enforcement SQL
217     $groupmode    = groups_get_activity_groupmode($cm);
218     $currentgroup = groups_get_activity_group($cm);
220     $groupselect = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
222     if ($groupmode && $currentgroup) {
223         $params['groupid'] = $currentgroup;
224     }
226     //do we only want new posts?
227     if ($newsince) {
228         $newsince = " AND p.modified > '$newsince'";
229     } else {
230         $newsince = '';
231     }
233     $sql = "SELECT p.id AS postid,
234                  d.id AS discussionid,
235                  d.name AS discussionname,
236                  u.id AS userid,
237                  u.firstname AS userfirstname,
238                  u.lastname AS userlastname,
239                  p.subject AS postsubject,
240                  p.message AS postmessage,
241                  p.created AS postcreated,
242                  p.messageformat AS postformat,
243                  p.messagetrust AS posttrust
244             FROM {forum_discussions} d,
245                {forum_posts} p,
246                {user} u
247             WHERE d.forum = {$forum->id} AND
248                 p.discussion = d.id AND
249                 u.id = p.userid $newsince
250                 $groupselect
251             ORDER BY p.created desc";
253     return $sql;
256 /**
257  * Retrieve the correct SQL snippet for group-only forums
258  *
259  * @param stdClass $cm           Course Module object
260  * @param int      $groupmode    the mode in which the forum's groups are operating
261  * @param bool     $currentgroup true if the user is from the a group enabled on the forum
262  * @param stdClass $modcontext   The context instance of the forum module
263  * @return string SQL Query for group details of the forum
264  */
265 function forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext=null) {
266     $groupselect = '';
268     if ($groupmode) {
269         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
270             if ($currentgroup) {
271                 $groupselect = "AND (d.groupid = :groupid OR d.groupid = -1)";
272                 $params['groupid'] = $currentgroup;
273             }
274         } else {
275             //seprate groups without access all
276             if ($currentgroup) {
277                 $groupselect = "AND (d.groupid = :groupid OR d.groupid = -1)";
278                 $params['groupid'] = $currentgroup;
279             } else {
280                 $groupselect = "AND d.groupid = -1";
281             }
282         }
283     }
285     return $groupselect;
288 /**
289  * This function return the XML rss contents about the forum
290  * It returns false if something is wrong
291  *
292  * @param stdClass $forum the forum object
293  * @param string   $sql   The SQL used to retrieve the contents from the database
294  * @param object $context the context this forum relates to
295  * @return bool|string false if the contents is empty, otherwise the contents of the feed is returned
296  *
297  * @Todo MDL-31129 implement post attachment handling
298  */
300 function forum_rss_feed_contents($forum, $sql) {
301     global $CFG, $DB, $USER;
304     $status = true;
306     $params = array();
307     //$params['forumid'] = $forum->id;
308     $recs = $DB->get_recordset_sql($sql, $params, 0, $forum->rssarticles);
310     //set a flag. Are we displaying discussions or posts?
311     $isdiscussion = true;
312     if (!empty($forum->rsstype) && $forum->rsstype!=1) {
313         $isdiscussion = false;
314     }
316     if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
317         print_error('invalidcoursemodule');
318     }
319     $context = context_module::instance($cm->id);
321     $formatoptions = new stdClass();
322     $items = array();
323     foreach ($recs as $rec) {
324             $item = new stdClass();
325             $user = new stdClass();
327             if ($isdiscussion && !forum_user_can_see_discussion($forum, $rec->discussionid, $context)) {
328                 // This is a discussion which the user has no permission to view
329                 $item->title = get_string('forumsubjecthidden', 'forum');
330                 $message = get_string('forumbodyhidden', 'forum');
331                 $item->author = get_string('forumauthorhidden', 'forum');
332             } else if (!$isdiscussion && !forum_user_can_see_post($forum, $rec->discussionid, $rec->postid, $USER, $cm)) {
333                 // This is a post which the user has no permission to view
334                 $item->title = get_string('forumsubjecthidden', 'forum');
335                 $message = get_string('forumbodyhidden', 'forum');
336                 $item->author = get_string('forumauthorhidden', 'forum');
337             } else {
338                 // The user must have permission to view
339                 if ($isdiscussion && !empty($rec->discussionname)) {
340                     $item->title = format_string($rec->discussionname);
341                 } else if (!empty($rec->postsubject)) {
342                     $item->title = format_string($rec->postsubject);
343                 } else {
344                     //we should have an item title by now but if we dont somehow then substitute something somewhat meaningful
345                     $item->title = format_string($forum->name.' '.userdate($rec->postcreated,get_string('strftimedatetimeshort', 'langconfig')));
346                 }
347                 $user->firstname = $rec->userfirstname;
348                 $user->lastname = $rec->userlastname;
349                 $item->author = fullname($user);
350                 $message = file_rewrite_pluginfile_urls($rec->postmessage, 'pluginfile.php', $context->id,
351                         'mod_forum', 'post', $rec->postid);
352                 $formatoptions->trusted = $rec->posttrust;
353             }
355             if ($isdiscussion) {
356                 $item->link = $CFG->wwwroot."/mod/forum/discuss.php?d=".$rec->discussionid;
357             } else {
358                 $item->link = $CFG->wwwroot."/mod/forum/discuss.php?d=".$rec->discussionid."&parent=".$rec->postid;
359             }
361             $formatoptions->trusted = $rec->posttrust;
362             $item->description = format_text($message, $rec->postformat, $formatoptions, $forum->course);
364             //TODO: MDL-31129 implement post attachment handling
365             /*if (!$isdiscussion) {
366                 $post_file_area_name = str_replace('//', '/', "$forum->course/$CFG->moddata/forum/$forum->id/$rec->postid");
367                 $post_files = get_directory_list("$CFG->dataroot/$post_file_area_name");
369                 if (!empty($post_files)) {
370                     $item->attachments = array();
371                 }
372             }*/
373             $item->pubdate = $rec->postcreated;
375             $items[] = $item;
376         }
377     $recs->close();
380     if (!empty($items)) {
381         //First the RSS header
382         $header = rss_standard_header(strip_tags(format_string($forum->name,true)),
383                                       $CFG->wwwroot."/mod/forum/view.php?f=".$forum->id,
384                                       format_string($forum->intro,true)); // TODO: fix format
385         //Now all the rss items
386         if (!empty($header)) {
387             $articles = rss_add_items($items);
388         }
389         //Now the RSS footer
390         if (!empty($header) && !empty($articles)) {
391             $footer = rss_standard_footer();
392         }
393         //Now, if everything is ok, concatenate it
394         if (!empty($header) && !empty($articles) && !empty($footer)) {
395             $status = $header.$articles.$footer;
396         } else {
397             $status = false;
398         }
399     } else {
400         $status = false;
401     }
403     return $status;