See Release Notes
Long Term Support Release
Differences Between: [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 * Base class for representing a column. 19 * 20 * @package core_question 21 * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace core_question\local\bank; 26 27 /** 28 * Base class for representing a column. 29 * 30 * @copyright 2009 Tim Hunt 31 * @author 2021 Safat Shahin <safatshahin@catalyst-au.net> 32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 */ 34 abstract class column_base { 35 36 /** 37 * @var view $qbank the question bank view we are helping to render. 38 */ 39 protected $qbank; 40 41 /** @var bool determine whether the column is td or th. */ 42 protected $isheading = false; 43 44 /** 45 * Constructor. 46 * @param view $qbank the question bank view we are helping to render. 47 */ 48 public function __construct(view $qbank) { 49 $this->qbank = $qbank; 50 $this->init(); 51 } 52 53 /** 54 * A chance for subclasses to initialise themselves, for example to load lang strings, 55 * without having to override the constructor. 56 */ 57 protected function init(): void { 58 } 59 60 /** 61 * Set the column as heading 62 */ 63 public function set_as_heading(): void { 64 $this->isheading = true; 65 } 66 67 /** 68 * Check if the column is an extra row of not. 69 */ 70 public function is_extra_row(): bool { 71 return false; 72 } 73 74 /** 75 * Check if the row has an extra preference to view/hide. 76 */ 77 public function has_preference(): bool { 78 return false; 79 } 80 81 /** 82 * Get if the preference key of the row. 83 */ 84 public function get_preference_key(): string { 85 return ''; 86 } 87 88 /** 89 * Get if the preference of the row. 90 */ 91 public function get_preference(): bool { 92 return false; 93 } 94 95 /** 96 * Output the column header cell. 97 */ 98 public function display_header(): void { 99 global $PAGE; 100 $renderer = $PAGE->get_renderer('core_question', 'bank'); 101 102 $data = []; 103 $data['sortable'] = true; 104 $data['extraclasses'] = $this->get_classes(); 105 $sortable = $this->is_sortable(); 106 $name = get_class($this); 107 $title = $this->get_title(); 108 $tip = $this->get_title_tip(); 109 $links = []; 110 if (is_array($sortable)) { 111 if ($title) { 112 $data['title'] = $title; 113 } 114 foreach ($sortable as $subsort => $details) { 115 $links[] = $this->make_sort_link($name . '-' . $subsort, 116 $details['title'], isset($details['tip']) ? $details['tip'] : '', !empty($details['reverse'])); 117 } 118 $data['sortlinks'] = implode(' / ', $links); 119 } else if ($sortable) { 120 $data['sortlinks'] = $this->make_sort_link($name, $title, $tip); 121 } else { 122 $data['sortable'] = false; 123 $data['tiptitle'] = $title; 124 if ($tip) { 125 $data['sorttip'] = true; 126 $data['tip'] = $tip; 127 } 128 } 129 $help = $this->help_icon(); 130 if ($help) { 131 $data['help'] = $help->export_for_template($renderer); 132 } 133 134 echo $renderer->render_column_header($data); 135 } 136 137 /** 138 * Title for this column. Not used if is_sortable returns an array. 139 */ 140 abstract public function get_title(); 141 142 /** 143 * Use this when get_title() returns 144 * something very short, and you want a longer version as a tool tip. 145 * 146 * @return string a fuller version of the name. 147 */ 148 public function get_title_tip() { 149 return ''; 150 } 151 152 /** 153 * If you return a help icon here, it is shown in the column header after the title. 154 * 155 * @return \help_icon|null help icon to show, if required. 156 */ 157 public function help_icon(): ?\help_icon { 158 return null; 159 } 160 161 /** 162 * Get a link that changes the sort order, and indicates the current sort state. 163 * @param string $sort the column to sort on. 164 * @param string $title the link text. 165 * @param string $tip the link tool-tip text. If empty, defaults to title. 166 * @param bool $defaultreverse whether the default sort order for this column is descending, rather than ascending. 167 * @return string 168 */ 169 protected function make_sort_link($sort, $title, $tip, $defaultreverse = false): string { 170 global $PAGE; 171 $sortdata = []; 172 $currentsort = $this->qbank->get_primary_sort_order($sort); 173 $newsortreverse = $defaultreverse; 174 if ($currentsort) { 175 $newsortreverse = $currentsort > 0; 176 } 177 if (!$tip) { 178 $tip = $title; 179 } 180 if ($newsortreverse) { 181 $tip = get_string('sortbyxreverse', '', $tip); 182 } else { 183 $tip = get_string('sortbyx', '', $tip); 184 } 185 186 $link = $title; 187 if ($currentsort) { 188 $link .= $this->get_sort_icon($currentsort < 0); 189 } 190 191 $sortdata['sorturl'] = $this->qbank->new_sort_url($sort, $newsortreverse); 192 $sortdata['sortcontent'] = $link; 193 $sortdata['sorttip'] = $tip; 194 $renderer = $PAGE->get_renderer('core_question', 'bank'); 195 return $renderer->render_column_sort($sortdata); 196 197 } 198 199 /** 200 * Get an icon representing the corrent sort state. 201 * @param bool $reverse sort is descending, not ascending. 202 * @return string HTML image tag. 203 */ 204 protected function get_sort_icon($reverse): string { 205 global $OUTPUT; 206 if ($reverse) { 207 return $OUTPUT->pix_icon('t/sort_desc', get_string('desc'), '', ['class' => 'iconsort']); 208 } else { 209 return $OUTPUT->pix_icon('t/sort_asc', get_string('asc'), '', ['class' => 'iconsort']); 210 } 211 } 212 213 /** 214 * Output this column. 215 * @param object $question the row from the $question table, augmented with extra information. 216 * @param string $rowclasses CSS class names that should be applied to this row of output. 217 */ 218 public function display($question, $rowclasses): void { 219 $this->display_start($question, $rowclasses); 220 $this->display_content($question, $rowclasses); 221 $this->display_end($question, $rowclasses); 222 } 223 224 /** 225 * Output the opening column tag. If it is set as heading, it will use <th> tag instead of <td> 226 * 227 * @param \stdClass $question 228 * @param string $rowclasses 229 */ 230 protected function display_start($question, $rowclasses): void { 231 $tag = 'td'; 232 $attr = ['class' => $this->get_classes()]; 233 if ($this->isheading) { 234 $tag = 'th'; 235 $attr['scope'] = 'row'; 236 } 237 echo \html_writer::start_tag($tag, $attr); 238 } 239 240 /** 241 * The CSS classes to apply to every cell in this column. 242 * 243 * @return string 244 */ 245 protected function get_classes(): string { 246 $classes = $this->get_extra_classes(); 247 $classes[] = $this->get_name(); 248 return implode(' ', $classes); 249 } 250 251 /** 252 * Get the internal name for this column. Used as a CSS class name, 253 * and to store information about the current sort. Must match PARAM_ALPHA. 254 * 255 * @return string column name. 256 */ 257 abstract public function get_name(); 258 259 /** 260 * Get the name of this column. This must be unique. 261 * When using the inherited class to make many columns from one parent, 262 * ensure each instance returns a unique value. 263 * 264 * @return string The unique name; 265 */ 266 public function get_column_name() { 267 return (new \ReflectionClass($this))->getShortName(); 268 } 269 270 /** 271 * Any extra class names you would like applied to every cell in this column. 272 * 273 * @return array 274 */ 275 public function get_extra_classes(): array { 276 return []; 277 } 278 279 /** 280 * Output the contents of this column. 281 * @param object $question the row from the $question table, augmented with extra information. 282 * @param string $rowclasses CSS class names that should be applied to this row of output. 283 */ 284 abstract protected function display_content($question, $rowclasses); 285 286 /** 287 * Output the closing column tag 288 * 289 * @param object $question 290 * @param string $rowclasses 291 */ 292 protected function display_end($question, $rowclasses): void { 293 $tag = 'td'; 294 if ($this->isheading) { 295 $tag = 'th'; 296 } 297 echo \html_writer::end_tag($tag); 298 } 299 300 /** 301 * Return an array 'table_alias' => 'JOIN clause' to bring in any data that 302 * this column required. 303 * 304 * The return values for all the columns will be checked. It is OK if two 305 * columns join in the same table with the same alias and identical JOIN clauses. 306 * If to columns try to use the same alias with different joins, you get an error. 307 * The only table included by default is the question table, which is aliased to 'q'. 308 * 309 * It is importnat that your join simply adds additional data (or NULLs) to the 310 * existing rows of the query. It must not cause additional rows. 311 * 312 * @return array 'table_alias' => 'JOIN clause' 313 */ 314 public function get_extra_joins(): array { 315 return []; 316 } 317 318 /** 319 * Use table alias 'q' for the question table, or one of the 320 * ones from get_extra_joins. Every field requested must specify a table prefix. 321 * 322 * @return array fields required. 323 */ 324 public function get_required_fields(): array { 325 return []; 326 } 327 328 /** 329 * If this column requires any aggregated statistics, it should declare that here. 330 * 331 * This is those statistics can be efficiently loaded in bulk. 332 * 333 * The statistics are all loaded just before load_additional_data is called on each column. 334 * The values are then available from $this->qbank->get_aggregate_statistic(...); 335 * 336 * @return string[] the names of the required statistics fields. E.g. ['facility']. 337 */ 338 public function get_required_statistics_fields(): array { 339 return []; 340 } 341 342 /** 343 * If this column needs extra data (e.g. tags) then load that here. 344 * 345 * The extra data should be added to the question object in the array. 346 * Probably a good idea to check that another column has not already 347 * loaded the data you want. 348 * 349 * @param \stdClass[] $questions the questions that will be displayed, indexed by question id. 350 */ 351 public function load_additional_data(array $questions) { 352 } 353 354 /** 355 * Load the tags for each question. 356 * 357 * Helper that can be used from {@see load_additional_data()}; 358 * 359 * @param array $questions 360 */ 361 public function load_question_tags(array $questions): void { 362 $firstquestion = reset($questions); 363 if (isset($firstquestion->tags)) { 364 // Looks like tags are already loaded, so don't do it again. 365 return; 366 } 367 368 // Load the tags. 369 $tagdata = \core_tag_tag::get_items_tags('core_question', 'question', 370 array_keys($questions)); 371 372 // Add them to the question objects. 373 foreach ($tagdata as $questionid => $tags) { 374 $questions[$questionid]->tags = $tags; 375 } 376 } 377 378 /** 379 * Can this column be sorted on? You can return either: 380 * + false for no (the default), 381 * + a field name, if sorting this column corresponds to sorting on that datbase field. 382 * + an array of subnames to sort on as follows 383 * return [ 384 * 'firstname' => ['field' => 'uc.firstname', 'title' => get_string('firstname')], 385 * 'lastname' => ['field' => 'uc.lastname', 'title' => get_string('lastname')], 386 * ]; 387 * As well as field, and field, you can also add 'revers' => 1 if you want the default sort 388 * order to be DESC. 389 * @return mixed as above. 390 */ 391 public function is_sortable() { 392 return false; 393 } 394 395 /** 396 * Helper method for building sort clauses. 397 * @param bool $reverse whether the normal direction should be reversed. 398 * @return string 'ASC' or 'DESC' 399 */ 400 protected function sortorder($reverse): string { 401 if ($reverse) { 402 return ' DESC'; 403 } else { 404 return ' ASC'; 405 } 406 } 407 408 /** 409 * Sorts the expressions. 410 * 411 * @param bool $reverse Whether to sort in the reverse of the default sort order. 412 * @param string $subsort if is_sortable returns an array of subnames, then this will be 413 * one of those. Otherwise will be empty. 414 * @return string some SQL to go in the order by clause. 415 */ 416 public function sort_expression($reverse, $subsort): string { 417 $sortable = $this->is_sortable(); 418 if (is_array($sortable)) { 419 if (array_key_exists($subsort, $sortable)) { 420 return $sortable[$subsort]['field'] . $this->sortorder($reverse); 421 } else { 422 throw new \coding_exception('Unexpected $subsort type: ' . $subsort); 423 } 424 } else if ($sortable) { 425 return $sortable . $this->sortorder($reverse); 426 } else { 427 throw new \coding_exception('sort_expression called on a non-sortable column.'); 428 } 429 } 430 431 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body