Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 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      $userfieldsapi = \core_user\fields::for_userpic();
 186      $userpicturefields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;
 187  
 188      $sql = "SELECT $postdata, d.id as discussionid, d.name as discussionname, d.timemodified, d.usermodified, d.groupid,
 189                     d.timestart, d.timeend, $userpicturefields
 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 AND p.deleted <> 1
 194                     $timelimit $groupselect $newsince
 195            ORDER BY $forumsort";
 196      return array($sql, $params);
 197  }
 198  
 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   */
 207  function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) {
 208      global $USER;
 209  
 210      $modcontext = context_module::instance($cm->id);
 211  
 212      // Get group enforcement SQL.
 213      $groupmode = groups_get_activity_groupmode($cm);
 214      $currentgroup = groups_get_activity_group($cm);
 215      $params = array();
 216  
 217      list($groupselect, $groupparams) = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
 218  
 219      // Add the groupparams to the params array.
 220      $params = array_merge($params, $groupparams);
 221  
 222      // Do we only want new posts?
 223      if ($newsince) {
 224          $params['newsince'] = $newsince;
 225          $newsince = " AND p.modified > :newsince";
 226      } else {
 227          $newsince = '';
 228      }
 229  
 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  
 239      $userfieldsapi = \core_user\fields::for_name();
 240      $usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
 241      $sql = "SELECT p.id AS postid,
 242                   d.id AS discussionid,
 243                   d.name AS discussionname,
 244                   d.groupid,
 245                   d.timestart,
 246                   d.timeend,
 247                   u.id AS userid,
 248                   $usernamefields,
 249                   p.subject AS postsubject,
 250                   p.message AS postmessage,
 251                   p.created AS postcreated,
 252                   p.messageformat AS postformat,
 253                   p.messagetrust AS posttrust,
 254                   p.parent as postparent
 255              FROM {forum_discussions} d,
 256                 {forum_posts} p,
 257                 {user} u
 258              WHERE d.forum = {$forum->id} AND
 259                  p.discussion = d.id AND p.deleted <> 1 AND
 260                  u.id = p.userid $newsince
 261                  $privatewhere
 262                  $groupselect
 263              ORDER BY p.created desc";
 264  
 265      return array($sql, $params);
 266  }
 267  
 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   */
 277  function forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext=null) {
 278      $groupselect = '';
 279      $params = array();
 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 {
 288              // Separate groups without access all.
 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";
 294              }
 295          }
 296      }
 297  
 298      return array($groupselect, $params);
 299  }
 300  
 301  /**
 302   * This function return the XML rss contents about the forum
 303   * It returns false if something is wrong
 304   *
 305   * @param stdClass $forum the forum object
 306   * @param string $sql the SQL used to retrieve the contents from the database
 307   * @param array $params the SQL parameters used
 308   * @param object $context the context this forum relates to
 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
 312   */
 313  
 314  function forum_rss_feed_contents($forum, $sql, $params, $context) {
 315      global $CFG, $DB, $USER;
 316  
 317      $status = true;
 318  
 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) {
 324          $isdiscussion = false;
 325      }
 326  
 327      if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
 328          throw new \moodle_exception('invalidcoursemodule');
 329      }
 330  
 331      $formatoptions = new stdClass();
 332      $items = array();
 333      foreach ($recs as $rec) {
 334              $item = new stdClass();
 335  
 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)) {
 351                  // This is a discussion which the user has no permission to view
 352                  $item->title = get_string('forumsubjecthidden', 'forum');
 353                  $message = get_string('forumbodyhidden', 'forum');
 354                  $item->author = get_string('forumauthorhidden', 'forum');
 355              } else if (!$isdiscussion && !forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
 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                  }
 367              } else {
 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                  }
 377                  $item->author = fullname($rec);
 378                  $message = file_rewrite_pluginfile_urls($rec->postmessage, 'pluginfile.php', $context->id,
 379                          'mod_forum', 'post', $rec->postid);
 380                  $formatoptions->trusted = $rec->posttrust;
 381              }
 382  
 383              if ($isdiscussion) {
 384                  $item->link = $CFG->wwwroot."/mod/forum/discuss.php?d=".$rec->discussionid;
 385              } else {
 386                  $item->link = $CFG->wwwroot."/mod/forum/discuss.php?d=".$rec->discussionid."&parent=".$rec->postid;
 387              }
 388  
 389              $formatoptions->trusted = $rec->posttrust;
 390              $item->description = format_text($message, $rec->postformat, $formatoptions, $forum->course);
 391  
 392              //TODO: MDL-31129 implement post attachment handling
 393              /*if (!$isdiscussion) {
 394                  $post_file_area_name = str_replace('//', '/', "$forum->course/$CFG->moddata/forum/$forum->id/$rec->postid");
 395                  $post_files = get_directory_list("$CFG->dataroot/$post_file_area_name");
 396  
 397                  if (!empty($post_files)) {
 398                      $item->attachments = array();
 399                  }
 400              }*/
 401              $item->pubdate = $rec->postcreated;
 402  
 403              $items[] = $item;
 404          }
 405      $recs->close();
 406  
 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 = '';
 413      if (!empty($items)) {
 414          $articles = rss_add_items($items);
 415      }
 416      // Create the RSS footer.
 417      $footer = rss_standard_footer();
 418  
 419      return $header . $articles . $footer;
 420  }