Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [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   * This file contains components used by the restore UI
  19   *
  20   * @package   core_backup
  21   * @copyright 2010 Sam Hemelryk
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  /**
  26   * A base class that can be used to build a specific search upon
  27   *
  28   * @package   core_backup
  29   * @copyright 2010 Sam Hemelryk
  30   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31   */
  32  abstract class restore_search_base implements renderable {
  33  
  34      /**
  35       * The default values for this components params
  36       */
  37      const DEFAULT_SEARCH = '';
  38  
  39      /**
  40       * The param used to convey the current search string
  41       * @var string
  42       */
  43      static $VAR_SEARCH = 'search';
  44  
  45      /**
  46       * The current search string
  47       * @var string|null
  48       */
  49      private $search = null;
  50      /**
  51       * The URL for this page including required params to return to it
  52       * @var moodle_url
  53       */
  54      private $url = null;
  55      /**
  56       * The results of the search
  57       * @var array|null
  58       */
  59      private $results = null;
  60      /**
  61       * The total number of results available
  62       * @var int
  63       */
  64      private $totalcount = null;
  65      /**
  66       * Array of capabilities required for each item in the search
  67       * @var array
  68       */
  69      private $requiredcapabilities = array();
  70      /**
  71       * Max number of courses to return in a search.
  72       * @var int
  73       */
  74      private $maxresults = null;
  75      /**
  76       * Indicates if we have more than maxresults found.
  77       * @var boolean
  78       */
  79      private $hasmoreresults = false;
  80  
  81      /**
  82       * Constructor
  83       * @param array $config Config options
  84       */
  85      public function __construct(array $config = array()) {
  86  
  87          $this->search = optional_param($this->get_varsearch(), self::DEFAULT_SEARCH, PARAM_NOTAGS);
  88          $this->search = trim($this->search);
  89          $this->maxresults = get_config('backup', 'import_general_maxresults');
  90  
  91          foreach ($config as $name => $value) {
  92              $method = 'set_'.$name;
  93              if (method_exists($this, $method)) {
  94                  $this->$method($value);
  95              }
  96          }
  97      }
  98  
  99      /**
 100       * The URL for this search
 101       * @global moodle_page $PAGE
 102       * @return moodle_url The URL for this page
 103       */
 104      final public function get_url() {
 105          global $PAGE;
 106          $params = array(
 107              $this->get_varsearch()    => $this->get_search()
 108          );
 109          return ($this->url !== null) ? new moodle_url($this->url, $params) : new moodle_url($PAGE->url, $params);
 110      }
 111  
 112      /**
 113       * The current search string
 114       * @return string
 115       */
 116      final public function get_search() {
 117          return ($this->search !== null) ? $this->search : self::DEFAULT_SEARCH;
 118      }
 119  
 120      /**
 121       * The total number of results
 122       * @return int
 123       */
 124      final public function get_count() {
 125          if ($this->totalcount === null) {
 126              $this->search();
 127          }
 128          return $this->totalcount;
 129      }
 130  
 131      /**
 132       * Returns an array of results from the search
 133       * @return array
 134       */
 135      final public function get_results() {
 136          if ($this->results === null) {
 137              $this->search();
 138          }
 139          return $this->results;
 140      }
 141  
 142      /**
 143       * Sets the page URL
 144       * @param moodle_url $url
 145       */
 146      final public function set_url(moodle_url $url) {
 147          $this->url = $url;
 148      }
 149  
 150      /**
 151       * Invalidates the results collected so far
 152       */
 153      final public function invalidate_results() {
 154          $this->results = null;
 155          $this->totalcount = null;
 156      }
 157  
 158      /**
 159       * Adds a required capability which all results will be checked against
 160       * @param string $capability
 161       * @param int|null $user
 162       */
 163      final public function require_capability($capability, $user = null) {
 164          if (!is_int($user)) {
 165              $user = null;
 166          }
 167          $this->requiredcapabilities[] = array(
 168              'capability' => $capability,
 169              'user' => $user
 170          );
 171      }
 172  
 173      /**
 174       * Executes the search
 175       *
 176       * @global moodle_database $DB
 177       * @return int The number of results
 178       */
 179      final public function search() {
 180          global $DB;
 181          if (!is_null($this->results)) {
 182              return $this->results;
 183          }
 184  
 185          $this->results = array();
 186          $this->totalcount = 0;
 187          $contextlevel = $this->get_itemcontextlevel();
 188          list($sql, $params) = $this->get_searchsql();
 189          // Get total number, to avoid some incorrect iterations.
 190          $countsql = preg_replace('/ORDER BY.*/', '', $sql);
 191          $totalcourses = $DB->count_records_sql("SELECT COUNT(*) FROM ($countsql) sel", $params);
 192          if ($totalcourses > 0) {
 193              // User to be checked is always the same (usually null, get it from first element).
 194              $firstcap = reset($this->requiredcapabilities);
 195              $userid = isset($firstcap['user']) ? $firstcap['user'] : null;
 196              // Extract caps to check, this saves us a bunch of iterations.
 197              $requiredcaps = array();
 198              foreach ($this->requiredcapabilities as $cap) {
 199                  $requiredcaps[] = $cap['capability'];
 200              }
 201              // Iterate while we have records and haven't reached $this->maxresults.
 202              $resultset = $DB->get_recordset_sql($sql, $params);
 203              foreach ($resultset as $result) {
 204                  context_helper::preload_from_record($result);
 205                  $classname = context_helper::get_class_for_level($contextlevel);
 206                  $context = $classname::instance($result->id);
 207                  if (count($requiredcaps) > 0) {
 208                      if (!has_all_capabilities($requiredcaps, $context, $userid)) {
 209                          continue;
 210                      }
 211                  }
 212                  // Check if we are over the limit.
 213                  if ($this->totalcount + 1 > $this->maxresults) {
 214                      $this->hasmoreresults = true;
 215                      break;
 216                  }
 217                  // If not, then continue.
 218                  $this->totalcount++;
 219                  $this->results[$result->id] = $result;
 220              }
 221              $resultset->close();
 222          }
 223  
 224          return $this->totalcount;
 225      }
 226  
 227      /**
 228       * Returns true if there are more search results.
 229       * @return bool
 230       */
 231      final public function has_more_results() {
 232          if ($this->results === null) {
 233              $this->search();
 234          }
 235          return $this->hasmoreresults;
 236      }
 237  
 238      /**
 239       * Returns an array containing the SQL for the search and the params
 240       * @return array
 241       */
 242      abstract protected function get_searchsql();
 243  
 244      /**
 245       * Gets the context level associated with this components items
 246       * @return CONTEXT_*
 247       */
 248      abstract protected function get_itemcontextlevel();
 249  
 250      /**
 251       * Formats the results
 252       */
 253      abstract protected function format_results();
 254  
 255      /**
 256       * Gets the string used to transfer the search string for this compontents requests
 257       * @return string
 258       */
 259      abstract public function get_varsearch();
 260  }
 261  
 262  /**
 263   * A course search component
 264   *
 265   * @package   core_backup
 266   * @copyright 2010 Sam Hemelryk
 267   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 268   */
 269  class restore_course_search extends restore_search_base {
 270  
 271      /**
 272       * @var string
 273       */
 274      static $VAR_SEARCH = 'search';
 275  
 276      /**
 277       * The current course id.
 278       * @var int
 279       */
 280      protected $currentcourseid = null;
 281  
 282      /**
 283       * Determines if the current course is included in the results.
 284       * @var bool
 285       */
 286      protected $includecurrentcourse;
 287  
 288      /**
 289       * Constructor
 290       * @param array $config
 291       * @param int $currentcouseid The current course id so it can be ignored
 292       */
 293      public function __construct(array $config = array(), $currentcouseid = null) {
 294          parent::__construct($config);
 295          $this->setup_restrictions();
 296          $this->currentcourseid = $currentcouseid;
 297          $this->includecurrentcourse = false;
 298      }
 299  
 300      /**
 301       * Sets up any access restrictions for the courses to be displayed in the search.
 302       *
 303       * This will typically call $this->require_capability().
 304       */
 305      protected function setup_restrictions() {
 306          $this->require_capability('moodle/restore:restorecourse');
 307      }
 308  
 309      /**
 310       * Get the search SQL.
 311       * @global moodle_database $DB
 312       * @return array
 313       */
 314      protected function get_searchsql() {
 315          global $DB;
 316  
 317          $ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
 318          $ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
 319          $params = array(
 320              'contextlevel' => CONTEXT_COURSE,
 321              'fullnamesearch' => '%'.$this->get_search().'%',
 322              'shortnamesearch' => '%'.$this->get_search().'%'
 323          );
 324  
 325          $select     = " SELECT c.id, c.fullname, c.shortname, c.visible, c.sortorder ";
 326          $from       = " FROM {course} c ";
 327          $where      = " WHERE (".$DB->sql_like('c.fullname', ':fullnamesearch', false)." OR ".
 328              $DB->sql_like('c.shortname', ':shortnamesearch', false).")";
 329          $orderby    = " ORDER BY c.sortorder";
 330  
 331          if ($this->currentcourseid !== null && !$this->includecurrentcourse) {
 332              $where .= " AND c.id <> :currentcourseid";
 333              $params['currentcourseid'] = $this->currentcourseid;
 334          }
 335  
 336          return array($select.$ctxselect.$from.$ctxjoin.$where.$orderby, $params);
 337      }
 338  
 339      /**
 340       * Gets the context level for the search result items.
 341       * @return CONTEXT_|int
 342       */
 343      protected function get_itemcontextlevel() {
 344          return CONTEXT_COURSE;
 345      }
 346  
 347      /**
 348       * Formats results.
 349       */
 350      protected function format_results() {}
 351  
 352      /**
 353       * Returns the name the search variable should use
 354       * @return string
 355       */
 356      public function get_varsearch() {
 357          return self::$VAR_SEARCH;
 358      }
 359  
 360      /**
 361       * Returns true if the current course should be included in the results.
 362       */
 363      public function set_include_currentcourse() {
 364          $this->includecurrentcourse = true;
 365      }
 366  }
 367  
 368  /**
 369   * A category search component
 370   *
 371   * @package   core_backup
 372   * @copyright 2010 Sam Hemelryk
 373   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 374   */
 375  class restore_category_search extends restore_search_base  {
 376  
 377      /**
 378       * The search variable to use.
 379       * @var string
 380       */
 381      static $VAR_SEARCH = 'catsearch';
 382  
 383      /**
 384       * Constructor
 385       * @param array $config
 386       */
 387      public function __construct(array $config = array()) {
 388          parent::__construct($config);
 389          $this->require_capability('moodle/course:create');
 390      }
 391      /**
 392       * Returns the search SQL.
 393       * @global moodle_database $DB
 394       * @return array
 395       */
 396      protected function get_searchsql() {
 397          global $DB;
 398  
 399          $ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
 400          $ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
 401          $params = array(
 402              'contextlevel' => CONTEXT_COURSECAT,
 403              'namesearch' => '%'.$this->get_search().'%',
 404          );
 405  
 406          $select     = " SELECT c.id, c.name, c.visible, c.sortorder, c.description, c.descriptionformat ";
 407          $from       = " FROM {course_categories} c ";
 408          $where      = " WHERE ".$DB->sql_like('c.name', ':namesearch', false);
 409          $orderby    = " ORDER BY c.sortorder";
 410  
 411          return array($select.$ctxselect.$from.$ctxjoin.$where.$orderby, $params);
 412      }
 413  
 414      /**
 415       * Returns the context level of the search results.
 416       * @return CONTEXT_COURSECAT
 417       */
 418      protected function get_itemcontextlevel() {
 419          return CONTEXT_COURSECAT;
 420      }
 421  
 422      /**
 423       * Formats the results.
 424       */
 425      protected function format_results() {}
 426  
 427      /**
 428       * Returns the name to use for the search variable.
 429       * @return string
 430       */
 431      public function get_varsearch() {
 432          return self::$VAR_SEARCH;
 433      }
 434  }