Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   1  <?php
   2  
   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  /**
  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   */
  26  
  27  /* Include the core RSS lib */
  28  require_once($CFG->libdir.'/rsslib.php');
  29  
  30  /**
  31   * Returns the path to the cached rss feed contents. Creates/updates the cache if necessary.
  32   * @param stdClass $context the context
  33   * @param array    $args    the arguments received in the url
  34   * @return string the full path to the cached RSS feed directory. Null if there is a problem.
  35   */
  36  function forum_rss_get_feed($context, $args) {
  37      global $CFG, $DB, $USER;
  38  
  39      $status = true;
  40  
  41      //are RSS feeds enabled?
  42      if (empty($CFG->forum_enablerssfeeds)) {
  43          debugging('DISABLED (module configuration)');
  44          return null;
  45      }
  46  
  47      $forumid  = clean_param($args[3], PARAM_INT);
  48      $cm = get_coursemodule_from_instance('forum', $forumid, 0, false, MUST_EXIST);
  49      $modcontext = context_module::instance($cm->id);
  50  
  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;
  54      }
  55  
  56      $forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
  57      if (!rss_enabled_for_mod('forum', $forum)) {
  58          return null;
  59      }
  60  
  61      //the sql that will retreive the data for the feed and be hashed to get the cache filename
  62      list($sql, $params) = forum_rss_get_sql($forum, $cm);
  63  
  64      // Hash the sql to get the cache file name.
  65      $filename = rss_get_file_name($forum, $sql, $params);
  66      $cachedfilepath = rss_get_file_full_name('mod_forum', $filename);
  67  
  68      //Is the cache out of date?
  69      $cachedfilelastmodified = 0;
  70      if (file_exists($cachedfilepath)) {
  71          $cachedfilelastmodified = filemtime($cachedfilepath);
  72      }
  73      // Used to determine if we need to generate a new RSS feed.
  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.
  78      if (($cachedfilelastmodified == 0) || (($dontrecheckcutoff > $cachedfilelastmodified) &&
  79          forum_rss_newstuff($forum, $cm, $cachedfilelastmodified))) {
  80          // Need to regenerate the cached version.
  81          $result = forum_rss_feed_contents($forum, $sql, $params, $modcontext);
  82          $status = rss_save_file('mod_forum', $filename, $result);
  83      }
  84  
  85      //return the path to the cached version
  86      return $cachedfilepath;
  87  }
  88  
  89  /**
  90   * Given a forum object, deletes all cached RSS files associated with it.
  91   *
  92   * @param stdClass $forum
  93   */
  94  function forum_rss_delete_file($forum) {
  95      rss_delete_file('mod_forum', $forum);
  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   *
 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
 109   */
 110  function forum_rss_newstuff($forum, $cm, $time) {
 111      global $DB;
 112  
 113      list($sql, $params) = forum_rss_get_sql($forum, $cm, $time);
 114  
 115      return $DB->record_exists_sql($sql, $params);
 116  }
 117  
 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   */
 126  function forum_rss_get_sql($forum, $cm, $time=0) {
 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);
 131      }
 132  }
 133  
 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   */
 142  function forum_rss_feed_discussions_sql($forum, $cm, $newsince=0) {
 143      global $CFG, $DB, $USER;
 144  
 145      $timelimit = '';
 146  
 147      $modcontext = null;
 148  
 149      $now = floor(time() / 60) * 60; // DB Cache Friendly.
 150      $params = array();
 151  
 152      $modcontext = context_module::instance($cm->id);
 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;
 162              }
 163              $timelimit .= ")";
 164          }
 165      }
 166  
 167      // Do we only want new posts?
 168      if ($newsince) {
 169          $params['newsince'] = $newsince;
 170          $newsince = " AND p.modified > :newsince";
 171      } else {
 172          $newsince = '';
 173      }
 174  
 175      // Get group enforcing SQL.
 176      $groupmode = groups_get_activity_groupmode($cm);
 177      $currentgroup = groups_get_activity_group($cm);
 178      list($groupselect, $groupparams) = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
 179  
 180      // Add the groupparams to the params array.
 181      $params = array_merge($params, $groupparams);
 182  
 183      $forumsort = "d.timemodified DESC";
 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";
 185      $userpicturefields = user_picture::fields('u', null, 'userid');
 186  
 187      $sql = "SELECT $postdata, d.id as discussionid, d.name as discussionname, d.timemodified, d.usermodified, d.groupid,
 188                     d.timestart, d.timeend, $userpicturefields
 189                FROM {forum_discussions} d
 190                     JOIN {forum_posts} p ON p.discussion = d.id
 191                     JOIN {user} u ON p.userid = u.id
 192               WHERE d.forum = {$forum->id} AND p.parent = 0 AND p.deleted <> 1
 193                     $timelimit $groupselect $newsince
 194            ORDER BY $forumsort";
 195      return array($sql, $params);
 196  }
 197  
 198  /**
 199   * Generates the SQL query used to get the Post details from the forum table of the database
 200   *
 201   * @param stdClass $forum     the forum object
 202   * @param stdClass $cm        Course Module object
 203   * @param int      $newsince  check for items since this epoch timestamp
 204   * @return string the SQL query to be used to get the Post details from the forum table of the database
 205   */
 206  function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) {
 207      global $USER;
 208  
 209      $modcontext = context_module::instance($cm->id);
 210  
 211      // Get group enforcement SQL.
 212      $groupmode = groups_get_activity_groupmode($cm);
 213      $currentgroup = groups_get_activity_group($cm);
 214      $params = array();
 215  
 216      list($groupselect, $groupparams) = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
 217  
 218      // Add the groupparams to the params array.
 219      $params = array_merge($params, $groupparams);
 220  
 221      // Do we only want new posts?
 222      if ($newsince) {
 223          $params['newsince'] = $newsince;
 224          $newsince = " AND p.modified > :newsince";
 225      } else {
 226          $newsince = '';
 227      }
 228  
 229      $canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $modcontext);
 230      if (!$canseeprivatereplies) {
 231          $privatewhere = ' AND (p.privatereplyto = :currentuser1 OR p.userid = :currentuser2 OR p.privatereplyto = 0)';
 232          $params['currentuser1'] = $USER->id;
 233          $params['currentuser2'] = $USER->id;
 234      } else {
 235          $privatewhere = '';
 236      }
 237  
 238      $usernamefields = get_all_user_name_fields(true, 'u');
 239      $sql = "SELECT p.id AS postid,
 240                   d.id AS discussionid,
 241                   d.name AS discussionname,
 242                   d.groupid,
 243                   d.timestart,
 244                   d.timeend,
 245                   u.id AS userid,
 246                   $usernamefields,
 247                   p.subject AS postsubject,
 248                   p.message AS postmessage,
 249                   p.created AS postcreated,
 250                   p.messageformat AS postformat,
 251                   p.messagetrust AS posttrust,
 252                   p.parent as postparent
 253              FROM {forum_discussions} d,
 254                 {forum_posts} p,
 255                 {user} u
 256              WHERE d.forum = {$forum->id} AND
 257                  p.discussion = d.id AND p.deleted <> 1 AND
 258                  u.id = p.userid $newsince
 259                  $privatewhere
 260                  $groupselect
 261              ORDER BY p.created desc";
 262  
 263      return array($sql, $params);
 264  }
 265  
 266  /**
 267   * Retrieve the correct SQL snippet for group-only forums
 268   *
 269   * @param stdClass $cm           Course Module object
 270   * @param int      $groupmode    the mode in which the forum's groups are operating
 271   * @param bool     $currentgroup true if the user is from the a group enabled on the forum
 272   * @param stdClass $modcontext   The context instance of the forum module
 273   * @return string SQL Query for group details of the forum
 274   */
 275  function forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext=null) {
 276      $groupselect = '';
 277      $params = array();
 278  
 279      if ($groupmode) {
 280          if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
 281              if ($currentgroup) {
 282                  $groupselect = "AND (d.groupid = :groupid OR d.groupid = -1)";
 283                  $params['groupid'] = $currentgroup;
 284              }
 285          } else {
 286              // Separate groups without access all.
 287              if ($currentgroup) {
 288                  $groupselect = "AND (d.groupid = :groupid OR d.groupid = -1)";
 289                  $params['groupid'] = $currentgroup;
 290              } else {
 291                  $groupselect = "AND d.groupid = -1";
 292              }
 293          }
 294      }
 295  
 296      return array($groupselect, $params);
 297  }
 298  
 299  /**
 300   * This function return the XML rss contents about the forum
 301   * It returns false if something is wrong
 302   *
 303   * @param stdClass $forum the forum object
 304   * @param string $sql the SQL used to retrieve the contents from the database
 305   * @param array $params the SQL parameters used
 306   * @param object $context the context this forum relates to
 307   * @return bool|string false if the contents is empty, otherwise the contents of the feed is returned
 308   *
 309   * @Todo MDL-31129 implement post attachment handling
 310   */
 311  
 312  function forum_rss_feed_contents($forum, $sql, $params, $context) {
 313      global $CFG, $DB, $USER;
 314  
 315      $status = true;
 316  
 317      $recs = $DB->get_recordset_sql($sql, $params, 0, $forum->rssarticles);
 318  
 319      //set a flag. Are we displaying discussions or posts?
 320      $isdiscussion = true;
 321      if (!empty($forum->rsstype) && $forum->rsstype!=1) {
 322          $isdiscussion = false;
 323      }
 324  
 325      if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
 326          print_error('invalidcoursemodule');
 327      }
 328  
 329      $formatoptions = new stdClass();
 330      $items = array();
 331      foreach ($recs as $rec) {
 332              $item = new stdClass();
 333  
 334              $discussion = new stdClass();
 335              $discussion->id = $rec->discussionid;
 336              $discussion->groupid = $rec->groupid;
 337              $discussion->timestart = $rec->timestart;
 338              $discussion->timeend = $rec->timeend;
 339  
 340              $post = null;
 341              if (!$isdiscussion) {
 342                  $post = new stdClass();
 343                  $post->id = $rec->postid;
 344                  $post->parent = $rec->postparent;
 345                  $post->userid = $rec->userid;
 346              }
 347  
 348              if ($isdiscussion && !forum_user_can_see_discussion($forum, $discussion, $context)) {
 349                  // This is a discussion which the user has no permission to view
 350                  $item->title = get_string('forumsubjecthidden', 'forum');
 351                  $message = get_string('forumbodyhidden', 'forum');
 352                  $item->author = get_string('forumauthorhidden', 'forum');
 353              } else if (!$isdiscussion && !forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
 354                  if (forum_user_can_see_post($forum, $discussion, $post, $USER, $cm, false)) {
 355                      // This is a post which the user has no permission to view.
 356                      $item->title = get_string('forumsubjecthidden', 'forum');
 357                      $message = get_string('forumbodyhidden', 'forum');
 358                      $item->author = get_string('forumauthorhidden', 'forum');
 359                  } else {
 360                      // This is a post which has been deleted.
 361                      $item->title = get_string('privacy:request:delete:post:subject', 'mod_forum');
 362                      $message = get_string('privacy:request:delete:post:subject', 'mod_forum');
 363                      $item->author = get_string('forumauthorhidden', 'forum');
 364                  }
 365              } else {
 366                  // The user must have permission to view
 367                  if ($isdiscussion && !empty($rec->discussionname)) {
 368                      $item->title = format_string($rec->discussionname);
 369                  } else if (!empty($rec->postsubject)) {
 370                      $item->title = format_string($rec->postsubject);
 371                  } else {
 372                      //we should have an item title by now but if we dont somehow then substitute something somewhat meaningful
 373                      $item->title = format_string($forum->name.' '.userdate($rec->postcreated,get_string('strftimedatetimeshort', 'langconfig')));
 374                  }
 375                  $item->author = fullname($rec);
 376                  $message = file_rewrite_pluginfile_urls($rec->postmessage, 'pluginfile.php', $context->id,
 377                          'mod_forum', 'post', $rec->postid);
 378                  $formatoptions->trusted = $rec->posttrust;
 379              }
 380  
 381              if ($isdiscussion) {
 382                  $item->link = $CFG->wwwroot."/mod/forum/discuss.php?d=".$rec->discussionid;
 383              } else {
 384                  $item->link = $CFG->wwwroot."/mod/forum/discuss.php?d=".$rec->discussionid."&parent=".$rec->postid;
 385              }
 386  
 387              $formatoptions->trusted = $rec->posttrust;
 388              $item->description = format_text($message, $rec->postformat, $formatoptions, $forum->course);
 389  
 390              //TODO: MDL-31129 implement post attachment handling
 391              /*if (!$isdiscussion) {
 392                  $post_file_area_name = str_replace('//', '/', "$forum->course/$CFG->moddata/forum/$forum->id/$rec->postid");
 393                  $post_files = get_directory_list("$CFG->dataroot/$post_file_area_name");
 394  
 395                  if (!empty($post_files)) {
 396                      $item->attachments = array();
 397                  }
 398              }*/
 399              $item->pubdate = $rec->postcreated;
 400  
 401              $items[] = $item;
 402          }
 403      $recs->close();
 404  
 405      // Create the RSS header.
 406      $header = rss_standard_header(strip_tags(format_string($forum->name,true)),
 407                                    $CFG->wwwroot."/mod/forum/view.php?f=".$forum->id,
 408                                    format_string($forum->intro,true)); // TODO: fix format
 409      // Now all the RSS items, if there are any.
 410      $articles = '';
 411      if (!empty($items)) {
 412          $articles = rss_add_items($items);
 413      }
 414      // Create the RSS footer.
 415      $footer = rss_standard_footer();
 416  
 417      return $header . $articles . $footer;
 418  }