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.
/blog/ -> locallib.php (source)

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Classes for Blogs.
  19   *
  20   * @package    moodlecore
  21   * @subpackage blog
  22   * @copyright  2009 Nicolas Connault
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  require_once($CFG->libdir . '/filelib.php');
  29  
  30  /**
  31   * Blog_entry class. Represents an entry in a user's blog. Contains all methods for managing this entry.
  32   * This class does not contain any HTML-generating code. See blog_listing sub-classes for such code.
  33   * This class follows the Object Relational Mapping technique, its member variables being mapped to
  34   * the fields of the post table.
  35   *
  36   * @package    moodlecore
  37   * @subpackage blog
  38   * @copyright  2009 Nicolas Connault
  39   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  class blog_entry implements renderable {
  42      // Public Database fields.
  43      public $id;
  44      public $userid;
  45      public $subject;
  46      public $summary;
  47      public $rating = 0;
  48      public $attachment;
  49      public $publishstate;
  50  
  51      // Locked Database fields (Don't touch these).
  52      public $courseid = 0;
  53      public $groupid = 0;
  54      public $module = 'blog';
  55      public $moduleid = 0;
  56      public $coursemoduleid = 0;
  57      public $content;
  58      public $format = 1;
  59      public $uniquehash = '';
  60      public $lastmodified;
  61      public $created;
  62      public $usermodified;
  63  
  64      // Other class variables.
  65      public $form;
  66      public $tags = array();
  67  
  68      /** @var StdClass Data needed to render the entry */
  69      public $renderable;
  70  
  71      /** @var string summary format. */
  72      public string $summaryformat;
  73  
  74      /** @var array summary editor. */
  75      public array $summary_editor;
  76  
  77      /** @var string */
  78      public $summarytrust;
  79  
  80      /** @var int course associated with the blog post. */
  81      public $courseassoc;
  82  
  83      /** @var string module associated with the blog post. */
  84      public $modassoc;
  85  
  86      /** @var mixed attachment. */
  87      public $attachment_filemanager;
  88  
  89      /** @var string blog post body. */
  90      public $body;
  91  
  92      /** @var int attachment entry id. */
  93      public $entryid;
  94  
  95      /** @var string|null submit button. */
  96      public $submitbutton;
  97  
  98      /** @var string|null user alias. */
  99      public $useridalias;
 100  
 101      /** @var string|null user picture. */
 102      public $picture;
 103  
 104      /** @var string|null user first name. */
 105      public $firstname;
 106  
 107      /** @var string|null user middle name. */
 108      public $middlename;
 109  
 110      /** @var string|null user last name. */
 111      public $lastname;
 112  
 113      /** @var string|null user first name phonetic. */
 114      public $firstnamephonetic;
 115  
 116      /** @var string|null user last name phonetic. */
 117      public $lastnamephonetic;
 118  
 119      /** @var string|null user alternate name. */
 120      public $alternatename;
 121  
 122      /** @var string|null user email address. */
 123      public $email;
 124  
 125      /** @var string */
 126      public $action;
 127  
 128      /** @var string|null user picture description. */
 129      public $imagealt;
 130  
 131      /** @var int module instance id. */
 132      public $modid;
 133  
 134      /**
 135       * Constructor. If given an id, will fetch the corresponding record from the DB.
 136       *
 137       * @param mixed $idorparams A blog entry id if INT, or data for a new entry if array
 138       */
 139      public function __construct($id=null, $params=null, $form=null) {
 140          global $DB, $PAGE, $CFG;
 141  
 142          if (!empty($id)) {
 143              $object = $DB->get_record('post', array('id' => $id));
 144              foreach ($object as $var => $val) {
 145                  $this->$var = $val;
 146              }
 147          } else if (!empty($params) && (is_array($params) || is_object($params))) {
 148              foreach ($params as $var => $val) {
 149                  $this->$var = $val;
 150              }
 151          }
 152  
 153          if (!empty($CFG->useblogassociations)) {
 154              $associations = $DB->get_records('blog_association', array('blogid' => $this->id));
 155              foreach ($associations as $association) {
 156                  $context = context::instance_by_id($association->contextid);
 157                  if ($context->contextlevel == CONTEXT_COURSE) {
 158                      $this->courseassoc = $association->contextid;
 159                  } else if ($context->contextlevel == CONTEXT_MODULE) {
 160                      $this->modassoc = $association->contextid;
 161                  }
 162              }
 163          }
 164  
 165          $this->form = $form;
 166      }
 167  
 168  
 169      /**
 170       * Gets the required data to print the entry
 171       */
 172      public function prepare_render() {
 173  
 174          global $DB, $CFG, $PAGE;
 175  
 176          $this->renderable = new StdClass();
 177  
 178          $this->renderable->user = $DB->get_record('user', array('id' => $this->userid));
 179  
 180          // Entry comments.
 181          if (!empty($CFG->usecomments) and $CFG->blogusecomments) {
 182              require_once($CFG->dirroot . '/comment/lib.php');
 183  
 184              $cmt = new stdClass();
 185              $cmt->context = context_user::instance($this->userid);
 186              $cmt->courseid = $PAGE->course->id;
 187              $cmt->area = 'format_blog';
 188              $cmt->itemid = $this->id;
 189              $cmt->showcount = $CFG->blogshowcommentscount;
 190              $cmt->component = 'blog';
 191              $this->renderable->comment = new comment($cmt);
 192          }
 193  
 194          $this->summary = file_rewrite_pluginfile_urls($this->summary, 'pluginfile.php', SYSCONTEXTID, 'blog', 'post', $this->id);
 195  
 196          // External blog link.
 197          if ($this->uniquehash && $this->content) {
 198              if ($externalblog = $DB->get_record('blog_external', array('id' => $this->content))) {
 199                  $urlparts = parse_url($externalblog->url);
 200                  $this->renderable->externalblogtext = get_string('retrievedfrom', 'blog') . get_string('labelsep', 'langconfig');
 201                  $this->renderable->externalblogtext .= html_writer::link($urlparts['scheme'] . '://' . $urlparts['host'],
 202                                                                           $externalblog->name);
 203              }
 204          }
 205  
 206          // Retrieve associations.
 207          $this->renderable->unassociatedentry = false;
 208          if (!empty($CFG->useblogassociations)) {
 209  
 210              // Adding the entry associations data.
 211              if ($associations = $associations = $DB->get_records('blog_association', array('blogid' => $this->id))) {
 212  
 213                  // Check to see if the entry is unassociated with group/course level access.
 214                  if ($this->publishstate == 'group' || $this->publishstate == 'course') {
 215                      $this->renderable->unassociatedentry = true;
 216                  }
 217  
 218                  foreach ($associations as $key => $assocrec) {
 219  
 220                      if (!$context = context::instance_by_id($assocrec->contextid, IGNORE_MISSING)) {
 221                          unset($associations[$key]);
 222                          continue;
 223                      }
 224  
 225                      // The renderer will need the contextlevel of the association.
 226                      $associations[$key]->contextlevel = $context->contextlevel;
 227  
 228                      // Course associations.
 229                      if ($context->contextlevel == CONTEXT_COURSE) {
 230                          // TODO: performance!!!!
 231                          $instancename = $DB->get_field('course', 'shortname', array('id' => $context->instanceid));
 232  
 233                          $associations[$key]->url = $assocurl = new moodle_url('/course/view.php',
 234                                                                                array('id' => $context->instanceid));
 235                          $associations[$key]->text = $instancename;
 236                          $associations[$key]->icon = new pix_icon('i/course', $associations[$key]->text);
 237                      }
 238  
 239                      // Mod associations.
 240                      if ($context->contextlevel == CONTEXT_MODULE) {
 241  
 242                          // Getting the activity type and the activity instance id.
 243                          $sql = 'SELECT cm.instance, m.name FROM {course_modules} cm
 244                                    JOIN {modules} m ON m.id = cm.module
 245                                   WHERE cm.id = :cmid';
 246                          $modinfo = $DB->get_record_sql($sql, array('cmid' => $context->instanceid));
 247                          // TODO: performance!!!!
 248                          $instancename = $DB->get_field($modinfo->name, 'name', array('id' => $modinfo->instance));
 249  
 250                          $associations[$key]->type = get_string('modulename', $modinfo->name);
 251                          $associations[$key]->url = new moodle_url('/mod/' . $modinfo->name . '/view.php',
 252                                                                    array('id' => $context->instanceid));
 253                          $associations[$key]->text = $instancename;
 254                          $associations[$key]->icon = new pix_icon('icon', $associations[$key]->text, $modinfo->name);
 255                      }
 256                  }
 257              }
 258              $this->renderable->blogassociations = $associations;
 259          }
 260  
 261          // Entry attachments.
 262          $this->renderable->attachments = $this->get_attachments();
 263  
 264          $this->renderable->usercanedit = blog_user_can_edit_entry($this);
 265      }
 266  
 267  
 268      /**
 269       * Gets the entry attachments list
 270       * @return array List of blog_entry_attachment instances
 271       */
 272      public function get_attachments() {
 273  
 274          global $CFG;
 275  
 276          require_once($CFG->libdir.'/filelib.php');
 277  
 278          $syscontext = context_system::instance();
 279  
 280          $fs = get_file_storage();
 281          $files = $fs->get_area_files($syscontext->id, 'blog', 'attachment', $this->id);
 282  
 283          // Adding a blog_entry_attachment for each non-directory file.
 284          $attachments = array();
 285          foreach ($files as $file) {
 286              if ($file->is_directory()) {
 287                  continue;
 288              }
 289              $attachments[] = new blog_entry_attachment($file, $this->id);
 290          }
 291  
 292          return $attachments;
 293      }
 294  
 295      /**
 296       * Inserts this entry in the database. Access control checks must be done by calling code.
 297       *
 298       * @param mform $form Used for attachments
 299       * @return void
 300       */
 301      public function process_attachment($form) {
 302          $this->form = $form;
 303      }
 304  
 305      /**
 306       * Inserts this entry in the database. Access control checks must be done by calling code.
 307       * TODO Set the publishstate correctly
 308       * @return void
 309       */
 310      public function add() {
 311          global $CFG, $USER, $DB;
 312  
 313          unset($this->id);
 314          $this->module       = 'blog';
 315          $this->userid       = (empty($this->userid)) ? $USER->id : $this->userid;
 316          $this->lastmodified = time();
 317          $this->created      = time();
 318  
 319          // Insert the new blog entry.
 320          $this->id = $DB->insert_record('post', $this);
 321  
 322          if (!empty($CFG->useblogassociations)) {
 323              $this->add_associations();
 324          }
 325  
 326          core_tag_tag::set_item_tags('core', 'post', $this->id, context_user::instance($this->userid), $this->tags);
 327  
 328          // Trigger an event for the new entry.
 329          $event = \core\event\blog_entry_created::create(array(
 330              'objectid'      => $this->id,
 331              'relateduserid' => $this->userid
 332          ));
 333          $event->set_blog_entry($this);
 334          $event->trigger();
 335      }
 336  
 337      /**
 338       * Updates this entry in the database. Access control checks must be done by calling code.
 339       *
 340       * @param array       $params            Entry parameters.
 341       * @param moodleform  $form              Used for attachments.
 342       * @param array       $summaryoptions    Summary options.
 343       * @param array       $attachmentoptions Attachment options.
 344       *
 345       * @return void
 346       */
 347      public function edit($params=array(), $form=null, $summaryoptions=array(), $attachmentoptions=array()) {
 348          global $CFG, $DB;
 349  
 350          $sitecontext = context_system::instance();
 351          $entry = $this;
 352  
 353          $this->form = $form;
 354          foreach ($params as $var => $val) {
 355              $entry->$var = $val;
 356          }
 357  
 358          $entry = file_postupdate_standard_editor($entry, 'summary', $summaryoptions, $sitecontext, 'blog', 'post', $entry->id);
 359          $entry = file_postupdate_standard_filemanager($entry,
 360                                                        'attachment',
 361                                                        $attachmentoptions,
 362                                                        $sitecontext,
 363                                                        'blog',
 364                                                        'attachment',
 365                                                        $entry->id);
 366  
 367          if (!empty($CFG->useblogassociations)) {
 368              $entry->add_associations();
 369          }
 370  
 371          $entry->lastmodified = time();
 372  
 373          // Update record.
 374          $DB->update_record('post', $entry);
 375          core_tag_tag::set_item_tags('core', 'post', $entry->id, context_user::instance($this->userid), $entry->tags);
 376  
 377          $event = \core\event\blog_entry_updated::create(array(
 378              'objectid'      => $entry->id,
 379              'relateduserid' => $entry->userid
 380          ));
 381          $event->set_blog_entry($entry);
 382          $event->trigger();
 383      }
 384  
 385      /**
 386       * Deletes this entry from the database. Access control checks must be done by calling code.
 387       *
 388       * @return void
 389       */
 390      public function delete() {
 391          global $DB;
 392  
 393          $this->delete_attachments();
 394          $this->remove_associations();
 395  
 396          // Get record to pass onto the event.
 397          $record = $DB->get_record('post', array('id' => $this->id));
 398          $DB->delete_records('post', array('id' => $this->id));
 399          core_tag_tag::remove_all_item_tags('core', 'post', $this->id);
 400  
 401          $event = \core\event\blog_entry_deleted::create(array(
 402              'objectid'      => $this->id,
 403              'relateduserid' => $this->userid
 404              ));
 405          $event->add_record_snapshot("post", $record);
 406          $event->set_blog_entry($this);
 407          $event->trigger();
 408      }
 409  
 410      /**
 411       * Function to add all context associations to an entry.
 412       *
 413       * @param string $unused This does nothing, do not use it.
 414       */
 415      public function add_associations($unused = null) {
 416  
 417          if ($unused !== null) {
 418              debugging('Illegal argument used in blog_entry->add_associations()', DEBUG_DEVELOPER);
 419          }
 420  
 421          $this->remove_associations();
 422  
 423          if (!empty($this->courseassoc)) {
 424              $this->add_association($this->courseassoc);
 425          }
 426  
 427          if (!empty($this->modassoc)) {
 428              $this->add_association($this->modassoc);
 429          }
 430      }
 431  
 432      /**
 433       * Add a single association for a blog entry
 434       *
 435       * @param int $contextid - id of context to associate with the blog entry.
 436       * @param string $unused This does nothing, do not use it.
 437       */
 438      public function add_association($contextid, $unused = null) {
 439          global $DB;
 440  
 441          if ($unused !== null) {
 442              debugging('Illegal argument used in blog_entry->add_association()', DEBUG_DEVELOPER);
 443          }
 444  
 445          $assocobject = new StdClass;
 446          $assocobject->contextid = $contextid;
 447          $assocobject->blogid = $this->id;
 448          $id = $DB->insert_record('blog_association', $assocobject);
 449  
 450          // Trigger an association created event.
 451          $context = context::instance_by_id($contextid);
 452          $eventparam = array(
 453              'objectid' => $id,
 454              'other' => array('associateid' => $context->instanceid, 'subject' => $this->subject, 'blogid' => $this->id),
 455              'relateduserid' => $this->userid
 456          );
 457          if ($context->contextlevel == CONTEXT_COURSE) {
 458              $eventparam['other']['associatetype'] = 'course';
 459  
 460          } else if ($context->contextlevel == CONTEXT_MODULE) {
 461              $eventparam['other']['associatetype'] = 'coursemodule';
 462          }
 463          $event = \core\event\blog_association_created::create($eventparam);
 464          $event->trigger();
 465      }
 466  
 467      /**
 468       * remove all associations for a blog entry
 469       *
 470       * @return void
 471       */
 472      public function remove_associations() {
 473          global $DB;
 474  
 475          $associations = $DB->get_records('blog_association', array('blogid' => $this->id));
 476          foreach ($associations as $association) {
 477  
 478              // Trigger an association deleted event.
 479              $context = context::instance_by_id($association->contextid);
 480              $eventparam = array(
 481                  'objectid' => $this->id,
 482                  'other' => array('subject' => $this->subject, 'blogid' => $this->id),
 483                  'relateduserid' => $this->userid
 484              );
 485              $event = \core\event\blog_association_deleted::create($eventparam);
 486              $event->add_record_snapshot('blog_association', $association);
 487              $event->trigger();
 488  
 489              // Now remove the association.
 490              $DB->delete_records('blog_association', array('id' => $association->id));
 491          }
 492      }
 493  
 494      /**
 495       * Deletes all the user files in the attachments area for an entry
 496       *
 497       * @return void
 498       */
 499      public function delete_attachments() {
 500          $fs = get_file_storage();
 501          $fs->delete_area_files(SYSCONTEXTID, 'blog', 'attachment', $this->id);
 502          $fs->delete_area_files(SYSCONTEXTID, 'blog', 'post', $this->id);
 503      }
 504  
 505      /**
 506       * User can edit a blog entry if this is their own blog entry and they have
 507       * the capability moodle/blog:create, or if they have the capability
 508       * moodle/blog:manageentries.
 509       * This also applies to deleting of entries.
 510       *
 511       * @param int $userid Optional. If not given, $USER is used
 512       * @return boolean
 513       */
 514      public function can_user_edit($userid=null) {
 515          global $CFG, $USER;
 516  
 517          if (empty($userid)) {
 518              $userid = $USER->id;
 519          }
 520  
 521          $sitecontext = context_system::instance();
 522  
 523          if (has_capability('moodle/blog:manageentries', $sitecontext)) {
 524              return true; // Can edit any blog entry.
 525          }
 526  
 527          if ($this->userid == $userid && has_capability('moodle/blog:create', $sitecontext)) {
 528              return true; // Can edit own when having blog:create capability.
 529          }
 530  
 531          return false;
 532      }
 533  
 534      /**
 535       * Checks to see if a user can view the blogs of another user.
 536       * Only blog level is checked here, the capabilities are enforced
 537       * in blog/index.php
 538       *
 539       * @param int $targetuserid ID of the user we are checking
 540       *
 541       * @return bool
 542       */
 543      public function can_user_view($targetuserid) {
 544          global $CFG, $USER, $DB;
 545          $sitecontext = context_system::instance();
 546  
 547          if (empty($CFG->enableblogs) || !has_capability('moodle/blog:view', $sitecontext)) {
 548              return false; // Blog system disabled or user has no blog view capability.
 549          }
 550  
 551          if (isloggedin() && $USER->id == $targetuserid) {
 552              return true; // Can view own entries in any case.
 553          }
 554  
 555          if (has_capability('moodle/blog:manageentries', $sitecontext)) {
 556              return true; // Can manage all entries.
 557          }
 558  
 559          // Coming for 1 entry, make sure it's not a draft.
 560          if ($this->publishstate == 'draft' && !has_capability('moodle/blog:viewdrafts', $sitecontext)) {
 561              return false;  // Can not view draft of others.
 562          }
 563  
 564          // Coming for 1 entry, make sure user is logged in, if not a public blog.
 565          if ($this->publishstate != 'public' && !isloggedin()) {
 566              return false;
 567          }
 568  
 569          switch ($CFG->bloglevel) {
 570              case BLOG_GLOBAL_LEVEL:
 571                  return true;
 572                  break;
 573  
 574              case BLOG_SITE_LEVEL:
 575                  if (isloggedin()) { // Not logged in viewers forbidden.
 576                      return true;
 577                  }
 578                  return false;
 579                  break;
 580  
 581              case BLOG_USER_LEVEL:
 582              default:
 583                  $personalcontext = context_user::instance($targetuserid);
 584                  return has_capability('moodle/user:readuserblogs', $personalcontext);
 585                  break;
 586          }
 587      }
 588  
 589      /**
 590       * Use this function to retrieve a list of publish states available for
 591       * the currently logged in user.
 592       *
 593       * @return array This function returns an array ideal for sending to moodles'
 594       *                choose_from_menu function.
 595       */
 596  
 597      public static function get_applicable_publish_states() {
 598          global $CFG;
 599          $options = array();
 600  
 601          // Everyone gets draft access.
 602          if ($CFG->bloglevel >= BLOG_USER_LEVEL) {
 603              $options['draft'] = get_string('publishtonoone', 'blog');
 604          }
 605  
 606          if ($CFG->bloglevel > BLOG_USER_LEVEL) {
 607              $options['site'] = get_string('publishtosite', 'blog');
 608          }
 609  
 610          if ($CFG->bloglevel >= BLOG_GLOBAL_LEVEL) {
 611              $options['public'] = get_string('publishtoworld', 'blog');
 612          }
 613  
 614          return $options;
 615      }
 616  }
 617  
 618  /**
 619   * Abstract Blog_Listing class: used to gather blog entries and output them as listings. One of the subclasses must be used.
 620   *
 621   * @package    moodlecore
 622   * @subpackage blog
 623   * @copyright  2009 Nicolas Connault
 624   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 625   */
 626  class blog_listing {
 627      /**
 628       * Array of blog_entry objects.
 629       * @var array $entries
 630       */
 631      public $entries = null;
 632  
 633      /**
 634       * Caches the total number of the entries.
 635       * @var int
 636       */
 637      public $totalentries = null;
 638  
 639      /**
 640       * An array of blog_filter_* objects
 641       * @var array $filters
 642       */
 643      public $filters = array();
 644  
 645      /**
 646       * Constructor
 647       *
 648       * @param array $filters An associative array of filtername => filterid
 649       */
 650      public function __construct($filters=array()) {
 651          // Unset filters overridden by more specific filters.
 652          foreach ($filters as $type => $id) {
 653              if (!empty($type) && !empty($id)) {
 654                  $this->filters[$type] = blog_filter::get_instance($id, $type);
 655              }
 656          }
 657  
 658          foreach ($this->filters as $type => $filter) {
 659              foreach ($filter->overrides as $override) {
 660                  if (array_key_exists($override, $this->filters)) {
 661                      unset($this->filters[$override]);
 662                  }
 663              }
 664          }
 665      }
 666  
 667      /**
 668       * Fetches the array of blog entries.
 669       *
 670       * @return array
 671       */
 672      public function get_entries($start=0, $limit=10) {
 673          global $DB;
 674  
 675          if ($this->entries === null) {
 676              if ($sqlarray = $this->get_entry_fetch_sql(false, 'created DESC')) {
 677                  $this->entries = $DB->get_records_sql($sqlarray['sql'], $sqlarray['params'], $start, $limit);
 678                  if (!$start && count($this->entries) < $limit) {
 679                      $this->totalentries = count($this->entries);
 680                  }
 681              } else {
 682                  return false;
 683              }
 684          }
 685  
 686          return $this->entries;
 687      }
 688  
 689      /**
 690       * Finds total number of blog entries
 691       *
 692       * @return int
 693       */
 694      public function count_entries() {
 695          global $DB;
 696          if ($this->totalentries === null) {
 697              if ($sqlarray = $this->get_entry_fetch_sql(true)) {
 698                  $this->totalentries = $DB->count_records_sql($sqlarray['sql'], $sqlarray['params']);
 699              } else {
 700                  $this->totalentries = 0;
 701              }
 702          }
 703          return $this->totalentries;
 704      }
 705  
 706      public function get_entry_fetch_sql($count=false, $sort='lastmodified DESC', $userid = false) {
 707          global $DB, $USER, $CFG;
 708  
 709          if (!$userid) {
 710              $userid = $USER->id;
 711          }
 712          $userfieldsapi = \core_user\fields::for_userpic();
 713          $allnamefields = $userfieldsapi->get_sql('u', false, '', 'useridalias', false)->selects;
 714          // The query used to locate blog entries is complicated.  It will be built from the following components:
 715          $requiredfields = "p.*, $allnamefields";  // The SELECT clause.
 716          $tables = array('p' => 'post', 'u' => 'user');   // Components of the FROM clause (table_id => table_name).
 717          // Components of the WHERE clause (conjunction).
 718          $conditions = array('u.deleted = 0', 'p.userid = u.id', '(p.module = \'blog\' OR p.module = \'blog_external\')');
 719  
 720          // Build up a clause for permission constraints.
 721  
 722          $params = array();
 723  
 724          // Fix for MDL-9165, use with readuserblogs capability in a user context can read that user's private blogs.
 725          // Admins can see all blogs regardless of publish states, as described on the help page.
 726          if (has_capability('moodle/user:readuserblogs', context_system::instance())) {
 727              // Don't add permission constraints.
 728  
 729          } else if (!empty($this->filters['user'])
 730                     && has_capability('moodle/user:readuserblogs',
 731                                       context_user::instance((empty($this->filters['user']->id) ? 0 : $this->filters['user']->id)))) {
 732              // Don't add permission constraints.
 733  
 734          } else {
 735              if (isloggedin() and !isguestuser()) {
 736                  // Dont check association records if there aren't any.
 737                  $assocexists = $DB->record_exists('blog_association', array());
 738  
 739                  // Begin permission sql clause.
 740                  $permissionsql = '(p.userid = ? ';
 741                  $params[] = $userid;
 742  
 743                  if ($CFG->bloglevel >= BLOG_SITE_LEVEL) { // Add permission to view site-level entries.
 744                      $permissionsql .= " OR p.publishstate = 'site' ";
 745                  }
 746  
 747                  if ($CFG->bloglevel >= BLOG_GLOBAL_LEVEL) { // Add permission to view global entries.
 748                      $permissionsql .= " OR p.publishstate = 'public' ";
 749                  }
 750  
 751                  $permissionsql .= ') ';   // Close permissions sql clause.
 752              } else {  // Default is access to public entries.
 753                  $permissionsql = "p.publishstate = 'public'";
 754              }
 755              $conditions[] = $permissionsql;  // Add permission constraints.
 756          }
 757  
 758          foreach ($this->filters as $type => $blogfilter) {
 759              $conditions = array_merge($conditions, $blogfilter->conditions);
 760              $params = array_merge($params, $blogfilter->params);
 761              $tables = array_merge($tables, $blogfilter->tables);
 762          }
 763  
 764          $tablessql = '';  // Build up the FROM clause.
 765          foreach ($tables as $tablename => $table) {
 766              $tablessql .= ($tablessql ? ', ' : '').'{'.$table.'} '.$tablename;
 767          }
 768  
 769          $sql = ($count) ? 'SELECT COUNT(*)' : 'SELECT ' . $requiredfields;
 770          $sql .= " FROM $tablessql WHERE " . implode(' AND ', $conditions);
 771          $sql .= ($count) ? '' : " ORDER BY $sort";
 772  
 773          return array('sql' => $sql, 'params' => $params);
 774      }
 775  
 776      /**
 777       * Outputs all the blog entries aggregated by this blog listing.
 778       *
 779       * @return void
 780       */
 781      public function print_entries() {
 782          global $CFG, $USER, $DB, $OUTPUT, $PAGE;
 783          $sitecontext = context_system::instance();
 784  
 785          // Blog renderer.
 786          $output = $PAGE->get_renderer('blog');
 787  
 788          $page  = optional_param('blogpage', 0, PARAM_INT);
 789          $limit = optional_param('limit', get_user_preferences('blogpagesize', 10), PARAM_INT);
 790          $start = $page * $limit;
 791  
 792          $morelink = '<br />&nbsp;&nbsp;';
 793  
 794          $entries = $this->get_entries($start, $limit);
 795          $totalentries = $this->count_entries();
 796          $pagingbar = new paging_bar($totalentries, $page, $limit, $this->get_baseurl());
 797          $pagingbar->pagevar = 'blogpage';
 798          $blogheaders = blog_get_headers();
 799  
 800          echo $OUTPUT->render($pagingbar);
 801  
 802          if (has_capability('moodle/blog:create', $sitecontext)) {
 803              // The user's blog is enabled and they are viewing their own blog.
 804              $userid = optional_param('userid', null, PARAM_INT);
 805  
 806              if (empty($userid) || (!empty($userid) && $userid == $USER->id)) {
 807  
 808                  $courseid = optional_param('courseid', null, PARAM_INT);
 809                  $modid = optional_param('modid', null, PARAM_INT);
 810  
 811                  $addurl = new moodle_url("$CFG->wwwroot/blog/edit.php");
 812                  $urlparams = array('action' => 'add',
 813                                     'userid' => $userid,
 814                                     'courseid' => $courseid,
 815                                     'groupid' => optional_param('groupid', null, PARAM_INT),
 816                                     'modid' => $modid,
 817                                     'tagid' => optional_param('tagid', null, PARAM_INT),
 818                                     'tag' => optional_param('tag', null, PARAM_INT),
 819                                     'search' => optional_param('search', null, PARAM_INT));
 820  
 821                  $urlparams = array_filter($urlparams);
 822                  $addurl->params($urlparams);
 823  
 824                  $addlink = '<div class="addbloglink">';
 825                  $addlink .= '<a href="'.$addurl->out().'">'. $blogheaders['stradd'].'</a>';
 826                  $addlink .= '</div>';
 827                  echo $addlink;
 828              }
 829          }
 830  
 831          if ($entries) {
 832              $count = 0;
 833              foreach ($entries as $entry) {
 834                  $blogentry = new blog_entry(null, $entry);
 835  
 836                  // Get the required blog entry data to render it.
 837                  $blogentry->prepare_render();
 838                  echo $output->render($blogentry);
 839  
 840                  $count++;
 841              }
 842  
 843              echo $OUTPUT->render($pagingbar);
 844  
 845              if (!$count) {
 846                  print '<br /><div style="text-align:center">'. get_string('noentriesyet', 'blog') .'</div><br />';
 847              }
 848  
 849              print $morelink.'<br />'."\n";
 850              return;
 851          }
 852      }
 853  
 854      // Find the base url from $_GET variables, for print_paging_bar.
 855      public function get_baseurl() {
 856          $getcopy  = $_GET;
 857  
 858          unset($getcopy['blogpage']);
 859  
 860          if (!empty($getcopy)) {
 861              $first = false;
 862              $querystring = '';
 863  
 864              foreach ($getcopy as $var => $val) {
 865                  if (!$first) {
 866                      $first = true;
 867                      $querystring .= "?$var=$val";
 868                  } else {
 869                      $querystring .= '&amp;'.$var.'='.$val;
 870                      $hasparam = true;
 871                  }
 872              }
 873          } else {
 874              $querystring = '?';
 875          }
 876  
 877          return strip_querystring(qualified_me()) . $querystring;
 878  
 879      }
 880  }
 881  
 882  /**
 883   * Abstract class for blog_filter objects.
 884   * A set of core filters are implemented here. To write new filters, you need to subclass
 885   * blog_filter and give it the name of the type you want (for example, blog_filter_entry).
 886   * The blog_filter abstract class will automatically use it when the filter is added to the
 887   * URL. The first parameter of the constructor is the ID of your filter, but it can be a string
 888   * or have any other meaning you wish it to have. The second parameter is called $type and is
 889   * used as a sub-type for filters that have a very similar implementation (see blog_filter_context for an example)
 890   */
 891  abstract class blog_filter {
 892      /**
 893       * An array of strings representing the available filter types for each blog_filter.
 894       * @var array $availabletypes
 895       */
 896      public $availabletypes = array();
 897  
 898      /**
 899       * The type of filter (for example, types of blog_filter_context are site, course and module)
 900       * @var string $type
 901       */
 902      public $type;
 903  
 904      /**
 905       * The unique ID for a filter's associated record
 906       * @var int $id
 907       */
 908      public $id;
 909  
 910      /**
 911       * An array of table aliases that are used in the WHERE conditions
 912       * @var array $tables
 913       */
 914      public $tables = array();
 915  
 916      /**
 917       * An array of WHERE conditions
 918       * @var array $conditions
 919       */
 920      public $conditions = array();
 921  
 922      /**
 923       * An array of SQL params
 924       * @var array $params
 925       */
 926      public $params = array();
 927  
 928      /**
 929       * An array of filter types which this particular filter type overrides: their conditions will not be evaluated
 930       */
 931      public $overrides = array();
 932  
 933      public function __construct($id, $type=null) {
 934          $this->id = $id;
 935          $this->type = $type;
 936      }
 937  
 938      /**
 939       * TODO This is poor design. A parent class should not know anything about its children.
 940       * The default case helps to resolve this design issue
 941       */
 942      public static function get_instance($id, $type) {
 943  
 944          switch ($type) {
 945              case 'site':
 946              case 'course':
 947              case 'module':
 948                  return new blog_filter_context($id, $type);
 949                  break;
 950  
 951              case 'group':
 952              case 'user':
 953                  return new blog_filter_user($id, $type);
 954                  break;
 955  
 956              case 'tag':
 957                  return new blog_filter_tag($id);
 958                  break;
 959  
 960              default:
 961                  $classname = "blog_filter_$type";
 962                  if (class_exists($classname)) {
 963                      return new $classname($id, $type);
 964                  }
 965          }
 966      }
 967  }
 968  
 969  /**
 970   * This filter defines the context level of the blog entries being searched: site, course, module
 971   */
 972  class blog_filter_context extends blog_filter {
 973      /**
 974       * Constructor
 975       *
 976       * @param string $type
 977       * @param int    $id
 978       */
 979      public function __construct($id=null, $type='site') {
 980          global $SITE, $CFG, $DB;
 981  
 982          if (empty($id)) {
 983              $this->type = 'site';
 984          } else {
 985              $this->id = $id;
 986              $this->type = $type;
 987          }
 988  
 989          $this->availabletypes = array('site' => get_string('site'),
 990                                        'course' => get_string('course'),
 991                                        'module' => get_string('activity'),
 992                                        'context' => get_string('coresystem'));
 993  
 994          switch ($this->type) {
 995              case 'course': // Careful of site course!
 996                  // Ignore course filter if blog associations are not enabled.
 997                  if ($this->id != $SITE->id && !empty($CFG->useblogassociations)) {
 998                      $this->overrides = array('site', 'context');
 999                      $context = context_course::instance($this->id);
1000                      $this->tables['ba'] = 'blog_association';
1001                      $this->conditions[] = 'p.id = ba.blogid';
1002                      $this->conditions[] = 'ba.contextid = '.$context->id;
1003                      break;
1004                  } else {
1005                      // We are dealing with the site course, do not break from the current case.
1006                  }
1007  
1008              case 'site':
1009                  // No special constraints.
1010                  break;
1011              case 'module':
1012                  if (!empty($CFG->useblogassociations)) {
1013                      $this->overrides = array('course', 'site', 'context');
1014  
1015                      $context = context_module::instance($this->id);
1016                      $this->tables['ba'] = 'blog_association';
1017                      $this->tables['p']  = 'post';
1018                      $this->conditions = array('p.id = ba.blogid', 'ba.contextid = ?');
1019                      $this->params = array($context->id);
1020                  }
1021                  break;
1022              case 'context':
1023                  if ($id != context_system::instance()->id && !empty($CFG->useblogassociations)) {
1024                      $this->overrides = array('site');
1025                      $context = context::instance_by_id($this->id);
1026                      $this->tables['ba'] = 'blog_association';
1027                      $this->tables['ctx'] = 'context';
1028                      $this->conditions[] = 'p.id = ba.blogid';
1029                      $this->conditions[] = 'ctx.id = ba.contextid';
1030                      $this->conditions[] = 'ctx.path LIKE ?';
1031                      $this->params = array($context->path . '%');
1032                  }
1033                  break;
1034  
1035          }
1036      }
1037  }
1038  
1039  /**
1040   * This filter defines the user level of the blog entries being searched: a userid or a groupid.
1041   * It can be combined with a context filter in order to refine the search.
1042   */
1043  class blog_filter_user extends blog_filter {
1044      public $tables = array('u' => 'user');
1045  
1046      /**
1047       * Constructor
1048       *
1049       * @param string $type
1050       * @param int    $id
1051       */
1052      public function __construct($id=null, $type='user') {
1053          global $CFG, $DB, $USER;
1054          $this->availabletypes = array('user' => get_string('user'), 'group' => get_string('group'));
1055  
1056          if (empty($id)) {
1057              $this->id = $USER->id;
1058              $this->type = 'user';
1059          } else {
1060              $this->id = $id;
1061              $this->type = $type;
1062          }
1063  
1064          if ($this->type == 'user') {
1065              $this->conditions = array('u.id = ?');
1066              $this->params = array($this->id);
1067              $this->overrides = array('group');
1068  
1069          } else if ($this->type == 'group') {
1070              $this->overrides = array('course', 'site');
1071  
1072              $this->tables['gm'] = 'groups_members';
1073              $this->conditions[] = 'p.userid = gm.userid';
1074              $this->conditions[] = 'gm.groupid = ?';
1075              $this->params[]     = $this->id;
1076  
1077              if (!empty($CFG->useblogassociations)) {  // Only show blog entries associated with this course.
1078                  $coursecontext     = context_course::instance($DB->get_field('groups', 'courseid', array('id' => $this->id)));
1079                  $this->tables['ba'] = 'blog_association';
1080                  $this->conditions[] = 'gm.groupid = ?';
1081                  $this->conditions[] = 'ba.contextid = ?';
1082                  $this->conditions[] = 'ba.blogid = p.id';
1083                  $this->params[]     = $this->id;
1084                  $this->params[]     = $coursecontext->id;
1085              }
1086          }
1087  
1088      }
1089  }
1090  
1091  /**
1092   * This filter defines a tag by which blog entries should be searched.
1093   */
1094  class blog_filter_tag extends blog_filter {
1095      public $tables = array('t' => 'tag', 'ti' => 'tag_instance', 'p' => 'post');
1096  
1097      /**
1098       * Constructor
1099       *
1100       * @return void
1101       */
1102      public function __construct($id) {
1103          global $DB;
1104          $this->id = $id;
1105  
1106          $this->conditions = array('ti.tagid = t.id',
1107                                    "ti.itemtype = 'post'",
1108                                    "ti.component = 'core'",
1109                                    'ti.itemid = p.id',
1110                                    't.id = ?');
1111          $this->params = array($this->id);
1112      }
1113  }
1114  
1115  /**
1116   * This filter defines a specific blog entry id.
1117   */
1118  class blog_filter_entry extends blog_filter {
1119      public $conditions = array('p.id = ?');
1120      public $overrides  = array('site', 'course', 'module', 'group', 'user', 'tag');
1121  
1122      public function __construct($id) {
1123          $this->id = $id;
1124          $this->params[] = $this->id;
1125      }
1126  }
1127  
1128  /**
1129   * This filter restricts the results to a time interval in seconds up to time()
1130   */
1131  class blog_filter_since extends blog_filter {
1132      public function __construct($interval) {
1133          $this->conditions[] = 'p.lastmodified >= ? AND p.lastmodified <= ?';
1134          $this->params[] = time() - $interval;
1135          $this->params[] = time();
1136      }
1137  }
1138  
1139  /**
1140   * Filter used to perform full-text search on an entry's subject, summary and content
1141   */
1142  class blog_filter_search extends blog_filter {
1143  
1144      public function __construct($searchterm) {
1145          global $DB;
1146          $this->conditions = array("(".$DB->sql_like('p.summary', '?', false)." OR
1147                                      ".$DB->sql_like('p.content', '?', false)." OR
1148                                      ".$DB->sql_like('p.subject', '?', false).")");
1149          $this->params[] = "%$searchterm%";
1150          $this->params[] = "%$searchterm%";
1151          $this->params[] = "%$searchterm%";
1152      }
1153  }
1154  
1155  
1156  /**
1157   * Renderable class to represent an entry attachment
1158   */
1159  class blog_entry_attachment implements renderable {
1160  
1161      public $filename;
1162      public $url;
1163      public $file;
1164  
1165      /**
1166       * Gets the file data
1167       *
1168       * @param stored_file $file
1169       * @param int $entryid Attachment entry id
1170       */
1171      public function __construct($file, $entryid) {
1172  
1173          global $CFG;
1174  
1175          $this->file = $file;
1176          $this->filename = $file->get_filename();
1177          $this->url = file_encode_url($CFG->wwwroot . '/pluginfile.php',
1178                                       '/' . SYSCONTEXTID . '/blog/attachment/' . $entryid . '/' . $this->filename);
1179      }
1180  
1181  }