MDL-62814 Question: Improve column base display_header
[moodle.git] / question / classes / bank / column_base.php
CommitLineData
17f229fa
RM
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 *
19 * @package moodlecore
20 * @subpackage questionbank
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
26namespace core_question\bank;
27
28/**
29 * Base class for representing a column in a {@link question_bank_view}.
30 *
31 * @copyright 2009 Tim Hunt
32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33 */
34
35abstract class column_base {
36 /**
37 * @var question_bank_view
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 $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,
9d7ccae2 87 $details['title'], isset($details['tip']) ? $details['tip'] : '', !empty($details['reverse']));
17f229fa
RM
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 * @param object $question the row from the $question table, augmented with extra information.
107 * @param string $rowclasses CSS class names that should be applied to this row of output.
108 */
109 protected abstract function get_title();
110
111 /**
112 * @return string a fuller version of the name. Use this when get_title() returns
113 * something very short, and you want a longer version as a tool tip.
114 */
115 protected function get_title_tip() {
116 return '';
117 }
118
119 /**
120 * Get a link that changes the sort order, and indicates the current sort state.
121 * @param $name internal name used for this type of sorting.
122 * @param $currentsort the current sort order -1, 0, 1 for descending, none, ascending.
123 * @param $title the link text.
124 * @param $defaultreverse whether the default sort order for this column is descending, rather than ascending.
125 * @return string HTML fragment.
126 */
127 protected function make_sort_link($sort, $title, $tip, $defaultreverse = false) {
128 $currentsort = $this->qbank->get_primary_sort_order($sort);
129 $newsortreverse = $defaultreverse;
130 if ($currentsort) {
131 $newsortreverse = $currentsort > 0;
132 }
133 if (!$tip) {
134 $tip = $title;
135 }
136 if ($newsortreverse) {
137 $tip = get_string('sortbyxreverse', '', $tip);
138 } else {
139 $tip = get_string('sortbyx', '', $tip);
140 }
141 $link = '<a href="' . $this->qbank->new_sort_url($sort, $newsortreverse) . '" title="' . $tip . '">';
142 $link .= $title;
143 if ($currentsort) {
144 $link .= $this->get_sort_icon($currentsort < 0);
145 }
146 $link .= '</a>';
147 return $link;
148 }
149
150 /**
151 * Get an icon representing the corrent sort state.
152 * @param $reverse sort is descending, not ascending.
153 * @return string HTML image tag.
154 */
155 protected function get_sort_icon($reverse) {
156 global $OUTPUT;
157 if ($reverse) {
158 return $OUTPUT->pix_icon('t/sort_desc', get_string('desc'), '', array('class' => 'iconsort'));
159 } else {
160 return $OUTPUT->pix_icon('t/sort_asc', get_string('asc'), '', array('class' => 'iconsort'));
161 }
162 }
163
164 /**
165 * Output this column.
166 * @param object $question the row from the $question table, augmented with extra information.
167 * @param string $rowclasses CSS class names that should be applied to this row of output.
168 */
169 public function display($question, $rowclasses) {
170 $this->display_start($question, $rowclasses);
171 $this->display_content($question, $rowclasses);
172 $this->display_end($question, $rowclasses);
173 }
174
175 /**
176 * Output the opening column tag. If it is set as heading, it will use <th> tag instead of <td>
177 *
178 * @param stdClass $question
179 * @param array $rowclasses
180 */
181 protected function display_start($question, $rowclasses) {
182 $tag = 'td';
183 $attr = array('class' => $this->get_classes());
184 if ($this->isheading) {
185 $tag = 'th';
186 $attr['scope'] = 'row';
187 }
188 echo \html_writer::start_tag($tag, $attr);
189 }
190
191 /**
192 * @return string the CSS classes to apply to every cell in this column.
193 */
194 protected function get_classes() {
195 $classes = $this->get_extra_classes();
196 $classes[] = $this->get_name();
197 return implode(' ', $classes);
198 }
199
200 /**
201 * @param object $question the row from the $question table, augmented with extra information.
202 * @return string internal name for this column. Used as a CSS class name,
203 * and to store information about the current sort. Must match PARAM_ALPHA.
204 */
205 public abstract function get_name();
206
207 /**
208 * @return array any extra class names you would like applied to every cell in this column.
209 */
210 public function get_extra_classes() {
211 return array();
212 }
213
214 /**
215 * Output the contents of this column.
216 * @param object $question the row from the $question table, augmented with extra information.
217 * @param string $rowclasses CSS class names that should be applied to this row of output.
218 */
219 protected abstract function display_content($question, $rowclasses);
220
221 /**
222 * Output the closing column tag
223 *
224 * @param object $question
225 * @param string $rowclasses
226 */
227 protected function display_end($question, $rowclasses) {
228 $tag = 'td';
229 if ($this->isheading) {
230 $tag = 'th';
231 }
232 echo \html_writer::end_tag($tag);
233 }
234
235 /**
236 * Return an array 'table_alias' => 'JOIN clause' to bring in any data that
237 * this column required.
238 *
239 * The return values for all the columns will be checked. It is OK if two
240 * columns join in the same table with the same alias and identical JOIN clauses.
241 * If to columns try to use the same alias with different joins, you get an error.
242 * The only table included by default is the question table, which is aliased to 'q'.
243 *
244 * It is importnat that your join simply adds additional data (or NULLs) to the
245 * existing rows of the query. It must not cause additional rows.
246 *
247 * @return array 'table_alias' => 'JOIN clause'
248 */
249 public function get_extra_joins() {
250 return array();
251 }
252
253 /**
254 * @return array fields required. use table alias 'q' for the question table, or one of the
255 * ones from get_extra_joins. Every field requested must specify a table prefix.
256 */
257 public function get_required_fields() {
258 return array();
259 }
260
261 /**
262 * Can this column be sorted on? You can return either:
263 * + false for no (the default),
264 * + a field name, if sorting this column corresponds to sorting on that datbase field.
265 * + an array of subnames to sort on as follows
266 * return array(
267 * 'firstname' => array('field' => 'uc.firstname', 'title' => get_string('firstname')),
268 * 'lastname' => array('field' => 'uc.lastname', 'field' => get_string('lastname')),
269 * );
270 * As well as field, and field, you can also add 'revers' => 1 if you want the default sort
271 * order to be DESC.
272 * @return mixed as above.
273 */
274 public function is_sortable() {
275 return false;
276 }
277
278 /**
279 * Helper method for building sort clauses.
280 * @param bool $reverse whether the normal direction should be reversed.
281 * @param string $normaldir 'ASC' or 'DESC'
282 * @return string 'ASC' or 'DESC'
283 */
284 protected function sortorder($reverse) {
285 if ($reverse) {
286 return ' DESC';
287 } else {
288 return ' ASC';
289 }
290 }
291
292 /**
293 * @param $reverse Whether to sort in the reverse of the default sort order.
294 * @param $subsort if is_sortable returns an array of subnames, then this will be
295 * one of those. Otherwise will be empty.
296 * @return string some SQL to go in the order by clause.
297 */
298 public function sort_expression($reverse, $subsort) {
299 $sortable = $this->is_sortable();
300 if (is_array($sortable)) {
301 if (array_key_exists($subsort, $sortable)) {
302 return $sortable[$subsort]['field'] . $this->sortorder($reverse, !empty($sortable[$subsort]['reverse']));
303 } else {
304 throw new coding_exception('Unexpected $subsort type: ' . $subsort);
305 }
306 } else if ($sortable) {
307 return $sortable . $this->sortorder($reverse);
308 } else {
309 throw new coding_exception('sort_expression called on a non-sortable column.');
310 }
311 }
312}