Allow question types to be displayed in order that is better than random or alphabetical. Since we don't know all the qtypes there may be:
1. Store the order in the DB (config plugins).
2. Set up a good default order for the standard types. (Unknown types go at the end by default.)
3. Allow admins to edit the order on the qtype admin screen.
admin_externalpage_setup('manageqtypes');
-/// Get some data we will need.
+/// Get some data we will need - question counts and which types are needed.
$counts = $DB->get_records_sql("
SELECT qtype, COUNT(1) as numquestions, SUM(hidden) as numhidden
FROM {question} GROUP BY qtype", array());
}
}
- // Process actions =========================================================
+/// Work of the correct sort order.
+ $config = get_config('question');
+ $sortedqtypes = array();
+ foreach ($QTYPES as $qtypename => $qtype) {
+ $sortedqtypes[$qtypename] = $qtype->local_name();
+ }
+ $sortedqtypes = question_sort_qtype_array($sortedqtypes, $config);
+
+/// Process actions ============================================================
// Disable.
if (($disable = optional_param('disable', '', PARAM_SAFEDIR)) && confirm_sesskey()) {
redirect(admin_url('qtypes.php'));
}
+ // Move up in order.
+ if (($up = optional_param('up', '', PARAM_SAFEDIR)) && confirm_sesskey()) {
+ if (!isset($QTYPES[$up])) {
+ print_error('unknownquestiontype', 'question', admin_url('qtypes.php'), $up);
+ }
+
+ $neworder = question_reorder_qtypes($sortedqtypes, $up, -1);
+ question_save_qtype_order($neworder, $config);
+ redirect(admin_url('qtypes.php'));
+ }
+
+ // Move down in order.
+ if (($down = optional_param('down', '', PARAM_SAFEDIR)) && confirm_sesskey()) {
+ if (!isset($QTYPES[$down])) {
+ print_error('unknownquestiontype', 'question', admin_url('qtypes.php'), $down);
+ }
+
+ $neworder = question_reorder_qtypes($sortedqtypes, $down, +1);
+ question_save_qtype_order($neworder, $config);
+ redirect(admin_url('qtypes.php'));
+ }
+
// Delete.
if ($delete = optional_param('delete', '', PARAM_SAFEDIR) && confirm_sesskey()) {
// Check it is OK to delete this question type.
if (!unset_all_config_for_plugin('qtype_' . $delete)) {
notify(get_string('errordeletingconfig', 'admin', 'qtype_' . $delete));
}
+ unset_config($delete . '_disabled', 'question');
+ unset_config($delete . '_sortorder', 'question');
// Then the tables themselves
drop_plugin_tables($delete, $QTYPES[$delete]->plugin_dir() . '/db/install.xml', false);
/// Add a row for each question type.
$createabletypes = question_type_menu();
- foreach ($QTYPES as $qtypename => $qtype) {
+ foreach ($sortedqtypes as $qtypename => $localname) {
+ $qtype = $QTYPES[$qtypename];
$row = array();
// Question icon and name.
$fakequestion = new stdClass;
$fakequestion->qtype = $qtypename;
$icon = print_question_icon($fakequestion, true);
- $row[] = $icon . ' ' . $qtype->local_name();
+ $row[] = $icon . ' ' . $localname;
// Number of questions of this type.
if ($counts[$qtypename]->numquestions + $counts[$qtypename]->numhidden > 0) {
$rowclass = '';
if ($qtype->menu_name()) {
$createable = isset($createabletypes[$qtypename]);
- $row[] = enable_disable_button($qtypename, $createable);
+ $icons = enable_disable_button($qtypename, $createable);
if (!$createable) {
$rowclass = 'dimmed_text';
}
} else {
- $row[] = '';
+ $icons = '<img src="' . $CFG->pixpath . '/spacer.gif" alt="" class="spacer" />';
}
+ // Move icons.
+ $icons .= icon_html('up', $qtypename, 't/up.gif', get_string('up'), '');
+ $icons .= icon_html('down', $qtypename, 't/down.gif', get_string('down'), '');
+ $row[] = $icons;
+
// Delete link, if available.
if ($needed[$qtypename]) {
$row[] = '';
}
function enable_disable_button($qtypename, $createable) {
- global $CFG;
if ($createable) {
- $action = 'disable';
- $tip = get_string('disable');
- $alt = get_string('enabled', 'question');
- $icon = 'hide';
+ return icon_html('disable', $qtypename, 'i/hide.gif', get_string('enabled', 'question'), get_string('disable'));
} else {
- $action = 'enable';
- $tip = get_string('enable');
- $alt = get_string('disabled', 'question');
- $icon = 'show';
+ return icon_html('enable', $qtypename, 'i/show.gif', get_string('disabled', 'question'), get_string('enable'));
+ }
+}
+
+function icon_html($action, $qtypename, $icon, $alt, $tip) {
+ global $CFG;
+ if ($tip) {
+ $tip = 'title="' . $tip . '" ';
}
- $html = '<form action="' . admin_url('qtypes.php') . '" method="post"><div>';
+ $html = ' <form action="' . admin_url('qtypes.php') . '" method="post"><div>';
$html .= '<input type="hidden" name="sesskey" value="' . sesskey() . '" />';
$html .= '<input type="image" name="' . $action . '" value="' . $qtypename .
- '" src="' . $CFG->pixpath . '/i/' . $icon . '.gif" alt="' . $alt . '" title="' . $tip . '" />';
+ '" src="' . $CFG->pixpath . '/' . $icon . '" alt="' . $alt . '" ' . $tip . '/>';
$html .= '</div></form>';
return $html;
}
/// Main savepoint reached
upgrade_main_savepoint($result, 2009021801);
}
+
+ /// Add default sort order for question types.
+ if ($result && $oldversion < 2009030300) {
+ set_config('multichoice_sortorder', 1, 'question');
+ set_config('truefalse_sortorder', 2, 'question');
+ set_config('shortanswer_sortorder', 3, 'question');
+ set_config('numerical_sortorder', 4, 'question');
+ set_config('calculated_sortorder', 5, 'question');
+ set_config('essay_sortorder', 6, 'question');
+ set_config('match_sortorder', 7, 'question');
+ set_config('randomsamatch_sortorder', 8, 'question');
+ set_config('multianswer_sortorder', 9, 'question');
+ set_config('description_sortorder', 10, 'question');
+ set_config('random_sortorder', 11, 'question');
+ set_config('missingtype_sortorder', 12, 'question');
+
+ upgrade_main_savepoint($result, 2009030300);
+ }
+
return $result;
}
global $QTYPES;
static $menuoptions = null;
if (is_null($menuoptions)) {
- $disbled = get_config('question');
+ $config = get_config('question');
$menuoptions = array();
foreach ($QTYPES as $name => $qtype) {
+ // Get the name if this qtype is enabled.
$menuname = $qtype->menu_name();
- $configname = $name . '_disabled';
- if ($menuname && !isset($disbled->$configname)) {
+ $enabledvar = $name . '_disabled';
+ if ($menuname && !isset($config->$enabledvar)) {
$menuoptions[$name] = $menuname;
}
}
- asort($menuoptions, SORT_LOCALE_STRING);
+
+ $menuoptions = question_sort_qtype_array($menuoptions, $config);
}
return $menuoptions;
}
+/**
+ * Sort an array of question type names according to the question type sort order stored in
+ * config_plugins. Entries for which there is no xxx_sortorder defined will go
+ * at the end, sorted according to asort($inarray, SORT_LOCALE_STRING).
+ * @param $inarray an array $qtype => $QTYPES[$qtype]->local_name().
+ * @param $config get_config('question'), if you happen to have it around, to save one DB query.
+ * @return array the sorted version of $inarray.
+ */
+function question_sort_qtype_array($inarray, $config = null) {
+ if (is_null($config)) {
+ $config = get_config('question');
+ }
+
+ $sortorder = array();
+ foreach ($inarray as $name => $notused) {
+ $sortvar = $name . '_sortorder';
+ if (isset($config->$sortvar)) {
+ $sortorder[$config->$sortvar] = $name;
+ }
+ }
+
+ ksort($sortorder);
+ $outarray = array();
+ foreach ($sortorder as $name) {
+ $outarray[$name] = $inarray[$name];
+ unset($inarray[$name]);
+ }
+ asort($inarray, SORT_LOCALE_STRING);
+ return array_merge($outarray, $inarray);
+}
+
+/**
+ * Move one question type in a list of question types. If you try to move one element
+ * off of the end, nothing will change.
+ *
+ * @param array $sortedqtypes An array $qtype => anything.
+ * @param string $tomove one of the keys from $sortedqtypes
+ * @param integer $direction +1 or -1
+ * @return array an array $index => $qtype, with $index from 0 to n in order, and
+ * the $qtypes in the same order as $sortedqtypes, except that $tomove will
+ * have been moved one place.
+ */
+function question_reorder_qtypes($sortedqtypes, $tomove, $direction) {
+ $neworder = array_keys($sortedqtypes);
+ // Find the element to move.
+ $key = array_search($tomove, $neworder);
+ if ($key === false) {
+ return $neworder;
+ }
+ // Work out the other index.
+ $otherkey = $key + $direction;
+ if (!isset($neworder[$otherkey])) {
+ return $neworder;
+ }
+ // Do the swap.
+ $swap = $neworder[$otherkey];
+ $neworder[$otherkey] = $neworder[$key];
+ $neworder[$key] = $swap;
+ return $neworder;
+}
+
+/**
+ * Save a new question type order to the config_plugins table.
+ * @param $neworder An arra $index => $qtype. Indices should start at 0 and be in order.
+ * @param $config get_config('question'), if you happen to have it around, to save one DB query.
+ */
+function question_save_qtype_order($neworder, $config = null) {
+ global $DB;
+
+ if (is_null($config)) {
+ $config = get_config('question');
+ }
+
+ foreach ($neworder as $index => $qtype) {
+ $sortvar = $qtype . '_sortorder';
+ if (!isset($config->$sortvar) || $config->$sortvar != $index + 1) {
+ set_config($sortvar, $index + 1, 'question');
+ }
+ }
+}
+
/// OTHER CLASSES /////////////////////////////////////////////////////////
/**
require_once($CFG->libdir . '/questionlib.php');
-class questionlib_test extends FakeDBUnitTestCase {
-
-
- function setUp() {
+class questionlib_test extends UnitTestCase {
+ function test_question_sort_qtype_array() {
+ $config = new stdClass();
+ $config->multichoice_sortorder = '1';
+ $config->calculated_sortorder = '2';
+ $qtypes = array(
+ 'frog' => 'toad',
+ 'calculated' => 'newt',
+ 'multichoice' => 'eft',
+ );
+ $this->assertEqual(question_sort_qtype_array($qtypes), array(
+ 'multichoice' => 'eft',
+ 'calculated' => 'newt',
+ 'frog' => 'toad',
+ ));
}
- function tearDown() {
+ function test_question_reorder_qtypes() {
+ $this->assertEqual(question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 't1', +1),
+ array(0 => 't2', 1 => 't1', 2 => 't3'));
+ $this->assertEqual(question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 't1', -1),
+ array(0 => 't1', 1 => 't2', 2 => 't3'));
+ $this->assertEqual(question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 't2', -1),
+ array(0 => 't2', 1 => 't1', 2 => 't3'));
+ $this->assertEqual(question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 't3', +1),
+ array(0 => 't1', 1 => 't2', 2 => 't3'));
+ $this->assertEqual(question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 'missing', +1),
+ array(0 => 't1', 1 => 't2', 2 => 't3'));
}
function test_question_state_is_closed() {
$state->event = QUESTION_EVENTGRADE;
$this->assertTrue(question_state_is_graded($state));
-
}
}
#admin-report-questioninstances-index #settingsform p {
margin-bottom: 0;
}
+#admin-qtypes th {
+ white-space: normal;
+}
#admin-qtypes .cell.c3 {
font-size: 0.7em;
}
#admin-qtypes .cell.c0 {
text-align: left;
}
+#admin-qtypes #qtypes div,
+#admin-qtypes #qtypes form {
+ display: inline;
+}
+#admin-qtypes #qtypes img.spacer {
+ width: 16px;
+}
#admin-roles-allowassign .buttons,
#admin-roles-allowoverride .buttons,
#admin-roles-manage .buttons,
// This is compared against the values stored in the database to determine
// whether upgrades should be performed (see lib/db/*.php)
- $version = 2009021801; // YYYYMMDD = date of the last version bump
+ $version = 2009030300; // YYYYMMDD = date of the last version bump
// XX = daily increments
$release = '2.0 dev (Build: 20090303)'; // Human-friendly version name