diff --git a/lang/en_utf8/moodle.php b/lang/en_utf8/moodle.php
index bd2928e2383..bdfdafec172 100644
--- a/lang/en_utf8/moodle.php
+++ b/lang/en_utf8/moodle.php
@@ -1450,6 +1450,8 @@ $string['socialheadline'] = 'Social forum - latest topics';
$string['someallowguest'] = 'Some courses may allow guest access';
$string['someerrorswerefound'] = 'Some information was missing or incorrect. Look below for details.';
$string['sortby'] = 'Sort by';
+$string['sortbyx'] = 'Sort by $a ascending';
+$string['sortbyxreverse'] = 'Sort by $a descending';
$string['sourcerole'] = 'Source role';
$string['specifyname'] = 'You must specify a name.';
$string['standard'] = 'Standard';
diff --git a/lang/en_utf8/question.php b/lang/en_utf8/question.php
index fb5c859d19a..1020a2c6036 100644
--- a/lang/en_utf8/question.php
+++ b/lang/en_utf8/question.php
@@ -63,6 +63,7 @@ $string['cwrqpfsnoprob'] = 'No question categories in your site are affected by
$string['copy']= 'Copy from $a and change links.';
$string['contexterror'] = 'You shouldn\'t have got here if you\'re not moving a category to another context.';
$string['created'] = 'Created';
+$string['createdby'] = 'Created by';
$string['createdmodifiedheader'] = 'Created / Last Saved';
$string['defaultfor'] = 'Default for $a';
$string['defaultinfofor'] = 'The default category for questions shared in context \'$a\'.';
@@ -108,6 +109,7 @@ $string['invalidconfirm'] = 'Confirmation string was incorrect';
$string['invalidcategoryidforparent'] = 'Invalid category id for parent!';
$string['invalidcategoryidtomove'] = 'Invalid category id to move!';
$string['invalidwizardpage'] = 'Incorrect or no wizard page specified!';
+$string['lastmodifiedby'] = 'Last modified by';
$string['linkedfiledoesntexist'] = 'Linked file $a doesn\'t exist';
$string['makechildof'] = "Make Child of '\$a'";
$string['maketoplevelitem'] = 'Move to top level';
@@ -144,6 +146,7 @@ $string['permissionmove'] = 'Move this question';
$string['permissionsaveasnew'] = 'Save this as a new question';
$string['permissionto'] = 'You have permission to :';
$string['published'] = 'shared';
+$string['qtypeveryshort'] = 'T';
$string['questionaffected'] = 'qurl\">Question \"$a->name\" ($a->qtype) is in this question category but is also being used in qurl\">quiz \"$a->quizname\" in another course \"$a->coursename\".';
$string['questionbank'] = 'Question bank';
$string['questioncategory'] = 'Question category';
diff --git a/mod/quiz/edit.php b/mod/quiz/edit.php
index 5421b827c21..18920133e9d 100644
--- a/mod/quiz/edit.php
+++ b/mod/quiz/edit.php
@@ -154,7 +154,7 @@ if ($quiz_qbanktool > -1) {
} else {
$quiz_qbanktool = get_user_preferences("quiz_qbanktool_open",0);
}
-$questionbank = new quiz_question_bank_view();
+$questionbank = new quiz_question_bank_view($contexts, $thispageurl, $cm);
//will be set further down in the code
$quiz_has_attempts=false;
@@ -631,7 +631,7 @@ echo '';
echo '
';
echo '
';
echo '
';
-$questionbank->display('editq', $contexts, $thispageurl, $cm,
+$questionbank->display('editq',
$pagevars['qpage'],
$pagevars['qperpage'], $pagevars['qsortorder'],
$pagevars['qsortorderdecoded'],
diff --git a/mod/quiz/editlib.php b/mod/quiz/editlib.php
index c708dbd33c0..3e5c93158a7 100644
--- a/mod/quiz/editlib.php
+++ b/mod/quiz/editlib.php
@@ -1014,8 +1014,8 @@ function quiz_question_tostring(&$question,$showicon=false,$showquestiontext=tru
class quiz_question_bank_view extends question_bank_view {
protected $quizhasattempts = false;
- public function __construct() {
-
+ public function __construct($contexts, $pageurl, $cm = null) {
+ parent::__construct($contexts, $pageurl, $cm);
}
/**
@@ -1040,42 +1040,11 @@ class quiz_question_bank_view extends question_bank_view {
* category Chooses the category
* displayoptions Sets display options
*/
- function display($tabname, $contexts, $pageurl, $cm, $page, $perpage, $sortorder,
+ public function display($tabname, $page, $perpage, $sortorder,
$sortorderdecoded, $cat, $recurse, $showhidden, $showquestiontext){
global $COURSE, $DB;
- if (optional_param('deleteselected', false, PARAM_BOOL)){ // teacher still has to confirm
- // make a list of all the questions that are selected
- $rawquestions = $_REQUEST; // This code is called by both POST forms and GET links, so cannot use data_submitted.
- $questionlist = ''; // comma separated list of ids of questions to be deleted
- $questionnames = ''; // string with names of questions separated by with
- // an asterix in front of those that are in use
- $inuse = false; // set to true if at least one of the questions is in use
- foreach ($rawquestions as $key => $value) { // Parse input for question ids
- if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
- $key = $matches[1];
- $questionlist .= $key.',';
- question_require_capability_on($key, 'edit');
- if ($DB->record_exists('quiz_question_instances', array('question' => $key))) {
- $questionnames .= '* ';
- $inuse = true;
- }
- $questionnames .= $DB->get_field('question', 'name', array('id' => $key)) . ' ';
- }
- }
- if (!$questionlist) { // no questions were selected
- redirect($pageurl->out());
- }
- $questionlist = rtrim($questionlist, ',');
-
- // Add an explanation about questions in use
- if ($inuse) {
- $questionnames .= ' '.get_string('questionsinuse', 'quiz');
- }
- notice_yesno(get_string("deletequestionscheck", "quiz", $questionnames),
- $pageurl->out_action(array('deleteselected'=>$questionlist, 'confirm'=>md5($questionlist))),
- $pageurl->out_action());
-
+ if ($this->process_actions_needing_ui()) {
return;
}
@@ -1112,17 +1081,17 @@ class quiz_question_bank_view extends question_bank_view {
print_box_start('generalbox questionbank');
- $this->display_category_form($contexts->having_one_edit_tab_cap($tabname),
- $pageurl, $cat, $recurse, $showhidden, $showquestiontext);
+ $this->display_category_form($this->contexts->having_one_edit_tab_cap($tabname),
+ $this->baseurl, $cat, $recurse, $showhidden, $showquestiontext);
// continues with list of questions
- $this->display_question_list($contexts->having_one_edit_tab_cap($tabname), $pageurl, $cat, isset($cm) ? $cm : null,
+ $this->display_question_list($this->contexts->having_one_edit_tab_cap($tabname), $this->baseurl, $cat, $this->cm,
$recurse, $page, $perpage, $showhidden, $sortorder, $sortorderdecoded, $showquestiontext,
- $contexts->having_cap('moodle/question:add'));
+ $this->contexts->having_cap('moodle/question:add'));
echo '\n";
}
-}
-/**
- * Add an arbitrary element to array at a specified index, pushing the rest
- * back.
- *
- * @param array $array The array to operate on
- * @param mixed $value The element to add
- * @param integer $at The position at which to add the element
- * @return array
- */
-function array_add_at($array,$value,$at){
- $beginpart=array_slice($array, 0,$at);
- $endpart=array_slice($array, $at, (count($array)-$at) );
- $beginpart[]=$value;
- $result=array_merge($beginpart,$endpart);
- return $result;
+ protected function display_question_sort_options($pageurl, $sortorder){
+ //sort options
+ $html = "
";
+ // POST method should only be used for parameters that change data
+ // or if POST method has to be used, the user must be redirected immediately to
+ // non-POSTed page to not break the back button
+ $html .= '\n";
+ $html .= "
\n";
+ echo $html;
+ }
+
+ public function process_actions() {
+ global $CFG, $COURSE, $DB;
+ /// Now, check for commands on this page and modify variables as necessary
+ if (optional_param('move', false, PARAM_BOOL) and confirm_sesskey()) { /// Move selected questions to new category
+ $category = required_param('category', PARAM_SEQUENCE);
+ list($tocategoryid, $contextid) = explode(',', $category);
+ if (! $tocategory = $DB->get_record('question_categories', array('id' => $tocategoryid, 'contextid' => $contextid))) {
+ print_error('cannotfindcate', 'question');
+ }
+ $tocontext = get_context_instance_by_id($contextid);
+ require_capability('moodle/question:add', $tocontext);
+ $rawdata = (array) data_submitted();
+ $questionids = array();
+ foreach ($rawdata as $key => $value) { // Parse input for question ids
+ if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
+ $key = $matches[1];
+ $questionids[] = $key;
+ }
+ }
+ if ($questionids){
+ list($usql, $params) = $DB->get_in_or_equal($questionids);
+ $sql = "SELECT q.*, c.contextid FROM {question} q, {question_categories} c WHERE q.id $usql AND c.id = q.category";
+ if (!$questions = $DB->get_records_sql($sql, $params)){
+ print_error('questiondoesnotexist', 'question', $pageurl->out());
+ }
+ $checkforfiles = false;
+ foreach ($questions as $question){
+ //check capabilities
+ question_require_capability_on($question, 'move');
+ $fromcontext = get_context_instance_by_id($question->contextid);
+ if (get_filesdir_from_context($fromcontext) != get_filesdir_from_context($tocontext)){
+ $checkforfiles = true;
+ }
+ }
+ $returnurl = $pageurl->out(false, array('category'=>"$tocategoryid,$contextid"));
+ if (!$checkforfiles){
+ if (!question_move_questions_to_category(implode(',', $questionids), $tocategory->id)) {
+ print_error('errormovingquestions', 'question', $returnurl, $questionids);
+ }
+ redirect($returnurl);
+ } else {
+ $movecontexturl = new moodle_url($CFG->wwwroot.'/question/contextmoveq.php',
+ array('returnurl' => $returnurl,
+ 'ids'=>$questionidlist,
+ 'tocatid'=> $tocategoryid));
+ if ($cm){
+ $movecontexturl->param('cmid', $cm->id);
+ } else {
+ $movecontexturl->param('courseid', $COURSE->id);
+ }
+ redirect($movecontexturl->out());
+ }
+ }
+ }
+
+ if (optional_param('deleteselected', false, PARAM_BOOL)) { // delete selected questions from the category
+ if (($confirm = optional_param('confirm', '', PARAM_ALPHANUM)) and confirm_sesskey()) { // teacher has already confirmed the action
+ $deleteselected = required_param('deleteselected');
+ if ($confirm == md5($deleteselected)) {
+ if ($questionlist = explode(',', $deleteselected)) {
+ // for each question either hide it if it is in use or delete it
+ foreach ($questionlist as $questionid) {
+ question_require_capability_on($questionid, 'edit');
+ if ($DB->record_exists('quiz_question_instances', array('question' => $questionid))) {
+ if (!$DB->set_field('question', 'hidden', 1, array('id' => $questionid))) {
+ question_require_capability_on($questionid, 'edit');
+ print_error('cannothidequestion', 'question');
+ }
+ } else {
+ delete_question($questionid);
+ }
+ }
+ }
+ redirect($pageurl->out());
+ } else {
+ print_error('invalidconfirm', 'question');
+ }
+ }
+ }
+
+ // Unhide a question
+ if(($unhide = optional_param('unhide', '', PARAM_INT)) and confirm_sesskey()) {
+ question_require_capability_on($unhide, 'edit');
+ if(!$DB->set_field('question', 'hidden', 0, array('id', $unhide))) {
+ print_error('cannotunhidequestion', 'question');
+ }
+ redirect($pageurl->out());
+ }
+ }
+
+ public function process_actions_needing_ui() {
+ if (optional_param('deleteselected', false, PARAM_BOOL)) {
+ // make a list of all the questions that are selected
+ $rawquestions = $_REQUEST; // This code is called by both POST forms and GET links, so cannot use data_submitted.
+ $questionlist = ''; // comma separated list of ids of questions to be deleted
+ $questionnames = ''; // string with names of questions separated by with
+ // an asterix in front of those that are in use
+ $inuse = false; // set to true if at least one of the questions is in use
+ foreach ($rawquestions as $key => $value) { // Parse input for question ids
+ if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
+ $key = $matches[1];
+ $questionlist .= $key.',';
+ question_require_capability_on($key, 'edit');
+ if ($DB->record_exists('quiz_question_instances', array('question' => $key))) {
+ $questionnames .= '* ';
+ $inuse = true;
+ }
+ $questionnames .= $DB->get_field('question', 'name', array('id' => $key)) . ' ';
+ }
+ }
+ if (!$questionlist) { // no questions were selected
+ redirect($this->baseurl->out());
+ }
+ $questionlist = rtrim($questionlist, ',');
+
+ // Add an explanation about questions in use
+ if ($inuse) {
+ $questionnames .= ' '.get_string('questionsinuse', 'quiz');
+ }
+ notice_yesno(get_string("deletequestionscheck", "quiz", $questionnames),
+ $pageurl->out_action(array('deleteselected'=>$questionlist, 'confirm'=>md5($questionlist))),
+ $pageurl->out_action());
+
+ return true;
+ }
+ }
}
/**
diff --git a/question/edit.php b/question/edit.php
index 53804e0916e..de3bf6e9f52 100644
--- a/question/edit.php
+++ b/question/edit.php
@@ -34,8 +34,8 @@
require_once("editlib.php");
list($thispageurl, $contexts, $cmid, $cm, $module, $pagevars) = question_edit_setup('questions');
- $questionbank = new question_bank_view();
- $questionbank->process_actions($thispageurl, $cm);
+ $questionbank = new question_bank_view($contexts, $thispageurl, $cm);
+ $questionbank->process_actions();
// TODO log this page view.
@@ -72,7 +72,7 @@
echo '
';
echo '
';
- $questionbank->display('questions', $contexts, $thispageurl, $cm, $pagevars['qpage'],
+ $questionbank->display('questions', $pagevars['qpage'],
$pagevars['qperpage'], $pagevars['qsortorder'], $pagevars['qsortorderdecoded'],
$pagevars['cat'], $pagevars['recurse'], $pagevars['showhidden'], $pagevars['showquestiontext']);
diff --git a/question/editlib.php b/question/editlib.php
index 7c02d41217d..dace5920361 100644
--- a/question/editlib.php
+++ b/question/editlib.php
@@ -128,13 +128,97 @@ function question_can_delete_cat($todelete) {
}
abstract class question_bank_column_base {
+ protected $qbank;
+
+ /**
+ * Constructor.
+ * @param $qbank the question_bank_view we are helping to render.
+ */
+ public function __construct(question_bank_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() {
+ }
/**
* Output the column header cell.
* @param integer $currentsort 0 for none. 1 for normal sort, -1 for reverse sort.
*/
- public function display_header($currentsort) {
- // TODO.
+ public function display_header() {
+ 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 function get_title() {
+ 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($name, $currentsort, $title, $defaultreverse = false) {
+ $newsortreverse = $defaultreverse;
+ if ($currentsort) {
+ $newsortreverse = $currentsort > 0;
+ }
+ if ($newsortreverse) {
+ $tip = get_string('sortbyxreverse', '', $title);
+ } else {
+ $tip = get_string('sortbyx', '', $title);
+ }
+ echo '';
+ echo $title;
+ if ($currentsort) {
+ echo $this->get_sort_icon($currentsort < 0);
+ }
+ echo '';
+ }
+
+ /**
+ * 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 $CFG;
+ if ($reverse) {
+ return ' ';
+ } else {
+ return ' ';
+ }
}
/**
@@ -149,16 +233,14 @@ abstract class question_bank_column_base {
}
protected function display_start($question, $rowclasses) {
- echo '
';
+ echo '
';
}
/**
* @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.
- * @return string CSS class names that should be applied to this column. Should normally be
- * a simple work relating the the column type like 'questionname'.
+ * @return string internal name for this column. Used as a CSS class name, and to store information about the current sort.
*/
- abstract protected function get_css_classes($question, $rowclasses);
+ abstract protected function get_name();
/**
* Output the contents of this column.
@@ -168,7 +250,7 @@ abstract class question_bank_column_base {
abstract protected function display_content($question, $rowclasses);
protected function display_end($question, $rowclasses) {
- echo '