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 in a {@link question_bank_view}. 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\bank; 26 defined('MOODLE_INTERNAL') || die(); 27 28 29 /** 30 * Base class for representing a column in a {@link question_bank_view}. 31 * 32 * @copyright 2009 Tim Hunt 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 abstract class column_base { 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() { 58 } 59 60 /** 61 * Set the column as heading 62 */ 63 public function set_as_heading() { 64 $this->isheading = true; 65 } 66 67 public function is_extra_row() { 68 return false; 69 } 70 71 /** 72 * Output the column header cell. 73 */ 74 public function display_header() { 75 echo '<th class="header ' . $this->get_classes() . '" scope="col">'; 76 $sortable = $this->is_sortable(); 77 $name = get_class($this); 78 $title = $this->get_title(); 79 $tip = $this->get_title_tip(); 80 if (is_array($sortable)) { 81 if ($title) { 82 echo '<div class="title">' . $title . '</div>'; 83 } 84 $links = array(); 85 foreach ($sortable as $subsort => $details) { 86 $links[] = $this->make_sort_link($name . '-' . $subsort, 87 $details['title'], isset($details['tip']) ? $details['tip'] : '', !empty($details['reverse'])); 88 } 89 echo '<div class="sorters">' . implode(' / ', $links) . '</div>'; 90 } else if ($sortable) { 91 echo $this->make_sort_link($name, $title, $tip); 92 } else { 93 if ($tip) { 94 echo '<span title="' . $tip . '">'; 95 } 96 echo $title; 97 if ($tip) { 98 echo '</span>'; 99 } 100 } 101 echo "</th>\n"; 102 } 103 104 /** 105 * Title for this column. Not used if is_sortable returns an array. 106 */ 107 protected abstract function get_title(); 108 109 /** 110 * @return string a fuller version of the name. Use this when get_title() returns 111 * something very short, and you want a longer version as a tool tip. 112 */ 113 protected function get_title_tip() { 114 return ''; 115 } 116 117 /** 118 * Get a link that changes the sort order, and indicates the current sort state. 119 * @param string $sort the column to sort on. 120 * @param string $title the link text. 121 * @param string $tip the link tool-tip text. If empty, defaults to title. 122 * @param bool $defaultreverse whether the default sort order for this column is descending, rather than ascending. 123 * @return string HTML fragment. 124 */ 125 protected function make_sort_link($sort, $title, $tip, $defaultreverse = false) { 126 $currentsort = $this->qbank->get_primary_sort_order($sort); 127 $newsortreverse = $defaultreverse; 128 if ($currentsort) { 129 $newsortreverse = $currentsort > 0; 130 } 131 if (!$tip) { 132 $tip = $title; 133 } 134 if ($newsortreverse) { 135 $tip = get_string('sortbyxreverse', '', $tip); 136 } else { 137 $tip = get_string('sortbyx', '', $tip); 138 } 139 $link = '<a href="' . $this->qbank->new_sort_url($sort, $newsortreverse) . '" title="' . $tip . '">'; 140 $link .= $title; 141 if ($currentsort) { 142 $link .= $this->get_sort_icon($currentsort < 0); 143 } 144 $link .= '</a>'; 145 return $link; 146 } 147 148 /** 149 * Get an icon representing the corrent sort state. 150 * @param bool $reverse sort is descending, not ascending. 151 * @return string HTML image tag. 152 */ 153 protected function get_sort_icon($reverse) { 154 global $OUTPUT; 155 if ($reverse) { 156 return $OUTPUT->pix_icon('t/sort_desc', get_string('desc'), '', array('class' => 'iconsort')); 157 } else { 158 return $OUTPUT->pix_icon('t/sort_asc', get_string('asc'), '', array('class' => 'iconsort')); 159 } 160 } 161 162 /** 163 * Output this column. 164 * @param object $question the row from the $question table, augmented with extra information. 165 * @param string $rowclasses CSS class names that should be applied to this row of output. 166 */ 167 public function display($question, $rowclasses) { 168 $this->display_start($question, $rowclasses); 169 $this->display_content($question, $rowclasses); 170 $this->display_end($question, $rowclasses); 171 } 172 173 /** 174 * Output the opening column tag. If it is set as heading, it will use <th> tag instead of <td> 175 * 176 * @param \stdClass $question 177 * @param string $rowclasses 178 */ 179 protected function display_start($question, $rowclasses) { 180 $tag = 'td'; 181 $attr = array('class' => $this->get_classes()); 182 if ($this->isheading) { 183 $tag = 'th'; 184 $attr['scope'] = 'row'; 185 } 186 echo \html_writer::start_tag($tag, $attr); 187 } 188 189 /** 190 * @return string the CSS classes to apply to every cell in this column. 191 */ 192 protected function get_classes() { 193 $classes = $this->get_extra_classes(); 194 $classes[] = $this->get_name(); 195 return implode(' ', $classes); 196 } 197 198 /** 199 * Get the internal name for this column. Used as a CSS class name, 200 * and to store information about the current sort. Must match PARAM_ALPHA. 201 * 202 * @return string column name. 203 */ 204 public abstract function get_name(); 205 206 /** 207 * @return array any extra class names you would like applied to every cell in this column. 208 */ 209 public function get_extra_classes() { 210 return array(); 211 } 212 213 /** 214 * Output the contents of 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 protected abstract function display_content($question, $rowclasses); 219 220 /** 221 * Output the closing column tag 222 * 223 * @param object $question 224 * @param string $rowclasses 225 */ 226 protected function display_end($question, $rowclasses) { 227 $tag = 'td'; 228 if ($this->isheading) { 229 $tag = 'th'; 230 } 231 echo \html_writer::end_tag($tag); 232 } 233 234 /** 235 * Return an array 'table_alias' => 'JOIN clause' to bring in any data that 236 * this column required. 237 * 238 * The return values for all the columns will be checked. It is OK if two 239 * columns join in the same table with the same alias and identical JOIN clauses. 240 * If to columns try to use the same alias with different joins, you get an error. 241 * The only table included by default is the question table, which is aliased to 'q'. 242 * 243 * It is importnat that your join simply adds additional data (or NULLs) to the 244 * existing rows of the query. It must not cause additional rows. 245 * 246 * @return array 'table_alias' => 'JOIN clause' 247 */ 248 public function get_extra_joins() { 249 return array(); 250 } 251 252 /** 253 * @return array fields required. use table alias 'q' for the question table, or one of the 254 * ones from get_extra_joins. Every field requested must specify a table prefix. 255 */ 256 public function get_required_fields() { 257 return array(); 258 } 259 260 /** 261 * If this column needs extra data (e.g. tags) then load that here. 262 * 263 * The extra data should be added to the question object in the array. 264 * Probably a good idea to check that another column has not already 265 * loaded the data you want. 266 * 267 * @param \stdClass[] $questions the questions that will be displayed. 268 */ 269 public function load_additional_data(array $questions) { 270 } 271 272 /** 273 * Load the tags for each question. 274 * 275 * Helper that can be used from {@link load_additional_data()}; 276 * 277 * @param array $questions 278 */ 279 public function load_question_tags(array $questions) { 280 $firstquestion = reset($questions); 281 if (isset($firstquestion->tags)) { 282 // Looks like tags are already loaded, so don't do it again. 283 return; 284 } 285 286 // Load the tags. 287 $tagdata = \core_tag_tag::get_items_tags('core_question', 'question', 288 array_keys($questions)); 289 290 // Add them to the question objects. 291 foreach ($tagdata as $questionid => $tags) { 292 $questions[$questionid]->tags = $tags; 293 } 294 } 295 296 /** 297 * Can this column be sorted on? You can return either: 298 * + false for no (the default), 299 * + a field name, if sorting this column corresponds to sorting on that datbase field. 300 * + an array of subnames to sort on as follows 301 * return array( 302 * 'firstname' => array('field' => 'uc.firstname', 'title' => get_string('firstname')), 303 * 'lastname' => array('field' => 'uc.lastname', 'title' => get_string('lastname')), 304 * ); 305 * As well as field, and field, you can also add 'revers' => 1 if you want the default sort 306 * order to be DESC. 307 * @return mixed as above. 308 */ 309 public function is_sortable() { 310 return false; 311 } 312 313 /** 314 * Helper method for building sort clauses. 315 * @param bool $reverse whether the normal direction should be reversed. 316 * @return string 'ASC' or 'DESC' 317 */ 318 protected function sortorder($reverse) { 319 if ($reverse) { 320 return ' DESC'; 321 } else { 322 return ' ASC'; 323 } 324 } 325 326 /** 327 * @param bool $reverse Whether to sort in the reverse of the default sort order. 328 * @param string $subsort if is_sortable returns an array of subnames, then this will be 329 * one of those. Otherwise will be empty. 330 * @return string some SQL to go in the order by clause. 331 */ 332 public function sort_expression($reverse, $subsort) { 333 $sortable = $this->is_sortable(); 334 if (is_array($sortable)) { 335 if (array_key_exists($subsort, $sortable)) { 336 return $sortable[$subsort]['field'] . $this->sortorder($reverse); 337 } else { 338 throw new \coding_exception('Unexpected $subsort type: ' . $subsort); 339 } 340 } else if ($sortable) { 341 return $sortable . $this->sortorder($reverse); 342 } else { 343 throw new \coding_exception('sort_expression called on a non-sortable column.'); 344 } 345 } 346 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body