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