. /** * * @package moodlecore * @subpackage questionbank * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_question\bank; /** * Base class for representing a column in a {@link question_bank_view}. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class column_base { /** * @var question_bank_view */ protected $qbank; /** @var bool determine whether the column is td or th. */ protected $isheading = false; /** * Constructor. * @param $qbank the question_bank_view we are helping to render. */ public function __construct(view $qbank) { $this->qbank = $qbank; $this->init(); } /** * A chance for subclasses to initialise themselves, for example to load lang strings, * without having to override the constructor. */ protected function init() { } /** * Set the column as heading */ public function set_as_heading() { $this->isheading = true; } public function is_extra_row() { return false; } /** * Output the column header cell. */ public function display_header() { echo ''; $sortable = $this->is_sortable(); $name = get_class($this); $title = $this->get_title(); $tip = $this->get_title_tip(); if (is_array($sortable)) { if ($title) { echo '
' . $title . '
'; } $links = array(); foreach ($sortable as $subsort => $details) { $links[] = $this->make_sort_link($name . '-' . $subsort, $details['title'], isset($details['tip']) ? $details['tip'] : '', !empty($details['reverse'])); } echo '
' . implode(' / ', $links) . '
'; } else if ($sortable) { echo $this->make_sort_link($name, $title, $tip); } else { if ($tip) { echo ''; } echo $title; if ($tip) { echo ''; } } echo "\n"; } /** * Title for this column. Not used if is_sortable returns an array. * @param object $question the row from the $question table, augmented with extra information. * @param string $rowclasses CSS class names that should be applied to this row of output. */ protected abstract function get_title(); /** * @return string a fuller version of the name. Use this when get_title() returns * something very short, and you want a longer version as a tool tip. */ protected function get_title_tip() { return ''; } /** * Get a link that changes the sort order, and indicates the current sort state. * @param $name internal name used for this type of sorting. * @param $currentsort the current sort order -1, 0, 1 for descending, none, ascending. * @param $title the link text. * @param $defaultreverse whether the default sort order for this column is descending, rather than ascending. * @return string HTML fragment. */ protected function make_sort_link($sort, $title, $tip, $defaultreverse = false) { $currentsort = $this->qbank->get_primary_sort_order($sort); $newsortreverse = $defaultreverse; if ($currentsort) { $newsortreverse = $currentsort > 0; } if (!$tip) { $tip = $title; } if ($newsortreverse) { $tip = get_string('sortbyxreverse', '', $tip); } else { $tip = get_string('sortbyx', '', $tip); } $link = ''; $link .= $title; if ($currentsort) { $link .= $this->get_sort_icon($currentsort < 0); } $link .= ''; return $link; } /** * Get an icon representing the corrent sort state. * @param $reverse sort is descending, not ascending. * @return string HTML image tag. */ protected function get_sort_icon($reverse) { global $OUTPUT; if ($reverse) { return $OUTPUT->pix_icon('t/sort_desc', get_string('desc'), '', array('class' => 'iconsort')); } else { return $OUTPUT->pix_icon('t/sort_asc', get_string('asc'), '', array('class' => 'iconsort')); } } /** * Output this column. * @param object $question the row from the $question table, augmented with extra information. * @param string $rowclasses CSS class names that should be applied to this row of output. */ public function display($question, $rowclasses) { $this->display_start($question, $rowclasses); $this->display_content($question, $rowclasses); $this->display_end($question, $rowclasses); } /** * Output the opening column tag. If it is set as heading, it will use tag instead of * * @param stdClass $question * @param array $rowclasses */ protected function display_start($question, $rowclasses) { $tag = 'td'; $attr = array('class' => $this->get_classes()); if ($this->isheading) { $tag = 'th'; $attr['scope'] = 'row'; } echo \html_writer::start_tag($tag, $attr); } /** * @return string the CSS classes to apply to every cell in this column. */ protected function get_classes() { $classes = $this->get_extra_classes(); $classes[] = $this->get_name(); return implode(' ', $classes); } /** * @param object $question the row from the $question table, augmented with extra information. * @return string internal name for this column. Used as a CSS class name, * and to store information about the current sort. Must match PARAM_ALPHA. */ public abstract function get_name(); /** * @return array any extra class names you would like applied to every cell in this column. */ public function get_extra_classes() { return array(); } /** * Output the contents of this column. * @param object $question the row from the $question table, augmented with extra information. * @param string $rowclasses CSS class names that should be applied to this row of output. */ protected abstract function display_content($question, $rowclasses); /** * Output the closing column tag * * @param object $question * @param string $rowclasses */ protected function display_end($question, $rowclasses) { $tag = 'td'; if ($this->isheading) { $tag = 'th'; } echo \html_writer::end_tag($tag); } /** * Return an array 'table_alias' => 'JOIN clause' to bring in any data that * this column required. * * The return values for all the columns will be checked. It is OK if two * columns join in the same table with the same alias and identical JOIN clauses. * If to columns try to use the same alias with different joins, you get an error. * The only table included by default is the question table, which is aliased to 'q'. * * It is importnat that your join simply adds additional data (or NULLs) to the * existing rows of the query. It must not cause additional rows. * * @return array 'table_alias' => 'JOIN clause' */ public function get_extra_joins() { return array(); } /** * @return array fields required. use table alias 'q' for the question table, or one of the * ones from get_extra_joins. Every field requested must specify a table prefix. */ public function get_required_fields() { return array(); } /** * Can this column be sorted on? You can return either: * + false for no (the default), * + a field name, if sorting this column corresponds to sorting on that datbase field. * + an array of subnames to sort on as follows * return array( * 'firstname' => array('field' => 'uc.firstname', 'title' => get_string('firstname')), * 'lastname' => array('field' => 'uc.lastname', 'field' => get_string('lastname')), * ); * As well as field, and field, you can also add 'revers' => 1 if you want the default sort * order to be DESC. * @return mixed as above. */ public function is_sortable() { return false; } /** * Helper method for building sort clauses. * @param bool $reverse whether the normal direction should be reversed. * @param string $normaldir 'ASC' or 'DESC' * @return string 'ASC' or 'DESC' */ protected function sortorder($reverse) { if ($reverse) { return ' DESC'; } else { return ' ASC'; } } /** * @param $reverse Whether to sort in the reverse of the default sort order. * @param $subsort if is_sortable returns an array of subnames, then this will be * one of those. Otherwise will be empty. * @return string some SQL to go in the order by clause. */ public function sort_expression($reverse, $subsort) { $sortable = $this->is_sortable(); if (is_array($sortable)) { if (array_key_exists($subsort, $sortable)) { return $sortable[$subsort]['field'] . $this->sortorder($reverse, !empty($sortable[$subsort]['reverse'])); } else { throw new coding_exception('Unexpected $subsort type: ' . $subsort); } } else if ($sortable) { return $sortable . $this->sortorder($reverse); } else { throw new coding_exception('sort_expression called on a non-sortable column.'); } } }