From 271e6decdac9d9445c047a7d90cc98b7912cf1b6 Mon Sep 17 00:00:00 2001 From: jamiesensei Date: Thu, 9 Aug 2007 21:51:09 +0000 Subject: [PATCH] merging MOODLE_19_QUESTIONS with HEAD --- admin/report/question/index.php | 72 ++ lib/db/access.php | 89 ++- lib/db/upgrade.php | 105 +-- lib/form/questioncategory.php | 55 +- mod/quiz/backuplib.php | 192 +++-- mod/quiz/db/install.xml | 25 +- mod/quiz/db/upgrade.php | 12 +- mod/quiz/edit.php | 91 +-- mod/quiz/editlib.php | 25 +- mod/quiz/index.php | 5 +- mod/quiz/locallib.php | 7 +- mod/quiz/tabs.php | 16 +- question/backuplib.php | 160 ++-- question/category.php | 147 ++-- question/category_class.php | 526 ++++++------- question/edit.php | 27 +- question/editlib.php | 701 +++++++++++++----- question/export.php | 163 ++-- question/format.php | 179 ++++- question/format/coursetestmanager/format.php | 6 +- question/import.php | 246 ++---- question/preview.php | 28 +- question/question.php | 144 +++- question/restorelib.php | 343 ++++++--- question/tabs.php | 5 +- .../type/calculated/edit_calculated_form.php | 49 +- .../type/datasetdependent/abstractqtype.php | 61 +- .../datasetdefinitions_form.php | 9 + .../datasetdependent/datasetitems_form.php | 9 + question/type/description/questiontype.php | 4 +- question/type/edit_question_form.php | 124 +++- question/type/essay/edit_essay_form.php | 3 +- question/type/match/edit_match_form.php | 10 +- question/type/match/questiontype.php | 34 +- .../missingtype/edit_missingtype_form.php | 13 +- .../multianswer/edit_multianswer_form.php | 2 +- question/type/multianswer/questiontype.php | 1 + .../multichoice/edit_multichoice_form.php | 24 +- question/type/multichoice/questiontype.php | 36 +- .../type/numerical/edit_numerical_form.php | 21 +- question/type/questiontype.php | 203 ++++- question/type/random/edit_random_form.php | 16 +- .../randomsamatch/edit_randomsamatch_form.php | 2 +- .../shortanswer/edit_shortanswer_form.php | 13 +- .../type/truefalse/edit_truefalse_form.php | 6 +- question/upgrade.php | 276 ++++++- version.php | 2 +- 47 files changed, 2774 insertions(+), 1513 deletions(-) create mode 100644 admin/report/question/index.php diff --git a/admin/report/question/index.php b/admin/report/question/index.php new file mode 100644 index 0000000000..e5f94c4cb6 --- /dev/null +++ b/admin/report/question/index.php @@ -0,0 +1,72 @@ +dirroot.'/question/upgrade.php'); + require_once($CFG->libdir.'/adminlib.php'); + + admin_externalpage_setup('reportquestion'); + + admin_externalpage_print_header(); + print_heading(page_doc_link(get_string('adminreport', 'question'))); + + $probstr = ''; + if ($CFG->version < 2007081000){ + ///cwrqpfs issue + $probstr = print_heading(get_string('cwrqpfs', 'question'), '', 3, 'main', true); + + if ($updates = question_cwqpfs_to_update()){ + + $probstr .=('

'.get_string('cwrqpfsinfo', 'question').'

'); + $probstr .= ''; + } else { + $probstr .=('

'.get_string('cwrqpfsnoprob', 'question').'

'); + } + } + if ($probstr) { + print_box($probstr); + } else { + print_box(get_string('noprobs', 'question'), 'boxwidthnarrow boxaligncenter generalbox'); + } + admin_externalpage_print_footer(); +?> diff --git a/lib/db/access.php b/lib/db/access.php index 9ca5b0a72c..8887363518 100644 --- a/lib/db/access.php +++ b/lib/db/access.php @@ -799,9 +799,7 @@ $moodle_capabilities = array( ) ), - 'moodle/question:import' => array( - - 'riskbitmask' => RISK_XSS, + 'moodle/question:managecategory' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_COURSE, @@ -809,40 +807,95 @@ $moodle_capabilities = array( 'editingteacher' => CAP_ALLOW, 'admin' => CAP_ALLOW ) + ), + + //new in moodle 1.9 + 'moodle/question:add' => array( + 'riskbitmask' => RISK_SPAM, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'legacy' => array( + 'editingteacher' => CAP_ALLOW, + 'admin' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/question:manage' ), - - 'moodle/question:export' => array( - + 'moodle/question:editmine' => array( + 'riskbitmask' => RISK_SPAM, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'legacy' => array( + 'editingteacher' => CAP_ALLOW, + 'admin' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/question:manage' + ), + 'moodle/question:editall' => array( + 'riskbitmask' => RISK_SPAM, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'legacy' => array( + 'editingteacher' => CAP_ALLOW, + 'admin' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/question:manage' + ), + 'moodle/question:viewmine' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_COURSE, 'legacy' => array( 'editingteacher' => CAP_ALLOW, 'admin' => CAP_ALLOW - ) + ), + 'clonepermissionsfrom' => 'moodle/question:manage' ), - - 'moodle/question:managecategory' => array( - + 'moodle/question:viewall' => array( + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSE, + 'legacy' => array( + 'editingteacher' => CAP_ALLOW, + 'admin' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/question:manage' + ), + 'moodle/question:usemine' => array( + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSE, + 'legacy' => array( + 'editingteacher' => CAP_ALLOW, + 'admin' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/question:manage' + ), + 'moodle/question:useall' => array( + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSE, + 'legacy' => array( + 'editingteacher' => CAP_ALLOW, + 'admin' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/question:manage' + ), + 'moodle/question:movemine' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_COURSE, 'legacy' => array( 'editingteacher' => CAP_ALLOW, 'admin' => CAP_ALLOW - ) + ), + 'clonepermissionsfrom' => 'moodle/question:manage' ), - - 'moodle/question:manage' => array( - - 'riskbitmask' => RISK_XSS, - + 'moodle/question:moveall' => array( 'captype' => 'write', 'contextlevel' => CONTEXT_COURSE, 'legacy' => array( 'editingteacher' => CAP_ALLOW, 'admin' => CAP_ALLOW - ) + ), + 'clonepermissionsfrom' => 'moodle/question:manage' ), - + //END new in moodle 1.9 + // Configure the installed question types. 'moodle/question:config' => array( diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 26d392c43d..802afaf171 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -37,13 +37,13 @@ function xmldb_main_upgrade($oldversion=0) { if ($module = get_record('modules', 'name', 'exercise')) { if ($module->visible) { // Hide/disable the module entry - set_field('modules', 'visible', '0', 'id', $module->id); + set_field('modules', 'visible', '0', 'id', $module->id); // Save existing visible state for all activities set_field('course_modules', 'visibleold', '1', 'visible' ,'1', 'module', $module->id); set_field('course_modules', 'visibleold', '0', 'visible' ,'0', 'module', $module->id); // Hide all activities set_field('course_modules', 'visible', '0', 'module', $module->id); - + require_once($CFG->dirroot.'/course/lib.php'); rebuild_course_cache(); // Rebuld cache for all modules because they might have changed } @@ -55,7 +55,7 @@ function xmldb_main_upgrade($oldversion=0) { set_field('modules', 'visible', 0, 'name', 'lams'); // Disable it by default } } - + if ($result && $oldversion < 2006102600) { /// Define fields to be added to user_info_field @@ -81,7 +81,7 @@ function xmldb_main_upgrade($oldversion=0) { $result = $result && add_field($table, $field4); $result = $result && add_field($table, $field5); } - + if ($result && $oldversion < 2006112000) { /// Define field attachment to be added to post @@ -92,7 +92,7 @@ function xmldb_main_upgrade($oldversion=0) { /// Launch add field attachment $result = $result && add_field($table, $field); } - + if ($result && $oldversion < 2006112200) { /// Define field imagealt to be added to user @@ -102,7 +102,7 @@ function xmldb_main_upgrade($oldversion=0) { /// Launch add field imagealt $result = $result && add_field($table, $field); - + $table = new XMLDBTable('user'); $field = new XMLDBField('screenreader'); $field->setAttributes(XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null, null, '0', 'imagealt'); @@ -121,7 +121,7 @@ function xmldb_main_upgrade($oldversion=0) { if ($oldversion < 2006120400) { /// Remove secureforms config setting execute_sql("DELETE FROM {$CFG->prefix}config where name='secureforms'", true); } - + if (!empty($CFG->rolesactive) && $oldversion < 2006120700) { // add moodle/user:viewdetails to all roles! // note: use of assign_capability() is discouraged in upgrade script! if ($roles = get_records('role')) { @@ -198,7 +198,7 @@ function xmldb_main_upgrade($oldversion=0) { XMLDB_NOTNULL, null, null, null, 0); $f = $table->addFieldInfo('last_log_id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, 0); - // PK and indexes + // PK and indexes $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id')); // Create the table $result = $result && create_table($table); @@ -317,14 +317,14 @@ function xmldb_main_upgrade($oldversion=0) { $result = $result && create_table($table); // - // Prime MNET configuration entries -- will be needed later by auth/mnet + // Prime MNET configuration entries -- will be needed later by auth/mnet // include_once $CFG->dirroot . '/mnet/lib.php'; $env = new mnet_environment(); $env->init(); unset($env); - // add mnethostid to user- + // add mnethostid to user- $table = new XMLDBTable('user'); $field = new XMLDBField('mnethostid'); $field->setType(XMLDB_TYPE_INTEGER); @@ -339,8 +339,8 @@ function xmldb_main_upgrade($oldversion=0) { // The default mnethostid is zero... we need to update this for all // users of the local IdP service. - set_field('user', - 'mnethostid', $CFG->mnet_localhost_id, + set_field('user', + 'mnethostid', $CFG->mnet_localhost_id, 'mnethostid', '0'); @@ -353,7 +353,7 @@ function xmldb_main_upgrade($oldversion=0) { notify(get_string('duplicate_usernames', 'mnet', 'http://docs.moodle.org/en/DuplicateUsernames')); } - unset($table, $field, $index); + unset($table, $field, $index); /** ** auth/mnet tables @@ -458,7 +458,7 @@ function xmldb_main_upgrade($oldversion=0) { $f = $table->addFieldInfo('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, NULL, null, null, 0); $f = $table->addFieldInfo('hostid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, - XMLDB_NOTNULL, NULL, null, null, 0); + XMLDB_NOTNULL, NULL, null, null, 0); $f = $table->addFieldInfo('courseid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, NULL, null, null, 0); $f = $table->addFieldInfo('rolename', XMLDB_TYPE_CHAR, '255', null, @@ -522,14 +522,14 @@ function xmldb_main_upgrade($oldversion=0) { /// Launch create table for context_rel $result = $result && create_table($table); - + /// code here to fill the context_rel table /// use get record set to iterate slower build_context_rel(); } if ($result && $oldversion < 2007011501) { - if (!empty($CFG->enablerecordcache) && empty($CFG->rcache) && + if (!empty($CFG->enablerecordcache) && empty($CFG->rcache) && // Note: won't force-load these settings into CFG // we don't need or want cache during the upgrade itself empty($CFG->cachetype) && empty($CFG->intcachemax)) { @@ -679,30 +679,30 @@ function xmldb_main_upgrade($oldversion=0) { /// Launch drop index text $result = $result && drop_index($table, $index); - + $field = new XMLDBField('text'); $field->setAttributes(XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null, 'userid'); /// Launch change of type for field text $result = $result && change_field_type($table, $field); - + $index = new XMLDBIndex('text'); $index->setAttributes(XMLDB_INDEX_NOTUNIQUE, array('text')); /// Launch add index text - $result = $result && add_index($table, $index); + $result = $result && add_index($table, $index); } - + if ($result && $oldversion < 2007041100) { /// Define field idnumber to be added to course_modules $table = new XMLDBTable('course_modules'); $field = new XMLDBField('idnumber'); $field->setAttributes(XMLDB_TYPE_CHAR, '100', null, null, null, null, null, null, 'section'); - + /// Launch add field idnumber $result = $result && add_field($table, $field); - + /// Define index idnumber (unique) to be added to course_modules $table = new XMLDBTable('course_modules'); $index = new XMLDBIndex('idnumber'); @@ -717,7 +717,7 @@ function xmldb_main_upgrade($oldversion=0) { We could do all this with one tricky SQL statement but it's a one-off so no harm in using PHP loops */ if ($result && $oldversion < 2007041600) { - + /// Get the menu fields if ($fields = get_records('user_info_field', 'datatype', 'menu')) { foreach ($fields as $field) { @@ -729,7 +729,7 @@ function xmldb_main_upgrade($oldversion=0) { $options = explode("\n", $this->field->param1); foreach ($data as $d) { $key = array_search($d->data, $options); - + /// If the data is an integer and is not one of the options, /// set the respective option value if (is_int($d->data) and (($key === NULL) or ($key === false)) and isset($options[$d->data])) { @@ -740,9 +740,9 @@ function xmldb_main_upgrade($oldversion=0) { } } } - + } - + /// adding new gradebook tables if ($result && $oldversion < 2007041800) { @@ -764,7 +764,7 @@ function xmldb_main_upgrade($oldversion=0) { /// Launch create table for events_handlers $result = $result && create_table($table); - + /// Define table events_queue to be created $table = new XMLDBTable('events_queue'); @@ -782,7 +782,7 @@ function xmldb_main_upgrade($oldversion=0) { /// Launch create table for events_queue $result = $result && create_table($table); - + /// Define table events_queue_handlers to be created $table = new XMLDBTable('events_queue_handlers'); @@ -813,7 +813,7 @@ function xmldb_main_upgrade($oldversion=0) { /// Launch add field schedule $result = $result && add_field($table, $field); - + /// Define field status to be added to events_handlers $table = new XMLDBTable('events_handlers'); $field = new XMLDBField('status'); @@ -899,7 +899,7 @@ function xmldb_main_upgrade($oldversion=0) { /// Launch add field usermodified $result = $result && add_field($table, $field); - + /// Define key usermodified (foreign) to be added to post $table = new XMLDBTable('post'); $key = new XMLDBKey('usermodified'); @@ -952,7 +952,7 @@ function xmldb_main_upgrade($oldversion=0) { $application->xmlrpc_server_url = '/api/xmlrpc/server.php'; $application->sso_land_url = '/auth/xmlrpc/land.php'; $result = $result && insert_record('mnet_application', $application, false); - + // New mnet_host->applicationid field $table = new XMLDBTable('mnet_host'); $field = new XMLDBField('applicationid'); @@ -975,7 +975,6 @@ function xmldb_main_upgrade($oldversion=0) { $result = $result && question_remove_rqp_qtype_config_string(); } - if ($result && $oldversion < 2007072200) { /// Remove obsoleted unit tests tables - they will be recreated automatically $tables = array('grade_categories', @@ -1448,21 +1447,21 @@ function xmldb_main_upgrade($oldversion=0) { if ($result && $oldversion < 2007073100) { - /// Define table grade_outcomes_courses to be created - $table = new XMLDBTable('grade_outcomes_courses'); - - /// Adding fields to table grade_outcomes_courses - $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null); - $table->addFieldInfo('courseid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null); - $table->addFieldInfo('outcomeid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null); - - /// Adding keys to table grade_outcomes_courses - $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id')); - $table->addKeyInfo('courseid', XMLDB_KEY_FOREIGN, array('courseid'), 'course', array('id')); - $table->addKeyInfo('outcomeid', XMLDB_KEY_FOREIGN, array('outcomeid'), 'grade_outcomes', array('id')); - - /// Launch create table for grade_outcomes_courses - $result = $result && create_table($table); + /// Define table grade_outcomes_courses to be created + $table = new XMLDBTable('grade_outcomes_courses'); + + /// Adding fields to table grade_outcomes_courses + $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null); + $table->addFieldInfo('courseid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null); + $table->addFieldInfo('outcomeid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null); + + /// Adding keys to table grade_outcomes_courses + $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->addKeyInfo('courseid', XMLDB_KEY_FOREIGN, array('courseid'), 'course', array('id')); + $table->addKeyInfo('outcomeid', XMLDB_KEY_FOREIGN, array('outcomeid'), 'grade_outcomes', array('id')); + + /// Launch create table for grade_outcomes_courses + $result = $result && create_table($table); } @@ -1562,7 +1561,7 @@ function xmldb_main_upgrade($oldversion=0) { $result = $result && add_field($table, $field); } } - + // adding unique contraint on (courseid,shortname) of an outcome if ($result && $oldversion < 2007080100) { @@ -1581,7 +1580,7 @@ function xmldb_main_upgrade($oldversion=0) { set_config('supportemail', s($firstadmin->email)); } } - + /// MDL-10679, context_rel clean up if ($result && $oldversion < 2007080200) { delete_records('context_rel'); @@ -1719,6 +1718,14 @@ function xmldb_main_upgrade($oldversion=0) { } } */ + //need to change this when we merge with HEAD + if ($result && $oldversion < 2007081000) { + require_once($CFG->dirroot . '/question/upgrade.php'); + $result = $result && question_upgrade_context_etc(); + } + return $result; } + + ?> diff --git a/lib/form/questioncategory.php b/lib/form/questioncategory.php index 04a90596dd..2347af82c8 100644 --- a/lib/form/questioncategory.php +++ b/lib/form/questioncategory.php @@ -2,20 +2,22 @@ /** * A moodle form field type for question categories. * - * @copyright © 2006 The Open University - * @author T.J.Hunt@open.ac.uk + * @copyright Jamie Pratt + * @author Jamie Pratt * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package moodleforms - *//** */ + */ global $CFG; -require_once("$CFG->libdir/form/select.php"); +require_once("$CFG->libdir/form/selectgroups.php"); +require_once("$CFG->libdir/questionlib.php"); /** * HTML class for a drop down element to select a question category. * @access public */ -class MoodleQuickForm_questioncategory extends MoodleQuickForm_select { +class MoodleQuickForm_questioncategory extends MoodleQuickForm_selectgroups { + var $_options = array('top'=>false, 'currentcat'=>0, 'nochildrenof' => -1); /** * Constructor @@ -28,45 +30,16 @@ class MoodleQuickForm_questioncategory extends MoodleQuickForm_select { * @access public * @return void */ - function MoodleQuickForm_questioncategory($elementName = null, - $elementLabel = null, $attributes = null, $options = null) { - HTML_QuickForm_element::HTML_QuickForm_element($elementName, $elementLabel, $attributes, null); - - global $COURSE; + function MoodleQuickForm_questioncategory($elementName = null, $elementLabel = null, $options = null, $attributes = null) { + MoodleQuickForm_selectgroups::MoodleQuickForm_selectgroups($elementName, $elementLabel, array(), $attributes); $this->_type = 'questioncategory'; - if (!empty($options['courseid'])) { - $this->_courseid = $options['courseid']; - } else { - $this->_courseid = $COURSE->id; - } - if (!empty($options['published'])) { - $this->_published = $options['published']; - } else { - $this->_published = false; - } - if (!empty($options['only_editable'])) { - $this->_only_editable = $options['only_editable']; - } else { - $this->_only_editable = false; + if (is_array($options)) { + $this->_options = $options + $this->_options; + $this->loadArrayOptGroups( + question_category_options($this->_options['contexts'], $this->_options['top'], $this->_options['currentcat'], + false, $this->_options['nochildrenof'])); } } - /** - * Called by HTML_QuickForm whenever form event is made on this element - * - * @param string $event Name of event - * @param mixed $arg event arguments - * @param object $caller calling object - * @access public - * @return mixed - */ - function onQuickFormEvent($event, $arg, &$caller) { - switch ($event) { - case 'createElement': - $this->load(question_category_options($this->_courseid, $this->_published, $this->_only_editable)); - break; - } - return parent::onQuickFormEvent($event, $arg, $caller); - } } ?> \ No newline at end of file diff --git a/mod/quiz/backuplib.php b/mod/quiz/backuplib.php index 3bfb63e48d..bcb6cfff5b 100644 --- a/mod/quiz/backuplib.php +++ b/mod/quiz/backuplib.php @@ -29,27 +29,54 @@ // When we backup a quiz we also need to backup the questions and possibly // the data about student interaction with the questions. The functions to do // that are included with the following library - require_once("$CFG->libdir/questionlib.php"); require_once("$CFG->dirroot/question/backuplib.php"); -//STEP 1. Backup categories/questions and associated structures - // (course independent) - - //Insert necessary category ids to backup_ids table - function insert_category_ids($course, $backup_unique_code, $instances = null) { + /* + * Insert necessary category ids to backup_ids table. Called during backup_check.html + */ + function insert_category_and_question_ids($course, $backup_unique_code, $instances = null) { global $CFG; - include_once("$CFG->dirroot/mod/quiz/lib.php"); // Create missing categories and reasign orphaned questions. fix_orphaned_questions($course); - // First, ALL categories from this course. + // First, all categories from this course's context. + $coursecontext = get_context_instance(CONTEXT_COURSE, $course); $status = execute_sql("INSERT INTO {$CFG->prefix}backup_ids (backup_code, table_name, old_id, info) SELECT '$backup_unique_code', 'question_categories', qc.id, '' FROM {$CFG->prefix}question_categories qc - WHERE qc.course = $course", false); - - // Then published categories from other courses used by the quizzes we are backing up. + WHERE qc.contextid = {$coursecontext->id}", false); + + + // then, all categories from this course's modules' contexts. + // using 'dummykeyname' in sql because otherwise get_records_sql_menu returns an error + // if two key names are the same. + $cmcontexts = get_records_sql_menu("SELECT c.id, c.id AS dummykeyname FROM `{$CFG->prefix}modules` AS `mod`, + `{$CFG->prefix}course_modules` AS `cm`, + `{$CFG->prefix}context` as `c` + WHERE mod.name = 'quiz' AND mod.id = cm.module AND cm.id = c.instanceid + AND c.contextlevel = ".CONTEXT_MODULE." AND cm.course = $course"); + if ($cmcontexts){ + $status = $status && execute_sql("INSERT INTO {$CFG->prefix}backup_ids + (backup_code, table_name, old_id, info) + SELECT '$backup_unique_code', 'question_categories', qc.id, '' + FROM {$CFG->prefix}question_categories qc + WHERE qc.contextid IN (".join(array_keys($cmcontexts), ', ').")", false); + } + //put the ids of the questions from all these categories into the db. + $status = $status && execute_sql("INSERT INTO {$CFG->prefix}backup_ids + (backup_code, table_name, old_id, info) + SELECT '$backup_unique_code', 'question', q.id, '' + FROM {$CFG->prefix}question AS q, {$CFG->prefix}backup_ids AS bk + WHERE q.category = bk.old_id AND bk.table_name = 'question_categories' AND + bk.backup_code = '$backup_unique_code'", false); + + // Then categories from parent contexts used by the quizzes we are backing up. + //TODO this will need generalising when we have modules other than quiz using shared questions above course level. + $parentcontexts = get_parent_contexts($coursecontext); + $from = "{$CFG->prefix}quiz quiz,"; + $where = "AND quiz.course = '$course' + AND qqi.quiz = quiz.id"; if (!empty($instances) && is_array($instances) && count($instances)) { $questionselectsqlfrom = ''; $questionselectsqlwhere = 'AND qqi.quiz IN ('.implode(',',array_keys($instances)).')'; @@ -61,7 +88,7 @@ $categoriesinothercourses = get_records_sql(" SELECT id, parent, 0 AS childrendone FROM {$CFG->prefix}question_categories - WHERE course <> $course + WHERE contextid IN (".join($parentcontexts, ', ').") AND id IN ( SELECT DISTINCT question.category FROM {$CFG->prefix}question question, @@ -70,63 +97,90 @@ WHERE qqi.question = question.id $questionselectsqlwhere )", false); - if (!$categoriesinothercourses) { - $categoriesinothercourses = array(); - } - - // Add the parent categories, of these categories up to the top of the category tree. - foreach ($categoriesinothercourses as $category) { - while ($category->parent != 0) { - if (array_key_exists($category->parent, $categoriesinothercourses)) { - // Parent category already on the list. - break; - } - $currentid = $category->id; - $category = get_record('question_categories', 'id', $category->parent, '', '', '', '', 'id, parent, 0 AS childrendone'); - if ($category) { - $categoriesinothercourses[$category->id] = $category; - } else { - // Parent not found: this indicates an error, but just fix it. - set_field('question_categories', 'parent', 0, 'id', $currentid); - break; + if (!$categories) { + $categories = array(); + } else { + //put the ids of the used questions from all these categories into the db. + $status = $status && execute_sql("INSERT INTO {$CFG->prefix}backup_ids + (backup_code, table_name, old_id, info) + SELECT '$backup_unique_code', 'question', q.id, '' + FROM {$CFG->prefix}question q, + $from + {$CFG->prefix}question_categories qc, + {$CFG->prefix}quiz_question_instances qqi + WHERE (qqi.question = q.id + OR qqi.question = q.parent) + AND q.category = qc.id + AND qc.contextid IN (".join($parentcontexts, ', ').") + $where", false); + + // Add the parent categories, of these categories up to the top of the category tree. + // not backing up the questions in these categories. + foreach ($categories as $category) { + while ($category->parent != 0) { + if (array_key_exists($category->parent, $categories)) { + // Parent category already on the list. + break; + } + $currentid = $category->id; + $category = get_record('question_categories', 'id', $category->parent, '', '', '', '', 'id, parent, 0 AS childrendone'); + if ($category) { + $categories[$category->id] = $category; + } else { + // Parent not found: this indicates an error, but just fix it. + set_field('question_categories', 'parent', 0, 'id', $currentid); + break; + } } } - } - // Now we look for random questions used in our quizzes - // that select from subcategories in other courses. That implies - // those subcategories also need to be backed up. (The categories themselves - // and their parents will already have been included.) - $categorieswithrandom = get_records_sql(" - SELECT DISTINCT question.category AS id - FROM {$CFG->prefix}quiz_question_instances qqi, - $questionselectsqlfrom - {$CFG->prefix}question question - WHERE question.id = qqi.question - AND question.qtype = '" . RANDOM . "' - AND question.questiontext = '1' - $questionselectsqlwhere - "); - if ($categorieswithrandom) { - foreach ($categorieswithrandom as $category) { - if (isset($categoriesinothercourses[$category->id])){ - $status = quiz_backup_add_sub_categories($categoriesinothercourses, $category->id); + // Now we look for categories from other courses containing random questions + // in our quizzes that select from the category and its subcategories. That implies + // those subcategories also need to be backed up. (The categories themselves + // and their parents will already have been included.) + $categorieswithrandom = get_records_sql(" + SELECT question.category AS id, SUM(question.questiontext) as questiontext + FROM {$CFG->prefix}quiz_question_instances qqi, + $from + {$CFG->prefix}question question + WHERE question.id = qqi.question + AND question.qtype = '" . RANDOM . "' + $where + GROUP BY question.category + "); + $randomselectedquestions = array(); + if ($categorieswithrandom) { + foreach ($categorieswithrandom as $category) { + if ($category->questiontext){ + $status = $status && quiz_backup_add_sub_categories($categories, $randomselectedquestions, $category->id); + } + } + $returnval = get_records_sql(" + SELECT question.id + FROM {$CFG->prefix}question question + WHERE question.category IN (".join(array_keys($categorieswithrandom), ', ').")"); + if ($returnval) { + $randomselectedquestions += $returnval; } } - } - // Finally, add all these extra categories to the backup_ids table. - foreach ($categoriesinothercourses as $category) { - $status = $status && backup_putid($backup_unique_code, 'question_categories', $category->id, 0); + // Finally, add all these extra categories to the backup_ids table. + foreach ($categories as $category) { + $status = $status && backup_putid($backup_unique_code, 'question_categories', $category->id, 0); + } + // Finally, add all these extra categories to the backup_ids table. + foreach ($randomselectedquestions as $question) { + $status = $status && backup_putid($backup_unique_code, 'question', $question->id, 0); + } } - return $status; } /** * Helper function adding the id of all the subcategories of a category to an array. */ - function quiz_backup_add_sub_categories(&$categories, $categoryid) { + function quiz_backup_add_sub_categories(&$categories, &$questions, $categoryid) { + global $CFG; $status = true; if ($categories[$categoryid]->childrendone) { return $status; @@ -136,7 +190,16 @@ if (!array_key_exists($subcategory->id, $categories)) { $categories[$subcategory->id] = $subcategory; } - $status = $status && quiz_backup_add_sub_categories($categories, $subcategory->id); + $status = $status && quiz_backup_add_sub_categories($categories, $questions, $subcategory->id); + } + $subcatlist = join(array_keys($subcategories), ','); + $returnval = get_records_sql(" + SELECT question.id + FROM {$CFG->prefix}question question + WHERE question.category IN ($subcatlist) + "); + if ($returnval) { + $questions += $returnval; } } $categories[$categoryid]->childrendone = 1; @@ -166,10 +229,10 @@ if (!$exist) { //Build a new category $db_cat = new stdClass; - $db_cat->course = $course; + // always create missing categories in course context + $db_cat->contextid = get_context_instance(CONTEXT_COURSE, $course); $db_cat->name = get_string('recreatedcategory','',$key); $db_cat->info = get_string('recreatedcategory','',$key); - $db_cat->publish = 1; $db_cat->stamp = make_unique_id_code(); //Insert the new category $catid = insert_record('question_categories',$db_cat); @@ -453,11 +516,14 @@ ////Return an array of info (name,value) /// $instances is an array with key = instanceid, value = object (name,id,userdata) function quiz_check_backup_mods($course,$user_data= false,$backup_unique_code,$instances=null) { - //Deletes data from mdl_backup_ids (categories section) - delete_category_ids ($backup_unique_code); - //Create date into mdl_backup_ids (categories section) - insert_category_ids ($course,$backup_unique_code,$instances); + delete_ids ($backup_unique_code, 'question_categories'); + delete_ids ($backup_unique_code, 'question'); + //this function selects all the questions / categories to be backed up. + insert_category_and_question_ids($course, $backup_unique_code, $instances); + if ($course != SITEID){ + question_insert_site_file_names($course, $backup_unique_code); + } if (!empty($instances) && is_array($instances) && count($instances)) { $info = array(); foreach ($instances as $id => $instance) { diff --git a/mod/quiz/db/install.xml b/mod/quiz/db/install.xml index 84fdd25731..a717b99933 100755 --- a/mod/quiz/db/install.xml +++ b/mod/quiz/db/install.xml @@ -1,17 +1,16 @@ - - - - - - - + + + + + @@ -20,7 +19,7 @@ - +
@@ -39,12 +38,18 @@ - + + + + + - + + +
diff --git a/mod/quiz/db/upgrade.php b/mod/quiz/db/upgrade.php index 31a05191d5..aaa42c1d0b 100644 --- a/mod/quiz/db/upgrade.php +++ b/mod/quiz/db/upgrade.php @@ -1,6 +1,6 @@ prefix . 'quiz SET review = ' . sql_bitor(sql_bitand('review', sql_bitnot(QUIZ_REVIEW_OVERALLFEEDBACK)), @@ -82,7 +82,7 @@ function xmldb_quiz_upgrade($oldversion=0) { (($CFG->quiz_review & QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_OPEN) << 14) | (($CFG->quiz_review & QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_CLOSED) << 12)); } - + return $result; } diff --git a/mod/quiz/edit.php b/mod/quiz/edit.php index 4930d296d5..dfe341e965 100644 --- a/mod/quiz/edit.php +++ b/mod/quiz/edit.php @@ -30,12 +30,12 @@ require_once($CFG->dirroot.'/mod/quiz/editlib.php'); /** - * Callback function called from question_list() function (which is called from showbank() + * Callback function called from question_list() function (which is called from showbank()) * Displays action icon as first action for each question. */ - function module_specific_actions($pageurl, $questionid, $cmid){ + function module_specific_actions($pageurl, $questionid, $cmid, $canuse){ global $CFG; - if (has_capability("mod/quiz:manage", get_context_instance(CONTEXT_MODULE, $cmid))){ + if ($canuse){ $straddtoquiz = get_string("addtoquiz", "quiz"); $out = "get_query_string()."&addquestion=$questionid&sesskey=".sesskey()."\">pixpath/t/moveleft.gif\" alt=\"$straddtoquiz\" /> "; @@ -45,28 +45,24 @@ } } /** - * Callback function called from question_list() function (which is called from showbank() + * Callback function called from question_list() function (which is called from showbank()) * Displays button in form with checkboxes for each question. */ function module_specific_buttons($cmid){ global $THEME; - if (has_capability("mod/quiz:manage", get_context_instance(CONTEXT_MODULE, $cmid))){ - $straddtoquiz = get_string("addtoquiz", "quiz"); - $out = "larrow} $straddtoquiz\" />\n"; - $out .= ''; diff --git a/mod/quiz/editlib.php b/mod/quiz/editlib.php index f75a8e95cf..bcfde9aa3f 100644 --- a/mod/quiz/editlib.php +++ b/mod/quiz/editlib.php @@ -141,6 +141,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete=true, $showbreak $strgrade = get_string("grade"); $strremove = get_string('remove', 'quiz'); $stredit = get_string("edit"); + $strview = get_string("view"); $straction = get_string("action"); $strmoveup = get_string("moveup"); $strmovedown = get_string("movedown"); @@ -155,7 +156,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete=true, $showbreak return 0; } - if (!$questions = get_records_sql("SELECT q.*,c.course + if (!$questions = get_records_sql("SELECT q.*,c.contextid FROM {$CFG->prefix}question q, {$CFG->prefix}question_categories c WHERE q.id in ($quiz->questions) @@ -169,7 +170,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete=true, $showbreak $count = 0; $qno = 1; $sumgrade = 0; - $order = explode(",", $quiz->questions); + $order = explode(',', $quiz->questions); $lastindex = count($order)-1; // If the list does not end with a pagebreak then add it on. if ($order[$lastindex] != 0) { @@ -235,12 +236,11 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete=true, $showbreak echo ''; } $count++; - // missing here, if loop is broken, need to close the from line 199/201 + // missing here, if loop is broken, need to close the echo ""; continue; } $question = $questions[$qnum]; - $canedit = has_capability('moodle/question:manage', get_context_instance(CONTEXT_COURSE, $question->course)); echo "'; - echo '
'; - return $out; - } else { - return ''; - } + $straddtoquiz = get_string("addtoquiz", "quiz"); + $out = "larrow} $straddtoquiz\" />\n"; + echo '
'; + return $out; } - - + + /** - * Callback function called from question_list() function (which is called from showbank() - * Displays button in form with checkboxes for each question. + * Callback function called from question_list() function (which is called from showbank()) */ - function module_specific_controls($totalnumber, $recurse, $categoryid, $cmid){ - if (has_capability("mod/quiz:manage", get_context_instance(CONTEXT_MODULE, $cmid))){ + function module_specific_controls($totalnumber, $recurse, $category, $cmid){ + $catcontext = get_context_instance_by_id($category->contextid); + if (has_capability('moodle/question:useall', $catcontext)){ for ($i = 1;$i <= min(10, $totalnumber); $i++) { $randomcount[$i] = $i; } @@ -76,16 +72,16 @@ $out = '
'; $out .= get_string('addrandom', 'quiz', choose_from_menu($randomcount, 'randomcount', '1', '', '', '', true)); $out .= ''; - $out .= ""; + $out .= "id\" />"; $out .= ' '; $out .= helpbutton('random', get_string('random', 'quiz'), 'quiz', true, false, '', true); - return $out; } else { - return ''; + $out = ''; } - } - - list($thispageurl, $courseid, $cmid, $cm, $quiz, $pagevars) = question_edit_setup(true); + return $out; + } + + list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) = question_edit_setup('editq', true); //these params are only passed from page request to request while we stay on this page //otherwise they would go in question_edit_setup @@ -99,12 +95,12 @@ if ($quiz_reordertool != 0) { $thispageurl->param('reordertool', $quiz_reordertool); } - + $strquizzes = get_string('modulenameplural', 'quiz'); $strquiz = get_string('modulename', 'quiz'); $streditingquestions = get_string('editquestions', "quiz"); $streditingquiz = get_string('editinga', 'moodle', $strquiz); - + @@ -112,15 +108,14 @@ if (! $course = get_record("course", "id", $quiz->course)) { error("This course doesn't exist"); } - $coursecontext = get_context_instance(CONTEXT_COURSE, $quiz->course); - $quizcontext = get_context_instance(CONTEXT_MODULE, $quiz->cmid); - + // Log this visit. add_to_log($cm->course, 'quiz', 'editquestions', "view.php?id=$cm->id", "$quiz->id", $cm->id); - require_capability('mod/quiz:manage', $quizcontext); + //you need mod/quiz:manage in addition to question capabilities to access this page. + require_capability('mod/quiz:manage', $contexts->lowest()); if (isset($quiz->instance) && empty($quiz->grades)){ // Construct an array to hold all the grades. @@ -187,6 +182,8 @@ if (! $category = get_record('question_categories', 'id', $categoryid)) { error('Category ID is incorrect'); } + $catcontext = get_context_instance_by_id($category->contextid); + require_capability('moodle/question:useall', $catcontext); $category->name = addslashes($category->name); // find existing random questions in this category $random = RANDOM; @@ -214,7 +211,7 @@ if ($randomcreate > 0) { $form->name = get_string('random', 'quiz') .' ('. $category->name .')'; - $form->category = $category->id; + $form->category = "$category->id,$category->contextid"; $form->questiontext = $recurse; // we use the questiontext field to store the info // on whether to include questions in subcategories $form->questiontextformat = 0; @@ -246,7 +243,6 @@ error('Could not save layout'); } } - if (isset($_REQUEST['delete']) and confirm_sesskey()) { /// Remove a question from the quiz quiz_delete_quiz_question($_REQUEST['delete'], $quiz); } @@ -267,7 +263,7 @@ $questions[$value] = $oldquestions[$key]; } } - + // If ordering info was given, reorder the questions if ($questions) { ksort($questions); @@ -296,26 +292,23 @@ delete_records('quiz_attempts', 'preview', '1', 'quiz', $quiz->id); } -/// all commands have been dealt with, now print the page + question_showbank_actions($thispageurl, $cm); - if (empty($quiz->category) or !record_exists('question_categories', 'id', $quiz->category)) { - $category = get_default_question_category($course->id); - $quiz->category = $category->id; - } +/// all commands have been dealt with, now print the page // Print basic page layout. if (isset($quiz->instance) and record_exists_select('quiz_attempts', "quiz = '$quiz->instance' AND preview = '0'")){ // one column layout with table of questions used in this quiz - $strupdatemodule = has_capability('moodle/course:manageactivities', $coursecontext) + $strupdatemodule = has_capability('moodle/course:manageactivities', $contexts->lowest()) ? update_module_button($cm->id, $course->id, get_string('modulename', 'quiz')) : ""; $navlinks = array(); - $navlinks[] = array('name' => $strquizzes, 'link' => "index.php?id=$course->id", 'type' => 'activity'); - $navlinks[] = array('name' => format_string($quiz->name), 'link' => "view.php?q=$quiz->instance", 'type' => 'activityinstance'); + $navlinks[] = array('name' => $strquizzes, 'link' => "index.php?id=$course->id", 'type' => 'activity'); + $navlinks[] = array('name' => format_string($quiz->name), 'link' => "view.php?q=$quiz->instance", 'type' => 'activityinstance'); $navlinks[] = array('name' => $streditingquiz, 'link' => '', 'type' => 'title'); $navigation = build_navigation($navlinks); - + print_header_simple($streditingquiz, '', $navigation, "", "", true, $strupdatemodule); @@ -329,9 +322,7 @@ $a->attemptnum = count_records('quiz_attempts', 'quiz', $quiz->id, 'preview', 0); $a->studentnum = count_records_select('quiz_attempts', "quiz = '$quiz->id' AND preview = '0'", 'COUNT(DISTINCT userid)'); $a->studentstring = $course->students; - if (! $cm = get_coursemodule_from_instance("quiz", $quiz->instance, $course->id)) { - error("Course Module ID was incorrect"); - } + echo "
\n"; echo "id\">".get_string('numattempts', 'quiz', $a)."
".get_string("attemptsexist","quiz"); echo "

\n"; @@ -347,15 +338,15 @@ } // two column layout with quiz info in left column - $strupdatemodule = has_capability('moodle/course:manageactivities', $coursecontext) + $strupdatemodule = has_capability('moodle/course:manageactivities', $contexts->lowest()) ? update_module_button($cm->id, $course->id, get_string('modulename', 'quiz')) : ""; $navlinks = array(); - $navlinks[] = array('name' => $strquizzes, 'link' => "index.php?id=$course->id", 'type' => 'activity'); - $navlinks[] = array('name' => format_string($quiz->name), 'link' => "view.php?q=$quiz->instance", 'type' => 'activityinstance'); + $navlinks[] = array('name' => $strquizzes, 'link' => "index.php?id=$course->id", 'type' => 'activity'); + $navlinks[] = array('name' => format_string($quiz->name), 'link' => "view.php?q=$quiz->instance", 'type' => 'activityinstance'); $navlinks[] = array('name' => $streditingquiz, 'link' => '', 'type' => 'title'); $navigation = build_navigation($navlinks); - + print_header_simple($streditingquiz, '', $navigation, "", "", true, $strupdatemodule); $currenttab = 'edit'; @@ -377,7 +368,7 @@ echo '
'; - question_showbank($thispageurl, $cm, $pagevars['qpage'], $pagevars['qperpage'], $pagevars['qsortorder'], $pagevars['qsortorderdecoded'], + question_showbank('editq', $contexts, $thispageurl, $cm, $pagevars['qpage'], $pagevars['qperpage'], $pagevars['qsortorder'], $pagevars['qsortorderdecoded'], $pagevars['cat'], $pagevars['recurse'], $pagevars['showhidden'], $pagevars['showquestiontext']); echo '
 
"; if ($count != 0) { @@ -276,17 +276,20 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete=true, $showbreak } echo ''; - if ($question->qtype != 'random') { - quiz_question_preview_button($quiz, $question); + if (($question->qtype != 'random')){ + echo quiz_question_preview_button($quiz, $question); } - if ($canedit) { - $returnurl = $pageurl->out(); - $questionparams = array('returnurl' => $returnurl, 'cmid'=>$quiz->cmid, 'id' => $qnum); - $questionurl = new moodle_url("$CFG->wwwroot/question/question.php", $questionparams); + $returnurl = $pageurl->out(); + $questionparams = array('returnurl' => $returnurl, 'cmid'=>$quiz->cmid, 'id' => $question->id); + $questionurl = new moodle_url("$CFG->wwwroot/question/question.php", $questionparams); + if (question_has_capability_on($question, 'edit', $question->category) || question_has_capability_on($question, 'move', $question->category)) { echo "out()."\"> pixpath/t/edit.gif\" class=\"iconsmall\" alt=\"$stredit\" />"; + } elseif (question_has_capability_on($question, 'view', $question->category)){ + echo "out(false, array('id'=>$question->id))."\">pixpath/i/info.gif\" alt=\"$strview\" /> "; } - if ($allowdelete) { + if ($allowdelete && question_has_capability_on($question, 'use', $question->category)) { // remove from quiz, not question delete. echo "out_action(array('delete'=>$count))."\"> pixpath/t/removeright.gif\" class=\"iconsmall\" alt=\"$strremove\" />"; } diff --git a/mod/quiz/index.php b/mod/quiz/index.php index d7d53c4ebd..60aa60448a 100644 --- a/mod/quiz/index.php +++ b/mod/quiz/index.php @@ -21,7 +21,8 @@ // Print the header $strquizzes = get_string("modulenameplural", "quiz"); $streditquestions = ''; - if (has_capability('moodle/question:manage', $coursecontext)) { + $editqcontexts = new question_edit_contexts($coursecontext); + if ($editqcontexts->have_one_edit_tab_cap('questions')) { $streditquestions = "
wwwroot/question/edit.php\">
@@ -33,7 +34,7 @@ $navlinks = array(); $navlinks[] = array('name' => $strquizzes, 'link' => '', 'type' => 'activity'); $navigation = build_navigation($navlinks); - + print_header_simple($strquizzes, '', $navigation, '', '', true, $streditquestions, navmenu($course)); diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index d0ef3ec790..eab2d7441d 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -124,13 +124,13 @@ function quiz_delete_attempt($attempt, $quiz) { return; } } - + if ($attempt->quiz != $quiz->id) { debugging("Trying to delete attempt $attempt->id which belongs to quiz $attempt->quiz " . "but was passed quiz $quiz->id."); return; } - + delete_records('quiz_attempts', 'id', $attempt->id); delete_attempt($attempt->uniqueid); @@ -662,6 +662,9 @@ function quiz_upgrade_states($attempt) { */ function quiz_question_preview_button($quiz, $question) { global $CFG; + if (!question_has_capability_on($question, 'use', $question->category)){ + return ''; + } $strpreview = get_string('previewquestion', 'quiz'); return link_to_popup_window('/question/preview.php?id=' . $question->id . '&quizid=' . $quiz->id, 'questionpreview', "pixpath/t/preview.gif\" class=\"iconsmall\" alt=\"$strpreview\" />", diff --git a/mod/quiz/tabs.php b/mod/quiz/tabs.php index 059e435941..4bc6c1b542 100644 --- a/mod/quiz/tabs.php +++ b/mod/quiz/tabs.php @@ -16,12 +16,13 @@ if (!isset($currenttab)) { if (!isset($cm)) { $cm = get_coursemodule_from_instance('quiz', $quiz->id); } -if (!isset($course)) { - $course = get_record('course', 'id', $quiz->course); -} + $context = get_context_instance(CONTEXT_MODULE, $cm->id); +if (!isset($contexts)){ + $contexts = new question_edit_contexts($context); +} $tabs = array(); $row = array(); $inactive = array(); @@ -36,7 +37,7 @@ if (has_capability('mod/quiz:viewreports', $context)) { if (has_capability('mod/quiz:preview', $context)) { $row[] = new tabobject('preview', "$CFG->wwwroot/mod/quiz/attempt.php?q=$quiz->id", get_string('preview', 'quiz')); } -if (has_capability('mod/quiz:manage', $context)) { +if ($contexts->have_one_edit_tab_cap('editq')) { $row[] = new tabobject('edit', "$CFG->wwwroot/mod/quiz/edit.php?cmid=$cm->id", get_string('edit')); } @@ -83,9 +84,12 @@ if ($currenttab == 'edit' and isset($mode)) { $streditingquiz = get_string("editinga", "moodle", $strquiz); $strupdate = get_string('updatethis', 'moodle', $strquiz); - $row[] = new tabobject('editq', "$CFG->wwwroot/mod/quiz/edit.php?".$thispageurl->get_query_string(), $strquiz, $streditingquiz); - questionbank_navigation_tabs($row, $context, $thispageurl->get_query_string()); + if ($contexts->have_one_edit_tab_cap('editq')) { + $row[] = new tabobject('editq', "$CFG->wwwroot/mod/quiz/edit.php?".$thispageurl->get_query_string(), $strquiz, $streditingquiz); + } + questionbank_navigation_tabs($row, $contexts, $thispageurl->get_query_string()); $tabs[] = $row; + } print_tabs($tabs, $currenttab, $inactive, $activated); diff --git a/question/backuplib.php b/question/backuplib.php index 3637f944f2..14de28b4eb 100644 --- a/question/backuplib.php +++ b/question/backuplib.php @@ -77,6 +77,42 @@ require_once("$CFG->libdir/questionlib.php"); + function backup_question_category_context($bf, $contextid, $course) { + $status = true; + $context = get_context_instance_by_id($contextid); + $status = $status && fwrite($bf,start_tag("CONTEXT",4,true)); + switch ($context->contextlevel){ + case CONTEXT_MODULE: + $status = $status && fwrite($bf,full_tag("LEVEL",5,false, 'module')); + $status = $status && fwrite($bf,full_tag("INSTANCE",5,false, $context->instanceid)); + break; + case CONTEXT_COURSE: + $status = $status && fwrite($bf,full_tag("LEVEL",5,false, 'course')); + break; + case CONTEXT_COURSECAT: + $thiscourse = get_record('course', 'id', $course); + $cat = $thiscourse->category; + $catno = 1; + while($context->instanceid != $cat){ + $catno ++; + if ($cat ==0) { + return false; + } + $cat = get_field('course_categories', 'parent', 'id', $cat); + } + $status = $status && fwrite($bf,full_tag("LEVEL",5,false, 'coursecategory')); + $status = $status && fwrite($bf,full_tag("COURSECATEGORYLEVEL",5,false, $catno)); + break; + case CONTEXT_SYSTEM: + $status = $status && fwrite($bf,full_tag("LEVEL",5,false, 'system')); + break; + default : + return false; + } + $status = $status && fwrite($bf,end_tag("CONTEXT",4,true)); + return $status; + } + function backup_question_categories($bf,$preferences) { global $CFG; @@ -89,28 +125,28 @@ //If we've categories if ($categories) { //Write start tag - $status = fwrite($bf,start_tag("QUESTION_CATEGORIES",2,true)); + $status = $status && fwrite($bf,start_tag("QUESTION_CATEGORIES",2,true)); //Iterate over each category foreach ($categories as $cat) { //Start category - $status = fwrite ($bf,start_tag("QUESTION_CATEGORY",3,true)); + $status = $status && fwrite ($bf,start_tag("QUESTION_CATEGORY",3,true)); //Get category data from question_categories $category = get_record ("question_categories","id",$cat->old_id); //Print category contents - fwrite($bf,full_tag("ID",4,false,$category->id)); - fwrite($bf,full_tag("NAME",4,false,$category->name)); - fwrite($bf,full_tag("INFO",4,false,$category->info)); - fwrite($bf,full_tag("PUBLISH",4,false,$category->publish)); - fwrite($bf,full_tag("STAMP",4,false,$category->stamp)); - fwrite($bf,full_tag("PARENT",4,false,$category->parent)); - fwrite($bf,full_tag("SORTORDER",4,false,$category->sortorder)); + $status = $status && fwrite($bf,full_tag("ID",4,false,$category->id)); + $status = $status && fwrite($bf,full_tag("NAME",4,false,$category->name)); + $status = $status && fwrite($bf,full_tag("INFO",4,false,$category->info)); + $status = $status && backup_question_category_context($bf, $category->contextid, $preferences->backup_course); + $status = $status && fwrite($bf,full_tag("STAMP",4,false,$category->stamp)); + $status = $status && fwrite($bf,full_tag("PARENT",4,false,$category->parent)); + $status = $status && fwrite($bf,full_tag("SORTORDER",4,false,$category->sortorder)); //Now, backup their questions - $status = backup_question($bf,$preferences,$category->id); + $status = $status && backup_question($bf,$preferences,$category->id); //End category - $status = fwrite ($bf,end_tag("QUESTION_CATEGORY",3,true)); + $status = $status && fwrite ($bf,end_tag("QUESTION_CATEGORY",3,true)); } //Write end tag - $status = fwrite ($bf,end_tag("QUESTION_CATEGORIES",2,true)); + $status = $status && fwrite ($bf,end_tag("QUESTION_CATEGORIES",2,true)); } return $status; @@ -127,16 +163,21 @@ // We'll fetch the questions sorted by parent so that questions with no parents // (these are the ones which could be parents themselves) are backed up first. This // is important for the recoding of the parent field during the restore process - $questions = get_records("question","category",$category,"parent ASC, id"); + // Only select questions with ids in backup_ids table + $questions = get_records_sql("SELECT q.* FROM {$CFG->prefix}backup_ids bk, {$CFG->prefix}question q ". + "WHERE q.category= $category AND ". + "bk.old_id=q.id AND ". + "bk.backup_code = {$preferences->backup_unique_code} ". + "ORDER BY parent ASC, id"); //If there are questions if ($questions) { //Write start tag - $status = fwrite ($bf,start_tag("QUESTIONS",$level,true)); + $status = $status && fwrite ($bf,start_tag("QUESTIONS",$level,true)); $counter = 0; //Iterate over each question foreach ($questions as $question) { //Start question - $status = fwrite ($bf,start_tag("QUESTION",$level + 1,true)); + $status = $status && fwrite ($bf,start_tag("QUESTION",$level + 1,true)); //Print question contents fwrite ($bf,full_tag("ID",$level + 2,false,$question->id)); fwrite ($bf,full_tag("PARENT",$level + 2,false,$question->parent)); @@ -152,10 +193,14 @@ fwrite ($bf,full_tag("STAMP",$level + 2,false,$question->stamp)); fwrite ($bf,full_tag("VERSION",$level + 2,false,$question->version)); fwrite ($bf,full_tag("HIDDEN",$level + 2,false,$question->hidden)); + fwrite ($bf,full_tag("TIMECREATED",$level + 2,false,$question->timecreated)); + fwrite ($bf,full_tag("TIMEMODIFIED",$level + 2,false,$question->timemodified)); + fwrite ($bf,full_tag("CREATEDBY",$level + 2,false,$question->createdby)); + fwrite ($bf,full_tag("MODIFIEDBY",$level + 2,false,$question->modifiedby)); // Backup question type specific data - $status = $QTYPES[$question->qtype]->backup($bf,$preferences,$question->id, $level + 2); + $status = $status && $QTYPES[$question->qtype]->backup($bf,$preferences,$question->id, $level + 2); //End question - $status = fwrite ($bf,end_tag("QUESTION",$level + 1,true)); + $status = $status && fwrite ($bf,end_tag("QUESTION",$level + 1,true)); //Do some output $counter++; if ($counter % 10 == 0) { @@ -167,7 +212,7 @@ } } //Write end tag - $status = fwrite ($bf,end_tag("QUESTIONS",$level,true)); + $status = $status && fwrite ($bf,end_tag("QUESTIONS",$level,true)); } return $status; } @@ -183,18 +228,18 @@ $answers = get_records("question_answers","question",$question,"id"); //If there are answers if ($answers) { - $status = fwrite ($bf,start_tag("ANSWERS",$level,true)); + $status = $status && fwrite ($bf,start_tag("ANSWERS",$level,true)); //Iterate over each answer foreach ($answers as $answer) { - $status = fwrite ($bf,start_tag("ANSWER",$level + 1,true)); + $status = $status && fwrite ($bf,start_tag("ANSWER",$level + 1,true)); //Print answer contents fwrite ($bf,full_tag("ID",$level + 2,false,$answer->id)); fwrite ($bf,full_tag("ANSWER_TEXT",$level + 2,false,$answer->answer)); fwrite ($bf,full_tag("FRACTION",$level + 2,false,$answer->fraction)); fwrite ($bf,full_tag("FEEDBACK",$level + 2,false,$answer->feedback)); - $status = fwrite ($bf,end_tag("ANSWER",$level + 1,true)); + $status = $status && fwrite ($bf,end_tag("ANSWER",$level + 1,true)); } - $status = fwrite ($bf,end_tag("ANSWERS",$level,true)); + $status = $status && fwrite ($bf,end_tag("ANSWERS",$level,true)); } return $status; } @@ -209,17 +254,17 @@ $numerical_units = get_records("question_numerical_units","question",$question,"id"); //If there are numericals_units if ($numerical_units) { - $status = fwrite ($bf,start_tag("NUMERICAL_UNITS",$level,true)); + $status = $status && fwrite ($bf,start_tag("NUMERICAL_UNITS",$level,true)); //Iterate over each numerical_unit foreach ($numerical_units as $numerical_unit) { - $status = fwrite ($bf,start_tag("NUMERICAL_UNIT",$level+1,true)); + $status = $status && fwrite ($bf,start_tag("NUMERICAL_UNIT",$level+1,true)); //Print numerical_unit contents fwrite ($bf,full_tag("MULTIPLIER",$level+2,false,$numerical_unit->multiplier)); fwrite ($bf,full_tag("UNIT",$level+2,false,$numerical_unit->unit)); //Now backup numerical_units - $status = fwrite ($bf,end_tag("NUMERICAL_UNIT",$level+1,true)); + $status = $status && fwrite ($bf,end_tag("NUMERICAL_UNIT",$level+1,true)); } - $status = fwrite ($bf,end_tag("NUMERICAL_UNITS",$level,true)); + $status = $status && fwrite ($bf,end_tag("NUMERICAL_UNITS",$level,true)); } return $status; @@ -251,7 +296,7 @@ fwrite ($bf,full_tag("OPTIONS",$level+2,false,$def->options)); fwrite ($bf,full_tag("ITEMCOUNT",$level+2,false,$def->itemcount)); //Now backup dataset_entries - $status = question_backup_dataset_items($bf,$preferences,$def->id,$level+2); + $status = $status && question_backup_dataset_items($bf,$preferences,$def->id,$level+2); //End dataset definition $status = $status &&fwrite ($bf,end_tag("DATASET_DEFINITION",$level+1,true)); } @@ -303,11 +348,11 @@ //If there are states if ($question_states) { //Write start tag - $status = fwrite ($bf,start_tag("STATES",$level,true)); + $status = $status && fwrite ($bf,start_tag("STATES",$level,true)); //Iterate over each state foreach ($question_states as $state) { //Start state - $status = fwrite ($bf,start_tag("STATE",$level + 1,true)); + $status = $status && fwrite ($bf,start_tag("STATE",$level + 1,true)); //Print state contents fwrite ($bf,full_tag("ID",$level + 2,false,$state->id)); fwrite ($bf,full_tag("QUESTION",$level + 2,false,$state->question)); @@ -320,10 +365,10 @@ fwrite ($bf,full_tag("RAW_GRADE",$level + 2,false,$state->raw_grade)); fwrite ($bf,full_tag("PENALTY",$level + 2,false,$state->penalty)); //End state - $status = fwrite ($bf,end_tag("STATE",$level + 1,true)); + $status = $status && fwrite ($bf,end_tag("STATE",$level + 1,true)); } //Write end tag - $status = fwrite ($bf,end_tag("STATES",$level,true)); + $status = $status && fwrite ($bf,end_tag("STATES",$level,true)); } } @@ -337,23 +382,23 @@ //If there are sessions if ($question_sessions) { //Write start tag (the funny name 'newest states' has historical reasons) - $status = fwrite ($bf,start_tag("NEWEST_STATES",$level,true)); + $status = $status && fwrite ($bf,start_tag("NEWEST_STATES",$level,true)); //Iterate over each newest_state foreach ($question_sessions as $newest_state) { //Start newest_state - $status = fwrite ($bf,start_tag("NEWEST_STATE",$level + 1,true)); + $status = $status && fwrite ($bf,start_tag("NEWEST_STATE",$level + 1,true)); //Print newest_state contents fwrite ($bf,full_tag("ID",$level + 2,false,$newest_state->id)); fwrite ($bf,full_tag("QUESTIONID",$level + 2,false,$newest_state->questionid)); fwrite ($bf,full_tag("NEWEST",$level + 2,false,$newest_state->newest)); fwrite ($bf,full_tag("NEWGRADED",$level + 2,false,$newest_state->newgraded)); fwrite ($bf,full_tag("SUMPENALTY",$level + 2,false,$newest_state->sumpenalty)); - fwrite ($bf,full_tag("MANUALCOMMENT",$level + 2,false,$newest_state->manualcomment)); + fwrite ($bf,full_tag("MANUALCOMMENT",$level + 2,false,$newest_state->manualcomment)); //End newest_state - $status = fwrite ($bf,end_tag("NEWEST_STATE",$level + 1,true)); + $status = $status && fwrite ($bf,end_tag("NEWEST_STATE",$level + 1,true)); } //Write end tag - $status = fwrite ($bf,end_tag("NEWEST_STATES",$level,true)); + $status = $status && fwrite ($bf,end_tag("NEWEST_STATES",$level,true)); } return $status; } @@ -373,21 +418,44 @@ global $CFG; - return get_records_sql ("SELECT q.id, q.category - FROM {$CFG->prefix}backup_ids a, - {$CFG->prefix}question q - WHERE a.backup_code = '$backup_unique_code' AND - q.category = a.old_id AND - a.table_name = 'question_categories'"); + return get_records_sql ("SELECT old_id, backup_code + FROM {$CFG->prefix}backup_ids + WHERE backup_code = '$backup_unique_code' AND + table_name = 'question'"); } //Delete category ids from backup_ids table - function delete_category_ids ($backup_unique_code) { + function delete_ids ($backup_unique_code, $tablename) { global $CFG; - $status = true; $status = execute_sql("DELETE FROM {$CFG->prefix}backup_ids - WHERE backup_code = '$backup_unique_code'",false); + WHERE backup_code = '$backup_unique_code' AND table_name = '$tablename'",false); + return $status; + } + function question_insert_site_file_names($course, $backup_unique_code){ + global $QTYPES, $CFG; + $status = true; + $questionids = question_ids_by_backup ($backup_unique_code); + $urls = array(); + if ($questionids){ + foreach ($questionids as $question_bk){ + $question = get_record('question', 'id', $question_bk->old_id); + $QTYPES[$question->qtype]->get_question_options(&$question); + $urls = array_merge_recursive($urls, $QTYPES[$question->qtype]->find_file_links($question, SITEID)); + } + } + ksort($urls); + foreach (array_keys($urls) as $url){ + if (file_exists($CFG->dataroot.'/'.SITEID.'/'.$url)){ + $inserturl = new object(); + $inserturl->backup_code = $backup_unique_code; + $inserturl->file_type = 'site'; + $url = clean_param($url, PARAM_PATH); + $inserturl->path = addslashes($url); + $status = $status && insert_record('backup_files', $inserturl); + } else { + notify(get_string('linkedfiledoesntexist', 'question', $url)); + } + } return $status; } - ?> diff --git a/question/category.php b/question/category.php index 190f5d0ae7..fea7f1cda9 100644 --- a/question/category.php +++ b/question/category.php @@ -9,13 +9,12 @@ */ require_once("../config.php"); - require_once("editlib.php"); - require_once("category_class.php"); + require_once($CFG->dirroot."/question/editlib.php"); + require_once($CFG->dirroot."/question/category_class.php"); - list($thispageurl, $courseid, $cmid, $cm, $module, $pagevars) = question_edit_setup(); - + list($thispageurl, $contexts, $cmid, $cm, $module, $pagevars) = question_edit_setup('categories'); // get values from form for actions on this page $param = new stdClass(); @@ -23,100 +22,108 @@ $param->moveup = optional_param('moveup', 0, PARAM_INT); $param->movedown = optional_param('movedown', 0, PARAM_INT); + $param->moveupcontext = optional_param('moveupcontext', 0, PARAM_INT); + $param->movedowncontext = optional_param('movedowncontext', 0, PARAM_INT); + $param->tocontext = optional_param('tocontext', 0, PARAM_INT); $param->left = optional_param('left', 0, PARAM_INT); $param->right = optional_param('right', 0, PARAM_INT); - $param->hide = optional_param('hide', 0, PARAM_INT); $param->delete = optional_param('delete', 0, PARAM_INT); $param->confirm = optional_param('confirm', 0, PARAM_INT); $param->cancel = optional_param('cancel', '', PARAM_ALPHA); $param->move = optional_param('move', 0, PARAM_INT); $param->moveto = optional_param('moveto', 0, PARAM_INT); - $param->publish = optional_param('publish', 0, PARAM_INT); - $param->addcategory = optional_param('addcategory', '', PARAM_NOTAGS); $param->edit = optional_param('edit', 0, PARAM_INT); - $param->updateid = optional_param('updateid', 0, PARAM_INT); - - if (! $course = get_record("course", "id", $courseid)) { - error("Course ID is incorrect"); - } - - $context = get_context_instance(CONTEXT_COURSE, $courseid); - require_capability('moodle/question:managecategory', $context); - - $qcobject = new question_category_object($pagevars['cpage'], $thispageurl); + $qcobject = new question_category_object($pagevars['cpage'], $thispageurl, $contexts->having_one_edit_tab_cap('categories'), $param->edit, $pagevars['cat'], $param->delete, + $contexts->having_cap('moodle/question:add')); $streditingcategories = get_string('editcategories', 'quiz'); - if ($qcobject->editlist->process_actions($param->left, $param->right, $param->moveup, $param->movedown)) { - //processing of these actions is handled in the method and page redirects. - } else if ($cm!==null) { + if ($param->left || $param->right || $param->moveup || $param->movedown|| $param->moveupcontext || $param->movedowncontext){ + confirm_sesskey(); + foreach ($qcobject->editlists as $list){ + //processing of these actions is handled in the method where appropriate and page redirects. + $list->process_actions($param->left, $param->right, $param->moveup, $param->movedown, + $param->moveupcontext, $param->movedowncontext, $param->tocontext); + } + } + if ($param->delete && ($questionstomove = count_records("question", "category", $param->delete))){ + if (!$category = get_record("question_categories", "id", $param->delete)) { // security + error("No such category {$param->delete}!", $thispageurl->out()); + } + $categorycontext = get_context_instance_by_id($category->contextid); + $qcobject->moveform = new question_move_form($thispageurl, + array('contexts'=>array($categorycontext), 'currentcat'=>$param->delete)); + if ($qcobject->moveform->is_cancelled()){ + redirect($thispageurl->out()); + } elseif ($formdata = $qcobject->moveform->get_data()) { + /// 'confirm' is the category to move existing questions to + $qcobject->move_questions_and_delete_category($formdata->delete, $formdata->category); + redirect($thispageurl->out()); + } + } else { + $questionstomove = 0; + } + if ($qcobject->catform->is_cancelled()){ + redirect($thispageurl->out()); + }elseif ($catformdata = $qcobject->catform->get_data()) { + if (!$catformdata->id) {//new category + $qcobject->add_category($catformdata->parent, $catformdata->name, $catformdata->info); + } else { + $qcobject->update_category($catformdata->id, $catformdata->parent, $catformdata->name, $catformdata->info); + } + redirect($thispageurl->out()); + } elseif ((!empty($param->delete) and (!$questionstomove) and confirm_sesskey())) { + $qcobject->delete_category($param->delete);//delete the category now no questions to move + } + $crumbs = array(); + if ($cm!==null) { // Page header - $strupdatemodule = has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $course->id)) - ? update_module_button($cm->id, $course->id, get_string('modulename', $cm->modname)) + $strupdatemodule = has_capability('moodle/course:manageactivities', $contexts->lowest()) + ? update_module_button($cm->id, $COURSE->id, get_string('modulename', $cm->modname)) : ""; $navlinks = array(); - $navlinks[] = array('name' => get_string('modulenameplural', $cm->modname), - 'link' => "$CFG->wwwroot/mod/{$cm->modname}/index.php?id=$course->id", + $navlinks[] = array('name' => get_string('modulenameplural', $cm->modname), + 'link' => "$CFG->wwwroot/mod/{$cm->modname}/index.php?id=$course->id", 'type' => 'activity'); - $navlinks[] = array('name' => format_string($module->name), + $navlinks[] = array('name' => format_string($module->name), 'link' => "$CFG->wwwroot/mod/{$cm->modname}/view.php?cmid={$cm->id}", 'type' => 'title'); + } else { + // Print basic page layout. + $strupdatemodule = ''; + $navlinks = array(); + } + + if (!$param->edit){ $navlinks[] = array('name' => $streditingcategories, 'link' => '', 'type' => 'title'); - $navigation = build_navigation($navlinks); - print_header_simple($streditingcategories, '', $navigation, "", "", true, $strupdatemodule); + } else { + $navlinks[] = array('name' => $streditingcategories, 'link' => $thispageurl->out(), 'type' => 'title'); + $navlinks[] = array('name' => get_string('editingcategory', 'question'), 'link' => '', 'type' => 'title'); + } + $navigation = build_navigation($navlinks); + print_header_simple($streditingcategories, '', $navigation, "", "", true, $strupdatemodule); + // print tabs + if ($cm!==null) { $currenttab = 'edit'; $mode = 'categories'; ${$cm->modname} = $module; include($CFG->dirroot."/mod/{$cm->modname}/tabs.php"); } else { - // Print basic page layout. - $navlinks = array(); - $navlinks[] = array('name' => $streditingcategories, 'link' => '', 'type' => 'title'); - $navigation = build_navigation($navlinks); - - print_header_simple($streditingcategories, '', $navigation); - - // print tabs $currenttab = 'categories'; + $context = $contexts->lowest(); include('tabs.php'); } - // Process actions. - if (isset($_REQUEST['sesskey']) and confirm_sesskey()) { // sesskey must be ok - if (!empty($param->delete) and empty($param->cancel)) { - if (!empty($param->confirm)) { - /// 'confirm' is the category to move existing questions to - $qcobject->delete_category($param->delete, $param->confirm); - } else { - $qcobject->delete_category($param->delete); - } - } else if (!empty($param->hide)) { - $qcobject->publish_category(false, $param->hide); - } else if (!empty($param->publish)) { - $qcobject->publish_category(true, $param->publish); - } else if (!empty($param->addcategory)) { - $param->newparent = required_param('newparent',PARAM_INT); - $param->newcategory = required_param('newcategory',PARAM_NOTAGS); - $param->newinfo = required_param('newinfo',PARAM_NOTAGS); - $param->newpublish = required_param('newpublish',PARAM_INT); - $qcobject->add_category($param->newparent, $param->newcategory, $param->newinfo, - $param->newpublish, $course->id); - } else if (!empty($param->edit)) { - $qcobject->edit_single_category($param->edit, $pagevars['cpage']); - } else if (!empty($param->updateid)) { - $param->updateparent = required_param('updateparent',PARAM_INT); - $param->updatename = required_param('updatename',PARAM_NOTAGS); - $param->updateinfo = required_param('updateinfo',PARAM_NOTAGS); - $param->updatepublish = required_param('updatepublish',PARAM_INT); - $qcobject->update_category($param->updateid, $param->updateparent, $param->updatename, - $param->updateinfo, $param->updatepublish, $course->id); - } + // display UI + if (!empty($param->edit)) { + $qcobject->edit_single_category($param->edit); + } else if ($questionstomove){ + $qcobject->display_move_form($questionstomove, $category); + } else { + // display the user interface + $qcobject->display_user_interface(); } - - // display the user interface - $qcobject->display_user_interface(); - - print_footer($course); + print_footer($COURSE); ?> diff --git a/question/category_class.php b/question/category_class.php index d6a9864e1d..07b457d4df 100644 --- a/question/category_class.php +++ b/question/category_class.php @@ -8,64 +8,91 @@ */ // number of categories to display on page -define("QUESTION_PAGE_LENGTH", 20); +define("QUESTION_PAGE_LENGTH", 25); require_once("$CFG->libdir/listlib.php"); +require_once("$CFG->dirroot/question/category_form.php"); +require_once('move_form.php'); class question_category_list extends moodle_list { var $table = "question_categories"; var $listitemclassname = 'question_category_list_item'; - + /** + * @var reference to list displayed below this one. + */ + var $nextlist = null; + /** + * @var reference to list displayed above this one. + */ + var $lastlist = null; + + var $context = null; + + function question_category_list($type='ul', $attributes='', $editable = false, $pageurl=null, $page = 0, $pageparamname = 'page', $itemsperpage = 20, $context = null){ + parent::moodle_list('ul', '', $editable, $pageurl, $page, 'cpage', $itemsperpage); + $this->context = $context; + } function get_records() { - global $COURSE, $CFG; - $categories = get_records($this->table, 'course', "{$COURSE->id}", $this->sortby); - - $catids = array_keys($categories); - $select = "WHERE category IN ('".join("', '", $catids)."') AND hidden='0' AND parent='0'"; - $questioncounts = get_records_sql_menu('SELECT category, COUNT(*) FROM '. $CFG->prefix . 'question' .' '. $select.' GROUP BY category'); - foreach ($categories as $categoryid => $category){ - if (isset($questioncounts[$categoryid])){ - $categories[$categoryid]->questioncount = $questioncounts[$categoryid]; - } else { - $categories[$categoryid]->questioncount = 0; + $this->records = get_categories_for_contexts($this->context->id, $this->sortby); + } + function process_actions($left, $right, $moveup, $movedown, $moveupcontext, $movedowncontext, $tocontext){ + global $CFG; + //parent::procces_actions redirects after any action + parent::process_actions($left, $right, $moveup, $movedown); + if ($tocontext == $this->context->id){ + //only called on toplevel list + if ($moveupcontext){ + $cattomove = $moveupcontext; + $totop = 0; + } elseif ($movedowncontext){ + $cattomove = $movedowncontext; + $totop = 1; } + $toparent = "0,{$this->context->id}"; + redirect($CFG->wwwroot.'/question/contextmove.php?'. + $this->pageurl->get_query_string(compact('cattomove', 'totop', 'toparent'))); } - $this->records = $categories; } } class question_category_list_item extends list_item { + function set_icon_html($first, $last, &$lastitem){ + global $CFG; + $category = $this->item; + $this->icons['edit']= $this->image_icon(get_string('editthiscategory'), + "{$CFG->wwwroot}/question/category.php?".$this->parentlist->pageurl->get_query_string(array('edit'=>$category->id)), 'edit'); + parent::set_icon_html($first, $last, $lastitem); + $toplevel = ($this->parentlist->parentitem === null);//this is a top level item + if (($this->parentlist->nextlist !== null) && $last && $toplevel && (count($this->parentlist->items)>1)){ + $this->icons['down'] = $this->image_icon(get_string('shareincontext', 'question', print_context_name($this->parentlist->nextlist->context)), + $this->parentlist->pageurl->out_action(array('movedowncontext'=>$this->id, 'tocontext'=>$this->parentlist->nextlist->context->id)), 'down'); + } + if (($this->parentlist->lastlist !== null) && $first && $toplevel && (count($this->parentlist->items)>1)){ + $this->icons['up'] = $this->image_icon(get_string('shareincontext', 'question', print_context_name($this->parentlist->lastlist->context)), + $this->parentlist->pageurl->out_action(array('moveupcontext'=>$this->id, 'tocontext'=>$this->parentlist->lastlist->context->id)), 'up'); + } + } function item_html($extraargs = array()){ global $CFG; $pixpath = $CFG->pixpath; $str = $extraargs['str']; $category = $this->item; - $linkcss = $category->publish ? ' class="published" ' : ' class="unpublished" '; - + $editqestions = get_string('editquestions', 'quiz'); /// Each section adds html to be displayed as part of this list item - - - $item = ' - ' .$str->edit. ' ' . $category->name . '('.$category->questioncount.')'. ''; + $questionbankurl = "{$CFG->wwwroot}/question/edit.php?". + $this->parentlist->pageurl->get_query_string(array('category'=>"$category->id,$category->contextid")); + $catediturl = $this->parentlist->pageurl->out(false, array('edit'=>$this->id)); + $item = "edit}\" href=\"$catediturl\">".$category->name ." ".'('.$category->questioncount.')'; $item .= ' '. $category->info; - if (!empty($category->publish)) { - $item .= ' - ' .$str->hide. ' '; - } else { - $item .= ' - ' .$str->publish. ' '; - } - - if ($category->id != $extraargs['defaultcategory']->id) { + if (count($this->parentlist->records)!=1){ // don't allow delete if this is the last category in this context. $item .= ' ' .$str->delete. ' '; } @@ -88,29 +115,30 @@ class question_category_object { var $str; var $pixpath; /** - * Nested list to display categories. + * Nested lists to display categories. * - * @var question_category_list + * @var array */ - var $editlist; + var $editlists = array(); var $newtable; var $tab; var $tabsize = 3; - var $categories; - var $categorystrings; - var $defaultcategory; //------------------------------------------------------ /** * @var moodle_url Object representing url for this page */ var $pageurl; + /** + * @var question_category_edit_form Object representing form for adding / editing categories. + */ + var $catform; /** * Constructor * * Gets necessary strings and sets relevant path information */ - function question_category_object($page, $pageurl) { + function question_category_object($page, $pageurl, $contexts, $currentcat, $defaultcategory, $todelete, $addcontexts) { global $CFG, $COURSE; $this->tab = str_repeat(' ', $this->tabsize); @@ -138,14 +166,38 @@ class question_category_object { $this->str->page = get_string('page'); $this->pixpath = $CFG->pixpath; - $this->editlist = new question_category_list('ul', '', true, $pageurl, $page, 'cpage', QUESTION_PAGE_LENGTH); - $this->pageurl = $pageurl; - - $this->initialize(); + + $this->initialize($page, $contexts, $currentcat, $defaultcategory, $todelete, $addcontexts); } - + + + + /** + * Initializes this classes general category-related variables + */ + function initialize($page, $contexts, $currentcat, $defaultcategory, $todelete, $addcontexts) { + $lastlist = null; + foreach ($contexts as $context){ + $this->editlists[$context->id] = new question_category_list('ul', '', true, $this->pageurl, $page, 'cpage', QUESTION_PAGE_LENGTH, $context); + $this->editlists[$context->id]->lastlist =& $lastlist; + if ($lastlist!== null){ + $lastlist->nextlist =& $this->editlists[$context->id]; + } + $lastlist =& $this->editlists[$context->id]; + } + + $count = 1; + $paged = false; + foreach ($this->editlists as $key => $list){ + list($paged, $count) = $list->list_from_records($paged, $count); + } + $this->catform = new question_category_edit_form($this->pageurl, compact('contexts', 'currentcat')); + if (!$currentcat){ + $this->catform->set_data(array('parent'=>$defaultcategory)); + } + } /** * Displays the user interface * @@ -153,91 +205,21 @@ class question_category_object { function display_user_interface() { /// Interface for editing existing categories - print_heading_with_help($this->str->editcategories, 'categories', 'quiz'); - $this->output_edit_list(); + $this->output_edit_lists(); echo '
'; /// Interface for adding a new category: - print_heading_with_help($this->str->addcategory, 'categories_edit', 'quiz'); $this->output_new_table(); echo '
'; } - - /** - * Initializes this classes general category-related variables - */ - function initialize() { - global $COURSE, $CFG; - - /// Get the existing categories - if (!$this->defaultcategory = get_default_question_category($COURSE->id)) { - error("Error: Could not find or make a category!"); - } - - $this->editlist->list_from_records(); - - $this->categories = $this->editlist->records; - - // create the array of id=>full_name strings - $this->categorystrings = $this->expanded_category_strings($this->categories); - - - } - - /** * Outputs a table to allow entry of a new category */ function output_new_table() { - global $USER, $COURSE; - $publishoptions[0] = get_string("no"); - $publishoptions[1] = get_string("yes"); - - $this->newtable->head = array ($this->str->parent, $this->str->category, $this->str->categoryinfo, $this->str->publish, $this->str->action); - $this->newtable->width = '200'; - $this->newtable->data[] = array(); - $this->newtable->tablealign = 'center'; - - /// Each section below adds a data cell to the table row - - - $viableparents[0] = $this->str->top; - $viableparents = $viableparents + $this->categorystrings; - $this->newtable->align['parent'] = "left"; - $this->newtable->wrap['parent'] = "nowrap"; - $row['parent'] = choose_from_menu ($viableparents, "newparent", $this->str->top, "", "", "", true); - - $this->newtable->align['category'] = "left"; - $this->newtable->wrap['category'] = "nowrap"; - $row['category'] = ''; - - $this->newtable->align['info'] = "left"; - $this->newtable->wrap['info'] = "nowrap"; - $row['info'] = ''; - - $this->newtable->align['publish'] = "left"; - $this->newtable->wrap['publish'] = "nowrap"; - $row['publish'] = choose_from_menu ($publishoptions, "newpublish", "", "", "", "", true); - - $this->newtable->align['action'] = "left"; - $this->newtable->wrap['action'] = "nowrap"; - $row['action'] = ''; - - - $this->newtable->data[] = $row; - - // wrap the table in a form and output it - echo ''; - echo '
'; - echo "sesskey\" />"; - echo $this->pageurl->hidden_params_out(); - echo ''; - print_table($this->newtable); - echo '
'; - echo ''; + $this->catform->display(); } @@ -247,14 +229,19 @@ class question_category_object { * $this->initialize() must have already been called * */ - function output_edit_list() { - print_box_start('boxwidthwide boxaligncenter generalbox'); - echo $this->editlist->to_html(0, array('str'=>$this->str, - 'defaultcategory' => $this->defaultcategory)); - print_box_end(); - echo $this->editlist->display_page_numbers(); - - } + function output_edit_lists() { + print_heading_with_help(get_string('editcategories', 'quiz'), 'categories', 'quiz'); + foreach ($this->editlists as $context => $list){ + $listhtml = $list->to_html(0, array('str'=>$this->str)); + if ($listhtml){ + print_heading(get_string('questioncatsfor', 'question', print_context_name(get_context_instance_by_id($context))), '', 3); + print_box_start('boxwidthwide boxaligncenter generalbox'); + echo $listhtml; + print_box_end(); + } + } + echo $list->display_page_numbers(); + } @@ -279,101 +266,18 @@ class question_category_object { function edit_single_category($categoryid) { /// Interface for adding a new category - global $CFG, $USER, $COURSE; - + global $COURSE; /// Interface for editing existing categories if ($category = get_record("question_categories", "id", $categoryid)) { - print_heading_with_help($this->str->edit, 'categories_edit', 'quiz'); - $this->output_edit_single_table($category); - echo '
'; - print_single_button($CFG->wwwroot . '/question/category.php', - $this->pageurl->params, $this->str->cancel); - echo '
'; - print_footer($COURSE); - exit; - } else { - error("Category $categoryid not found", "category.php?id={$COURSE->id}"); - } - } - /** - * Outputs a table to allow editing of an existing category - * - * @param object category - * @param int page current page - */ - function output_edit_single_table($category) { - global $USER; - $publishoptions[0] = get_string("no"); - $publishoptions[1] = get_string("yes"); - $strupdate = get_string('update'); - - $edittable = new stdClass; - - $edittable->head = array ($this->str->parent, $this->str->category, $this->str->categoryinfo, $this->str->publish, $this->str->action); - $edittable->width = 200; - $edittable->data[] = array(); - $edittable->tablealign = 'center'; - - /// Each section below adds a data cell to the table row - - $viableparents = $this->categorystrings; - $this->set_viable_parents($viableparents, $category); - $viableparents = array(0=>$this->str->top) + $viableparents; - $edittable->align['parent'] = "left"; - $edittable->wrap['parent'] = "nowrap"; - $row['parent'] = choose_from_menu ($viableparents, "updateparent", "{$category->parent}", "", "", "", true); - - $edittable->align['category'] = "left"; - $edittable->wrap['category'] = "nowrap"; - $row['category'] = ''; - - $edittable->align['info'] = "left"; - $edittable->wrap['info'] = "nowrap"; - $row['info'] = ''; - - $edittable->align['publish'] = "left"; - $edittable->wrap['publish'] = "nowrap"; - $selected = (boolean)$category->publish ? 1 : 0; - $row['publish'] = choose_from_menu ($publishoptions, "updatepublish", $selected, "", "", "", true); - - $edittable->align['action'] = "left"; - $edittable->wrap['action'] = "nowrap"; - $row['action'] = ''; - - $edittable->data[] = $row; - - // wrap the table in a form and output it - echo '

'; - echo '
'; - echo ''; - echo $this->pageurl->hidden_params_out(); - echo ''; - print_table($edittable); - echo '
'; - echo '

'; - } - - /** - * Creates an array of "full-path" category strings - * Structure: - * key => string - * where key is the category id, and string contains the name of all ancestors as well as the particular category name - * E.g. '123'=>'Language / English / Grammar / Modal Verbs" - * - * @param array $categories an array containing categories arranged in a tree structure - */ - function expanded_category_strings($categories, $parent=null) { - $prefix = is_null($parent) ? '' : "$parent / "; - $categorystrings = array(); - foreach ($categories as $key => $category) { - $expandedname = "$prefix$category->name"; - $categorystrings[$key] = $expandedname; - if (isset($category->children)) { - $categorystrings = $categorystrings + $this->expanded_category_strings($category->children, $expandedname); - } + $category->parent = "$category->parent,$category->contextid"; + $category->submitbutton = get_string('savechanges'); + $category->categoryheader = $this->str->edit; + $this->catform->set_data($category); + $this->catform->display(); + } else { + error("Category $categoryid not found"); } - return $categorystrings; } @@ -418,55 +322,16 @@ class question_category_object { * Deletes an existing question category * * @param int deletecat id of category to delete - * @param int destcategoryid id of category which will inherit the orphans of deletecat */ - function delete_category($deletecat, $destcategoryid = null) { - global $USER, $COURSE; - - if (!$category = get_record("question_categories", "id", $deletecat)) { // security - error("No such category $deletecat!", "category.php?id={$COURSE->id}"); - } - - if (!is_null($destcategoryid)) { // Need to move some questions before deleting the category - if (!$category2 = get_record("question_categories", "id", $destcategoryid)) { // security - error("No such category $destcategoryid!", "category.php?id={$COURSE->id}"); - } - if (! set_field('question', 'category', $destcategoryid, 'category', $deletecat)) { - error("Error while moving questions from category '" . format_string($category->name) . "' to '$category2->name'", "category.php?id={$COURSE->id}"); - } - - } else { - // todo: delete any hidden questions that are not actually in use any more - if ($count = count_records("question", "category", $category->id)) { - $vars = new stdClass; - $vars->name = $category->name; - $vars->count = $count; - print_simple_box(get_string("categorymove", "quiz", $vars), "center"); - $this->initialize(); - $categorystrings = $this->categorystrings; - unset ($categorystrings[$category->id]); - echo "

"; - echo '
'; - echo "sesskey\" />"; - echo "id}\" />"; - echo "id\" />"; - choose_from_menu($categorystrings, "confirm", "", ""); - echo ""; - echo "str->cancel}\" />"; - echo '
'; - echo "

"; - print_footer($COURSE); - exit; - } + function delete_category($categoryid) { + global $CFG; + question_can_delete_cat($categoryid); + if (!$category = get_record("question_categories", "id", $categoryid)) { // security + error("No such category $cat!", $this->pageurl->out()); } - /// Send the children categories to live with their grandparent - if ($childcats = get_records("question_categories", "parent", $category->id)) { - foreach ($childcats as $childcat) { - if (! set_field("question_categories", "parent", $category->parent, "id", $childcat->id)) { - error("Could not update a child category!", "category.php?id={$COURSE->id}"); - } - } + if (!set_field("question_categories", "parent", $category->parent, "parent", $category->id)) { + error("Could not update a child category!", $this->pageurl->out()); } /// Finally delete the category itself @@ -475,64 +340,54 @@ class question_category_object { redirect($this->pageurl->out());//always redirect after successful action } } + function move_questions_and_delete_category($oldcat, $newcat){ + question_can_delete_cat($oldcat); + $this->move_questions($oldcat, $newcat); + $this->delete_category($oldcat); + } + function display_move_form($questionsincategory, $category){ + $vars = new stdClass; + $vars->name = $category->name; + $vars->count = $questionsincategory; + print_simple_box(get_string("categorymove", "quiz", $vars), "center"); + $this->moveform->display(); + } - - /** - * Changes the published status of a category - * - * @param boolean publish - * @param int categoryid - */ - function publish_category($publish, $categoryid) { - /// Hide or publish a category - - $publish = ($publish == false) ? 0 : 1; - $tempcat = get_record("question_categories", "id", $categoryid); - if ($tempcat) { - if (! set_field("question_categories", "publish", $publish, "id", $tempcat->id)) { - notify("Could not update that category!"); - } else { - redirect($this->pageurl->out());//always redirect after successful action - } - + function move_questions($oldcat, $newcat){ + if (!set_field('question', 'category', $newcat, 'category', $oldcat)) { + error("Error while moving questions from category '$oldcat' to '$newcat'", $this->pageurl->out()); } } /** * Creates a new category with given params * - * @param int $newparent id of the parent category - * @param string $newcategory the name for the new category - * @param string $newinfo the info field for the new category - * @param int $newpublish whether to publish the category - * @param int $newcourse the id of the associated course */ - function add_category($newparent, $newcategory, $newinfo, $newpublish, $newcourse) { + function add_category($newparent, $newcategory, $newinfo) { if (empty($newcategory)) { - notify(get_string('categorynamecantbeblank', 'quiz'), 'notifyproblem'); - return false; + error(get_string('categorynamecantbeblank', 'quiz')); } + list($parentid, $contextid) = explode(',', $newparent); + //moodle_form makes sure select element output is legal no need for further cleaning + require_capability('moodle/question:managecategory', get_context_instance_by_id($contextid)); - if ($newparent) { - // first check that the parent category is in the correct course - if(!(get_field('question_categories', 'course', 'id', $newparent) == $newcourse)) { - return false; + if ($parentid) { + if(!(get_field('question_categories', 'contextid', 'id', $parentid) == $contextid)) { + error("Could not insert the new question category '$newcategory' illegal contextid '$contextid'."); } } - $cat = NULL; - $cat->parent = $newparent; + $cat = new object(); + $cat->parent = $parentid; + $cat->contextid = $contextid; $cat->name = $newcategory; $cat->info = $newinfo; - $cat->publish = $newpublish; - $cat->course = $newcourse; $cat->sortorder = 999; $cat->stamp = make_unique_id_code(); if (!insert_record("question_categories", $cat)) { - error("Could not insert the new question category '$newcategory'", "category.php?id={$newcourse}"); + error("Could not insert the new question category '$newcategory'"); } else { - notify(get_string("categoryadded", "quiz", $newcategory), 'notifysuccess'); redirect($this->pageurl->out());//always redirect after successful action } } @@ -540,32 +395,75 @@ class question_category_object { /** * Updates an existing category with given params * - * @param int updateid - * @param int updateparent - * @param string updatename - * @param string updateinfo - * @param int updatepublish - * @param int courseid the id of the associated course */ - function update_category($updateid, $updateparent, $updatename, $updateinfo, $updatepublish, $courseid) { - if (empty($updatename)) { - notify(get_string('categorynamecantbeblank', 'quiz'), 'notifyproblem'); - return false; + function update_category($updateid, $newparent, $newname, $newinfo) { + global $CFG, $QTYPES; + if (empty($newname)) { + error(get_string('categorynamecantbeblank', 'quiz')); } + list($parentid, $tocontextid) = explode(',', $newparent); + + $oldcat = get_record('question_categories', 'id', $updateid); + $fromcontext = get_context_instance_by_id($oldcat->contextid); + require_capability('moodle/question:managecategory', $fromcontext); + if ($oldcat->contextid == $tocontextid){ + $tocontext = get_context_instance_by_id($tocontextid); + require_capability('moodle/question:managecategory', $tocontext); + } $cat = NULL; $cat->id = $updateid; - $cat->parent = $updateparent; - $cat->name = $updatename; - $cat->info = $updateinfo; - $cat->publish = $updatepublish; - if (!update_record("question_categories", $cat)) { - error("Could not update the category '$updatename'", "category.php?id={$courseid}"); + $cat->name = $newname; + $cat->info = $newinfo; + //never move category where it is the default + if (1 != count_records_sql("SELECT count(*) FROM {$CFG->prefix}question_categories as c1, {$CFG->prefix}question_categories as c2 WHERE c2.id = $updateid AND c1.contextid = c2.contextid")){ + if ($oldcat->contextid == $tocontextid){ // not moving contexts + $cat->parent = $parentid; + if (!update_record("question_categories", $cat)) { + error("Could not update the category '$newname'", $this->pageurl->out()); + } else { + redirect($this->pageurl->out()); + } + } else { + if (!update_record("question_categories", $cat)) { + error("Could not update the category '$newname'", $this->pageurl->out()); + } else { + redirect($CFG->wwwroot.'/question/contextmove.php?'. + $this->pageurl->get_query_string(array('cattomove' => $updateid, + 'toparent'=>$newparent))); + } + } } else { - notify(get_string("categoryupdated", 'quiz'), 'notifysuccess'); - redirect($this->pageurl->out()); + error("Cannot move the category '$newname'. It is the last in this context.", $this->pageurl->out()); } } + + function move_question_from_cat_confirm($fromcat, $fromcourse, $tocat=null, $question=null){ + global $QTYPES; + if (!$question){ + $questions[] = $question; + } else { + $questions = get_records('question', 'category', $tocat->id); + } + $urls = array(); + foreach ($questions as $question){ + $urls = array_merge($urls, $QTYPES[$question->qtype]->find_file_links_in_question($question)); + } + if ($fromcourse){ + $append = 'tocourse'; + } else { + $append = 'tosite'; + } + if ($tocat){ + echo '

'.get_string('needtomovethesefilesincat','question').'

'; + } else { + echo '

'.get_string('needtomovethesefilesinquestion','question').'

'; + } + } + + + + } ?> diff --git a/question/edit.php b/question/edit.php index 9edd5cd7ff..08949243fe 100644 --- a/question/edit.php +++ b/question/edit.php @@ -14,18 +14,15 @@ require_once("../config.php"); require_once("editlib.php"); - list($thispageurl, $courseid, $cmid, $cm, $module, $pagevars) = question_edit_setup(); - - if (! $course = get_record("course", "id", $courseid)) { - error("This course doesn't exist"); - } - $context = get_context_instance(CONTEXT_COURSE, $courseid); - + list($thispageurl, $contexts, $cmid, $cm, $module, $pagevars) = question_edit_setup('questions'); + + question_showbank_actions($thispageurl, $cm); + $context = $contexts->lowest(); $streditingquestions = get_string('editquestions', "quiz"); if ($cm!==null) { - $strupdatemodule = has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $course->id)) - ? update_module_button($cm->id, $course->id, get_string('modulename', $cm->modname)) + $strupdatemodule = has_capability('moodle/course:manageactivities', $contexts->lowest()) + ? update_module_button($cm->id, $COURSE->id, get_string('modulename', $cm->modname)) : ""; $navlinks = array(); $navlinks[] = array('name' => get_string('modulenameplural', $cm->modname), 'link' => "$CFG->wwwroot/mod/{$cm->modname}/index.php?id=$course->id", 'type' => 'activity'); @@ -43,23 +40,23 @@ $navlinks = array(); $navlinks[] = array('name' => $streditingquestions, 'link' => '', 'type' => 'title'); $navigation = build_navigation($navlinks); - + print_header_simple($streditingquestions, '', $navigation); - + // print tabs $currenttab = 'questions'; include('tabs.php'); } - - + + echo ''; echo ''; echo '
'; - question_showbank($thispageurl, $cm, $pagevars['qpage'], $pagevars['qperpage'], $pagevars['qsortorder'], $pagevars['qsortorderdecoded'], + question_showbank('questions', $contexts, $thispageurl, $cm, $pagevars['qpage'], $pagevars['qperpage'], $pagevars['qsortorder'], $pagevars['qsortorderdecoded'], $pagevars['cat'], $pagevars['recurse'], $pagevars['showhidden'], $pagevars['showquestiontext']); echo '
'; - print_footer($course); + print_footer($COURSE); ?> diff --git a/question/editlib.php b/question/editlib.php index 9f40765838..abead698da 100644 --- a/question/editlib.php +++ b/question/editlib.php @@ -2,8 +2,6 @@ /** * Functions used to show question editing interface * - * TODO: currently the function question_list still provides controls specific - * to the quiz module. This needs to be generalised. * * @author Martin Dougiamas and many others. This has recently been extensively * rewritten by members of the Serving Mathematics project @@ -29,7 +27,7 @@ function get_module_from_cmid($cmid){ } $modrec->instance = $modrec->id; $modrec->cmid = $cmrec->id; - + return array($modrec, $cmrec); } /** @@ -78,53 +76,70 @@ function get_questions_category( $category, $noparent=false, $recurse=true, $exp } /** -* Gets the default category in a course +* Gets the default category in the most specific context. +* If no categories exist yet then default ones are created in all contexts. * -* It returns the first category with no parent category. If no categories -* exist yet then one is created. -* @return object The default category -* @param integer $courseid The id of the course whose default category is wanted +* @param array $contexts The context objects for this context and all parent contexts. +* @return object The default category - the category in the course context */ -function get_default_question_category($courseid) { +function question_make_default_categories($contexts) { // If it already exists, just return it. - if ($category = get_records_select("question_categories", "course = '$courseid' AND parent = '0'", 'id', '*', '', 1)) { - return reset($category); - } - - // Otherwise, we need to make one - $category = new stdClass; - $category->name = get_string("default", "quiz"); - $category->info = get_string("defaultinfo", "quiz"); - $category->course = $courseid; - $category->parent = 0; - $category->sortorder = 999; // By default, all categories get this number, and are sorted alphabetically. - $category->publish = 0; - $category->stamp = make_unique_id_code(); - - if (!$category->id = insert_record("question_categories", $category)) { - notify("Error creating a default category!"); - return false; + foreach ($contexts as $key => $context) { + if (!$categoryrs = get_recordset_select("question_categories", "contextid = '{$context->id}'", 'sortorder, name', '*', '', 1)) { + error('error getting category record'); + } else { + if (!$category = rs_fetch_record($categoryrs)){ + // Otherwise, we need to make one + $category = new stdClass; + $contextname = print_context_name($context, false, true); + $category->name = addslashes(get_string('defaultfor', 'question', $contextname)); + $category->info = addslashes(get_string('defaultinfofor', 'question', $contextname)); + $category->contextid = $context->id; + $category->parent = 0; + $category->sortorder = 999; // By default, all categories get this number, and are sorted alphabetically. + $category->stamp = make_unique_id_code(); + if (!$category->id = insert_record('question_categories', $category)) { + error('Error creating a default category for context '.print_context_name($context)); + } + } + } + if ($context->contextlevel == CONTEXT_COURSE){ + $toreturn = clone($category); + } } - return $category; + + + return $toreturn; } +function question_can_delete_cat($todelete){ + global $CFG; + $record = get_record_sql("SELECT count(*) as count, c1.contextid as contextid FROM {$CFG->prefix}question_categories as c1, + {$CFG->prefix}question_categories as c2 WHERE c2.id = $todelete + AND c1.contextid = c2.contextid GROUP BY c1.contextid"); + $contextid = $record->contextid; + $count = $record->count; + if ($count < 2) { + error('You can\'t delete that category it is the default category for this context.'); + } else { + require_capability('moodle/question:managecategory', get_context_instance_by_id($contextid)); + } +} /** * prints a form to choose categories */ -function question_category_form($course, $pageurl, $current, $recurse=1, $showhidden=false, $showquestiontext=false) { +function question_category_form($contexts, $pageurl, $current, $recurse=1, $showhidden=false, $showquestiontext=false) { global $CFG; -/// Make sure the default category exists for this course - get_default_question_category($course->id); /// Get all the existing categories now - $catmenu = question_category_options($course->id, true); + $catmenu = question_category_options($contexts, false, 0, true); - $strcategory = get_string("category", "quiz"); - $strshow = get_string("show", "quiz"); - $streditcats = get_string("editcategories", "quiz"); + $strcategory = get_string('category', 'quiz'); + $strshow = get_string('show', 'quiz'); + $streditcats = get_string('editcategories', 'quiz'); - popup_form ("edit.php?".$pageurl->get_query_string()."&category=", $catmenu, "catmenu", $current, "", "", "", false, "self", "$strcategory"); + popup_form ('edit.php?'.$pageurl->get_query_string().'&category=', $catmenu, 'catmenu', $current, '', '', '', false, 'self', "$strcategory"); echo '
'; echo "
"; @@ -163,12 +178,16 @@ function question_category_form_checkbox($name, $checked) { * @param boolean $showhidden True if also hidden questions should be displayed * @param boolean $showquestiontext whether the text of each question should be shown in the list */ -function question_list($course, $pageurl, $categoryid, $cm = null, +function question_list($contexts, $pageurl, $categoryandcontext, $cm = null, $recurse=1, $page=0, $perpage=100, $showhidden=false, $sortorder='typename', $sortorderdecoded='qtype, name ASC', - $showquestiontext = false) { + $showquestiontext = false, $addcontexts = array()) { + global $QTYPE_MENU, $USER, $CFG, $THEME, $COURSE; + + list($categoryid, $contextid)= explode(',', $categoryandcontext); + + $qtypemenu = $QTYPE_MENU; + - global $QTYPE_MENU, $USER, $CFG, $THEME; - $strcategory = get_string("category", "quiz"); $strquestion = get_string("question", "quiz"); $straddquestions = get_string("addquestions", "quiz"); @@ -182,6 +201,8 @@ function question_list($course, $pageurl, $categoryid, $cm = null, $strquestionname = get_string("questionname", "quiz"); $strdelete = get_string("delete"); $stredit = get_string("edit"); + $strmove = get_string('moveqtoanothercontext', 'question'); + $strview = get_string("view"); $straction = get_string("action"); $strrestore = get_string('restore'); @@ -196,43 +217,54 @@ function question_list($course, $pageurl, $categoryid, $cm = null, return; } - if (!$category = get_record('question_categories', 'id', $categoryid)) { + if (!$category = get_record('question_categories', 'id', $categoryid, 'contextid', $contextid)) { notify('Category not found!'); return; } - $canedit = has_capability('moodle/question:manage', get_context_instance(CONTEXT_COURSE, $category->course)); + $catcontext = get_context_instance_by_id($contextid); + $canadd = has_capability('moodle/question:add', $catcontext); + //check for capabilities on all questions in category, will also apply to sub cats. + $caneditall =has_capability('moodle/question:editall', $catcontext); + $canuseall =has_capability('moodle/question:useall', $catcontext); + $canmoveall =has_capability('moodle/question:moveall', $catcontext); if ($cm AND $cm->modname == 'quiz') { $quizid = $cm->instance; } else { $quizid = 0; } - + $returnurl = $pageurl->out(); + $questionurl = new moodle_url("$CFG->wwwroot/question/question.php", + array('returnurl' => $returnurl)); + if ($cm!==null){ + $questionurl->param('cmid', $cm->id); + } else { + $questionurl->param('courseid', $COURSE->id); + } + $questionmoveurl = new moodle_url("$CFG->wwwroot/question/contextmoveq.php", + array('returnurl' => $returnurl)); + if ($cm!==null){ + $questionmoveurl->param('cmid', $cm->id); + } else { + $questionmoveurl->param('courseid', $COURSE->id); + } echo '
'; $formatoptions = new stdClass; $formatoptions->noclean = true; - echo format_text($category->info, FORMAT_MOODLE, $formatoptions, $course->id); + echo format_text($category->info, FORMAT_MOODLE, $formatoptions, $COURSE->id); echo ''; - // check if editing questions in this category is allowed - if ($canedit) { + if ($canadd) { echo ''; } else { echo ''; } @@ -265,19 +297,17 @@ function question_list($course, $pageurl, $categoryid, $cm = null, print_paging_bar($totalnumber, $page, $perpage, $pageurl, 'qpage'); + echo question_sort_options($pageurl, $sortorder); + + echo ''; echo '
'; echo ''; echo $pageurl->hidden_params_out(array('qsortorder')); echo '
'; - $returnurl = urlencode($pageurl->out()); - $questionurl = new moodle_url("$CFG->wwwroot/question/question.php", - array('returnurl' => $returnurl, - 'category' => $category->id)); - if ($cm!==null){ - $questionurl->param('cmid', $cm->id); - } - popup_form ($questionurl->out().'&qtype=', $QTYPE_MENU, "addquestion", "", "choose", "", "", false, "self", "$strcreatenewquestion"); + popup_form ($questionurl->out(false, array('category' => $category->id)).'&qtype=', $qtypemenu, "addquestion", "", "choose", "", "", false, "self", "$strcreatenewquestion"); echo ''; helpbutton("questiontypes", $strcreatenewquestion, "quiz"); echo ''; - print_string("publishedit","quiz"); + print_string('nopermissionadd', 'question'); echo '
'; echo ""; - - $sortoptions = array('alpha' => get_string("sortalpha", "quiz"), - 'typealpha' => get_string("sorttypealpha", "quiz"), - 'age' => get_string("sortage", "quiz")); - $orderselect = choose_from_menu ($sortoptions, 'qsortorder', $sortorder, false, 'this.form.submit();', '0', true); - $orderselect .= ''; - echo " + + echo ""; echo "\n"; foreach ($questions as $question) { @@ -296,25 +326,35 @@ function question_list($course, $pageurl, $categoryid, $cm = null, if ($textclass) { $textclass = 'class="' . $textclass . '"'; } - + echo "\n\n"; echo "\n"; @@ -338,14 +380,13 @@ function question_list($course, $pageurl, $categoryid, $cm = null, $formatoptions->noclean = true; $formatoptions->para = false; echo format_text($question->questiontext, $question->questiontextformat, - $formatoptions, $course->id); + $formatoptions, $COURSE->id); echo "\n"; } } echo "
$straction$strquestionname $orderselect$strquestionname $strtype
\n"; - + + $canuseq = question_has_capability_on($question, 'use', $question->category); if (function_exists('module_specific_actions')) { - echo module_specific_actions($pageurl, $question->id, $cm->id); + echo module_specific_actions($pageurl, $question->id, $cm->id, $canuseq); } - + // preview - link_to_popup_window('/question/preview.php?id=' . $question->id . '&quizid=' . $quizid, 'questionpreview', - "pixpath/t/preview.gif\" class=\"iconsmall\" alt=\"$strpreview\" />", - 0, 0, $strpreview, QUESTION_PREVIEW_POPUP_OPTIONS); - + if ($canuseq) { + link_to_popup_window('/question/preview.php?id=' . $question->id . '&quizid=' . $quizid, 'questionpreview', + "pixpath/t/preview.gif\" class=\"iconsmall\" alt=\"$strpreview\" />", + 0, 0, $strpreview, QUESTION_PREVIEW_POPUP_OPTIONS); + } // edit, hide, delete question, using question capabilities, not quiz capabilieies - if ($canedit) { - $questionparams = (($cm !== null)? array('cmid' => $cm->id) : array()) + - (array('returnurl' => $pageurl->out(), 'id'=>$question->id)); - $questionurl = new moodle_url("$CFG->wwwroot/question/question.php", $questionparams); - echo "out()."\">category) || question_has_capability_on($question, 'move', $question->category)) { + echo "out(false, array('id'=>$question->id))."\">pixpath/t/edit.gif\" alt=\"$stredit\" /> "; + } elseif (question_has_capability_on($question, 'view', $question->category)){ + echo "out(false, array('id'=>$question->id))."\">pixpath/i/info.gif\" alt=\"$strview\" /> "; + } + + if (question_has_capability_on($question, 'move', $question->category) && question_has_capability_on($question, 'view', $question->category)) { + echo "out(false, array('id'=>$question->id, 'movecontext'=>1))."\">pixpath/t/move.gif\" alt=\"$strmove\" /> "; + } + + if (question_has_capability_on($question, 'edit', $question->category)) { // hide-feature if($question->hidden) { echo "get_query_string()."&unhide=$question->id&sesskey=$USER->sesskey\">pixpath/t/delete.gif\" alt=\"$strdelete\" />"; } } - echo " id\" value=\"1\" />"; + if ($caneditall || $canmoveall || $canuseall){ + echo " id\" value=\"1\" />"; + } echo "" . format_string($question->name) . "
\n"; - $paging = print_paging_bar($totalnumber, $page, $perpage, $pageurl, 'qpage', - false, true); + $paging = print_paging_bar($totalnumber, $page, $perpage, $pageurl, 'qpage', false, true); if ($totalnumber > DEFAULT_QUESTIONS_PER_PAGE) { if ($perpage == DEFAULT_QUESTIONS_PER_PAGE) { $showall = ''.get_string('showall', 'moodle', $totalnumber).''; @@ -356,68 +397,108 @@ function question_list($course, $pageurl, $categoryid, $cm = null, $paging = substr($paging, 0, strrpos($paging, '
')); $paging .= "
$showall
"; } else { - $paging = "
$showall
"; + $paging = "
$showall
"; } } echo $paging; - echo '
'; - echo ''.$strselectall.' /'. - ' '.$strselectnone.''. - ' '.get_string('withselected', 'quiz').':
'; + if ($caneditall || $canmoveall || $canuseall){ + echo ''.$strselectall.' /'. + ' '.$strselectnone.''; + echo '
'; + echo ' '.get_string('withselected', 'quiz').':
'; - if (function_exists('module_specific_buttons')) { - echo module_specific_buttons($cm->id); - } - // print delete and move selected question - if ($canedit) { - echo '
\n"; - echo '\n"; - question_category_select_menu($course->id, false, true, $category->id); - } - echo "
"; + if (function_exists('module_specific_buttons')) { + echo module_specific_buttons($cm->id); + } + // print delete and move selected question + if ($caneditall) { + echo '\n"; + } + if ($canmoveall && count($addcontexts)) { + echo '\n"; + question_category_select_menu($addcontexts, false, 0, "$category->id,$category->contextid"); + } - if (function_exists('module_specific_controls')) { - echo module_specific_controls($totalnumber, $recurse, $category->id, $cm->id); + if (function_exists('module_specific_controls') && $canuseall) { + echo module_specific_controls($totalnumber, $recurse, $category, $cm->id); + } } echo ''; echo "\n"; } -/** - * Shows the question bank editing interface. - * - * The function also processes a number of actions: - * - * Actions affecting the question pool: - * move Moves a question to a different category - * deleteselected Deletes the selected questions from the category - * Other actions: - * category Chooses the category - * displayoptions Sets display options - * - * @author Martin Dougiamas and many others. This has recently been extensively - * rewritten by Gustav Delius and other members of the Serving Mathematics project - * {@link http://maths.york.ac.uk/serving_maths} - * @param moodle_url $pageurl object representing this pages url. - */ -function question_showbank($pageurl, $cm, $page, $perpage, $sortorder, $sortorderdecoded, $cat, $recurse, $showhidden, $showquestiontext){ - global $COURSE, $USER; +function question_sort_options($pageurl, $sortorder){ + global $USER; + //sort options + $html = "
"; + $html .= '
'; + $html .= '
'; + $html .= ''; + $html .= $pageurl->hidden_params_out(array('qsortorder')); + $sortoptions = array('alpha' => get_string("sortalpha", "quiz"), + 'typealpha' => get_string("sorttypealpha", "quiz"), + 'age' => get_string("sortage", "quiz")); + $html .= choose_from_menu ($sortoptions, 'qsortorder', $sortorder, false, 'this.form.submit();', '0', true); + $html .= ''; + $html .= '
'; + $html .= "
\n"; + $html .= "
\n"; + return $html; +} -/// Now, check for commands on this page and modify variables as necessary +function question_showbank_actions($pageurl, $cm){ + global $CFG; + /// Now, check for commands on this page and modify variables as necessary if (isset($_REQUEST['move']) and confirm_sesskey()) { /// Move selected questions to new category - $tocategoryid = required_param('category', PARAM_INT); - if (!$tocategory = get_record('question_categories', 'id', $tocategoryid)) { - error('Invalid category'); - } - if (!has_capability('moodle/question:managecategory', get_context_instance(CONTEXT_COURSE, $tocategory->course))){ - error(get_string('categorynoedit', 'quiz', $tocategory->name), $pageurl->out()); + $category = required_param('category', PARAM_SEQUENCE); + list($tocategoryid, $contextid) = explode(',', $category); + if (! $tocategory = get_record('question_categories', 'id', $tocategoryid, 'contextid', $contextid)) { + error('Could not find category record'); } + $tocontext = get_context_instance_by_id($contextid); + require_capability('moodle/question:add', $tocontext); + $questionids = array(); foreach ($_POST as $key => $value) { // Parse input for question ids if (preg_match('!^q([0-9]+)$!', $key, $matches)) { $key = $matches[1]; - if (!set_field('question', 'category', $tocategory->id, 'id', $key)) { - error('Could not update category field'); + $questionids[] = $key; + } + } + if ($questionids){ + $questionidlist = join($questionids, ','); + $sql = "SELECT q.*, c.contextid FROM {$CFG->prefix}question q, {$CFG->prefix}question_categories c WHERE q.id IN ($questionidlist) AND c.id = q.category"; + if (!$questions = get_records_sql($sql)){ + 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){ + foreach ($questionids as $questionid){ + //move question + if (!set_field('question', 'category', $tocategory->id, 'id', $questionid)) { + error('Could not update category field'); + } + } + 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()); } } } @@ -430,94 +511,119 @@ function question_showbank($pageurl, $cm, $page, $perpage, $sortorder, $sortorde 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 (record_exists('quiz_question_instances', 'question', $questionid) or record_exists('question_states', 'originalquestion', $questionid)) { if (!set_field('question', 'hidden', 1, 'id', $questionid)) { - error('Was not able to hide question'); + question_require_capability_on($questionid, 'edit'); + error('Was not able to hide question'); } } else { delete_question($questionid); } } } - echo '
'; - echo ''; redirect($pageurl->out()); } else { error("Confirmation string was incorrect"); } - } else { // teacher still has to confirm - // make a list of all the questions that are selected - $rawquestions = $_REQUEST; - $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.','; - if (record_exists('quiz_question_instances', 'question', $key) or - record_exists('question_states', 'originalquestion', $key)) { - $questionnames .= '* '; - $inuse = true; - } - $questionnames .= get_field('question', 'name', '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(), $pageurl->out(), - array('sesskey' => $USER->sesskey, 'deleteselected' => $questionlist, 'confirm' => md5($questionlist)), - NULL, 'post', 'get'); - - echo ''; - echo ''; - print_footer($COURSE); - exit; } } // Unhide a question if(isset($_REQUEST['unhide']) && confirm_sesskey()) { $unhide = required_param('unhide', PARAM_INT); + question_require_capability_on($unhide, 'edit'); if(!set_field('question', 'hidden', 0, 'id', $unhide)) { error("Failed to unhide the question."); } redirect($pageurl->out()); } +} +/** + * Shows the question bank editing interface. + * + * The function also processes a number of actions: + * + * Actions affecting the question pool: + * move Moves a question to a different category + * deleteselected Deletes the selected questions from the category + * Other actions: + * category Chooses the category + * displayoptions Sets display options + * + * @author Martin Dougiamas and many others. This has recently been extensively + * rewritten by Gustav Delius and other members of the Serving Mathematics project + * {@link http://maths.york.ac.uk/serving_maths} + * @param moodle_url $pageurl object representing this pages url. + */ +function question_showbank($tabname, $contexts, $pageurl, $cm, $page, $perpage, $sortorder, $sortorderdecoded, $cat, $recurse, $showhidden, $showquestiontext){ + global $COURSE; + + if (isset($_REQUEST['deleteselected'])){ // teacher still has to confirm + // make a list of all the questions that are selected + $rawquestions = $_REQUEST; + $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 (record_exists('quiz_question_instances', 'question', $key) or + record_exists('question_states', 'originalquestion', $key)) { + $questionnames .= '* '; + $inuse = true; + } + $questionnames .= get_field('question', 'name', '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()); + + echo ''; + echo ''; + print_footer($COURSE); + exit; + } + // starts with category selection form print_box_start('generalbox questionbank'); print_heading(get_string('questionbank', 'question'), '', 2); - question_category_form($COURSE, $pageurl, $cat, $recurse, - $showhidden, $showquestiontext); - + question_category_form($contexts->having_one_edit_tab_cap($tabname), $pageurl, $cat, $recurse, $showhidden, $showquestiontext); + // continues with list of questions - question_list($COURSE, $pageurl, $cat, isset($cm) ? $cm : null, - $recurse, $page, $perpage, $showhidden, $sortorder, $sortorderdecoded, - $showquestiontext); + question_list($contexts->having_one_edit_tab_cap($tabname), $pageurl, $cat, isset($cm) ? $cm : null, + $recurse, $page, $perpage, $showhidden, $sortorder, $sortorderdecoded, $showquestiontext, + $contexts->having_cap('moodle/question:add')); print_box_end(); } /** * Common setup for all pages for editing questions. + * @param string $edittab code for this edit tab * @param boolean $requirecmid require cmid? default false * @param boolean $requirecourseid require courseid, if cmid is not given? default true - * @return array $thispageurl, $courseid, $cmid, $cm, $module, $pagevars + * @return array $thispageurl, $contexts, $cmid, $cm, $module, $pagevars */ -function question_edit_setup($requirecmid = false, $requirecourseid = true){ - global $COURSE; - //$thispageurl is used to construct urls for all question edit pages we link to from this page. It contains an array +function question_edit_setup($edittab, $requirecmid = false, $requirecourseid = true){ + global $COURSE, $QUESTION_EDITTABCAPS; + + //$thispageurl is used to construct urls for all question edit pages we link to from this page. It contains an array //of parameters that are passed from page to page. $thispageurl = new moodle_url(); if ($requirecmid){ @@ -526,9 +632,11 @@ function question_edit_setup($requirecmid = false, $requirecourseid = true){ $cmid = optional_param('cmid', 0, PARAM_INT); } if ($cmid){ - list($module, $cm) = get_module_from_cmid($cmid); + list($module, $cm) = get_module_from_cmid($cmid); $courseid = $cm->course; $thispageurl->params(compact('cmid')); + require_login($courseid, false, $cm); + $thiscontext = get_context_instance(CONTEXT_MODULE, $cmid); } else { $module = null; $cm = null; @@ -539,18 +647,30 @@ function question_edit_setup($requirecmid = false, $requirecourseid = true){ } if ($courseid){ $thispageurl->params(compact('courseid')); + require_login($courseid, false); + $thiscontext = get_context_instance(CONTEXT_COURSE, $courseid); + } else { + $thiscontext = null; } } - require_login($courseid, false); - - + + if ($thiscontext){ + $contexts = new question_edit_contexts($thiscontext); + $contexts->require_one_edit_tab_cap($edittab); + + } else { + $contexts = null; + } + + + $pagevars['qpage'] = optional_param('qpage', -1, PARAM_INT); - + //pass 'cat' from page to page and when 'category' comes from a drop down menu - //then we also reset the qpage so we go to page 1 of + //then we also reset the qpage so we go to page 1 of //a new cat. - $pagevars['cat'] = optional_param('cat', 0, PARAM_INT); - if ($category = optional_param('category', 0, PARAM_INT)){ + $pagevars['cat'] = optional_param('cat', 0, PARAM_SEQUENCE);// if empty will be set up later + if ($category = optional_param('category', 0, PARAM_SEQUENCE)){ $pagevars['cat'] = $category; $pagevars['qpage'] = 0; } @@ -569,7 +689,7 @@ function question_edit_setup($requirecmid = false, $requirecourseid = true){ } else { $pagevars['qperpage'] = DEFAULT_QUESTIONS_PER_PAGE; } - + $sortoptions = array('alpha' => 'name, qtype ASC', 'typealpha' => 'qtype, name ASC', 'age' => 'id ASC'); @@ -581,13 +701,23 @@ function question_edit_setup($requirecmid = false, $requirecourseid = true){ } else { $pagevars['qsortorderdecoded'] = $sortoptions['typealpha']; $pagevars['qsortorder'] = 'typealpha'; - } - + } - if (empty($pagevars['cat']) or !count_records_select("question_categories", "id = '".$pagevars['cat']."' AND (course = '{$COURSE->id}' OR publish = '1')")) { - $category = get_default_question_category($COURSE->id); - $pagevars['cat'] = $category->id; - $thispageurl->param('cat', $category->id); + $defaultcategory = question_make_default_categories($contexts->all()); + + $contextlistarr = array(); + foreach ($contexts->having_one_edit_tab_cap($edittab) as $context){ + $contextlistarr[] = "'$context->id'"; + } + $contextlist = join($contextlistarr, ' ,'); + if (!empty($pagevars['cat'])){ + $catparts = explode(',', $pagevars['cat']); + if (!$catparts[0] || (FALSE !== array_search($catparts[1], $contextlistarr)) || !count_records_select("question_categories", "id = '".$catparts[0]."' AND contextid = $catparts[1]")) { + error(get_string('invalidcategory', 'quiz')); + } + } else { + $category = $defaultcategory; + $pagevars['cat'] = "$category->id,$category->contextid"; } if(($recurse = optional_param('recurse', -1, PARAM_BOOL)) != -1) { @@ -596,28 +726,213 @@ function question_edit_setup($requirecmid = false, $requirecourseid = true){ } else { $pagevars['recurse'] = 1; } - + if(($showhidden = optional_param('showhidden', -1, PARAM_BOOL)) != -1) { $pagevars['showhidden'] = $showhidden; $thispageurl->param('showhidden', $showhidden); } else { $pagevars['showhidden'] = 0; } - + if(($showquestiontext = optional_param('showquestiontext', -1, PARAM_BOOL)) != -1) { $pagevars['showquestiontext'] = $showquestiontext; $thispageurl->param('showquestiontext', $showquestiontext); } else { $pagevars['showquestiontext'] = 0; } - + //category list page $pagevars['cpage'] = optional_param('cpage', 1, PARAM_INT); if ($pagevars['cpage'] != 1){ $thispageurl->param('cpage', $pagevars['cpage']); } - - return array($thispageurl, $courseid, $cmid, $cm, $module, $pagevars); + + return array($thispageurl, $contexts, $cmid, $cm, $module, $pagevars); +} +class question_edit_contexts{ + var $allcontexts; + /** + * @param current context + */ + function question_edit_contexts($thiscontext){ + $pcontextids = get_parent_contexts($thiscontext); + $contexts = array($thiscontext); + foreach ($pcontextids as $pcontextid){ + $contexts[] = get_context_instance_by_id($pcontextid); + } + $this->allcontexts = $contexts; + } + /** + * @return array all parent contexts + */ + function all(){ + return $this->allcontexts; + } + /** + * @return object lowest context which must be either the module or course context + */ + function lowest(){ + return $this->allcontexts[0]; + } + /** + * @param string $cap capability + * @return array parent contexts having capability, zero based index + */ + function having_cap($cap){ + $contextswithcap = array(); + foreach ($this->allcontexts as $context){ + if (has_capability($cap, $context)){ + $contextswithcap[] = $context; + } + } + return $contextswithcap; + } + /** + * @param array $caps capabilities + * @return array parent contexts having at least one of $caps, zero based index + */ + function having_one_cap($caps){ + $contextswithacap = array(); + foreach ($this->allcontexts as $context){ + foreach ($caps as $cap){ + if (has_capability($cap, $context)){ + $contextswithacap[] = $context; + break; //done with caps loop + } + } + } + return $contextswithacap; + } + /** + * @param string $tabname edit tab name + * @return array parent contexts having at least one of $caps, zero based index + */ + function having_one_edit_tab_cap($tabname){ + global $QUESTION_EDITTABCAPS; + return $this->having_one_cap($QUESTION_EDITTABCAPS[$tabname]); + } + /** + * Has at least one parent context got the cap $cap? + * + * @param string $cap capability + * @return boolean + */ + function have_cap($cap){ + return (count($this->having_cap($cap))); + } + + /** + * Has at least one parent context got one of the caps $caps? + * + * @param string $cap capability + * @return boolean + */ + function have_one_cap($caps){ + foreach ($caps as $cap){ + if ($this->have_cap($cap)){ + return true; + } + } + return false; + } + /** + * Has at least one parent context got one of the caps for actions on $tabname + * + * @param string $tabname edit tab name + * @return boolean + */ + function have_one_edit_tab_cap($tabname){ + global $QUESTION_EDITTABCAPS; + return $this->have_one_cap($QUESTION_EDITTABCAPS[$tabname]); + } + /** + * Throw error if at least one parent context hasn't got the cap $cap + * + * @param string $cap capability + */ + function require_cap($cap){ + if (!$this->have_cap($cap)){ + print_error('nopermissions', '', '', $cap); + } + } + /** + * Throw error if at least one parent context hasn't got one of the caps $caps + * + * @param array $cap capabilities + */ + function require_one_cap($caps){ + if (!$this->have_one_cap($caps)){ + $capsstring = join($caps, ', '); + print_error('nopermissions', '', '', $capsstring); + } + } + /** + * Throw error if at least one parent context hasn't got one of the caps $caps + * + * @param string $tabname edit tab name + */ + function require_one_edit_tab_cap($tabname){ + if (!$this->have_one_edit_tab_cap($tabname)){ + print_error('nopermissions', '', '', 'access question edit tab '.$tabname); + } + } +} + +//capabilities for each page of edit tab. +//this determines which contexts' categories are available. At least one +//page is displayed if user has one of the capability on at least one context +$QUESTION_EDITTABCAPS = array( + 'editq' => array('moodle/question:add', + 'moodle/question:editmine', + 'moodle/question:editall', + 'moodle/question:viewmine', + 'moodle/question:viewall', + 'moodle/question:usemine', + 'moodle/question:useall', + 'moodle/question:movemine', + 'moodle/question:moveall'), + 'questions'=>array('moodle/question:add', + 'moodle/question:editmine', + 'moodle/question:editall', + 'moodle/question:viewmine', + 'moodle/question:viewall', + 'moodle/question:movemine', + 'moodle/question:moveall'), + 'categories'=>array('moodle/question:managecategory'), + 'import'=>array('moodle/question:add'), + 'export'=>array('moodle/question:viewall', 'moodle/question:viewmine')); + + + +/** + * Make sure user is logged in as required in this context. + */ +function require_login_in_context($contextorid = null){ + if (!is_object($contextorid)){ + $context = get_context_instance_by_id($contextorid); + } else { + $context = $contextorid; + } + if ($context && ($context->contextlevel == CONTEXT_COURSE)) { + require_login($context->instanceid); + } else if ($context && ($context->contextlevel == CONTEXT_MODULE)) { + if ($cm = get_record('course_modules','id',$context->instanceid)) { + if (!$course = get_record('course', 'id', $cm->course)) { + error('Incorrect course.'); + } + require_course_login($course, true, $cm); + + } else { + error('Incorrect course module id.'); + } + } else if ($context && ($context->contextlevel == CONTEXT_SYSTEM)) { + if (!empty($CFG->forcelogin)) { + require_login(); + } + + } else { + require_login(); + } } -?> +?> \ No newline at end of file diff --git a/question/export.php b/question/export.php index 57d0ede8e0..5af47350cf 100644 --- a/question/export.php +++ b/question/export.php @@ -10,66 +10,44 @@ */ require_once("../config.php"); - require_once( "editlib.php" ); + require_once("editlib.php"); + require_once("export_form.php"); - list($thispageurl, $courseid, $cmid, $cm, $module, $pagevars) = question_edit_setup(); + list($thispageurl, $contexts, $cmid, $cm, $module, $pagevars) = question_edit_setup('export'); - $cattofile = optional_param('cattofile',0, PARAM_BOOL); - - $exportfilename = optional_param('exportfilename','',PARAM_FILE ); - $format = optional_param('format','', PARAM_FILE ); - $categoryid = optional_param('category',0,PARAM_INT); // get display strings $txt = new object; - $txt->category = get_string('category','quiz'); - $txt->download = get_string('download','quiz'); - $txt->downloadextra = get_string('downloadextra','quiz'); - $txt->exporterror = get_string('exporterror','quiz'); - $txt->exportname = get_string('exportname','quiz'); + $txt->category = get_string('category', 'quiz'); + $txt->download = get_string('download', 'quiz'); + $txt->downloadextra = get_string('downloadextra', 'quiz'); + $txt->exporterror = get_string('exporterror', 'quiz'); + $txt->exportname = get_string('exportname', 'quiz'); $txt->exportquestions = get_string('exportquestions', 'quiz'); - $txt->fileformat = get_string('fileformat','quiz'); - $txt->exportcategory = get_string('exportcategory','quiz'); - $txt->modulename = get_string('modulename','quiz'); - $txt->modulenameplural = get_string('modulenameplural','quiz'); - $txt->tofile = get_string('tofile','quiz'); + $txt->fileformat = get_string('fileformat', 'quiz'); + $txt->exportcategory = get_string('exportcategory', 'quiz'); + $txt->modulename = get_string('modulename', 'quiz'); + $txt->modulenameplural = get_string('modulenameplural', 'quiz'); + $txt->tofile = get_string('tofile', 'quiz'); - if (!$course = get_record("course", "id", $courseid)) { - error("Course does not exist!"); - } // make sure we are using the user's most recent category choice if (empty($categoryid)) { $categoryid = $pagevars['cat']; } - if (!$category = get_record("question_categories", "id", $categoryid)) { - $category = get_default_question_category($courseid); - } - - if (!$categorycourse = get_record("course", "id", $category->course)) { - print_error('nocategory','quiz'); - } - - - // check role capability - $context = get_context_instance(CONTEXT_COURSE, $course->id); - require_capability('moodle/question:export', $context); - // ensure the files area exists for this course - make_upload_directory( "$course->id" ); - - // check category is valid - $validcats = question_category_options( $course->id, true, false ); - if (!array_key_exists( $categoryid, $validcats)) { - print_error( 'invalidcategory','quiz' ); + make_upload_directory("$COURSE->id"); + list($catid, $catcontext) = explode(',', $pagevars['cat']); + if (!$category = get_record("question_categories", "id", $catid, 'contextid', $catcontext)) { + print_error('nocategory','quiz'); } /// Header if ($cm!==null) { - $strupdatemodule = has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $course->id)) - ? update_module_button($cm->id, $course->id, get_string('modulename', $cm->modname)) + $strupdatemodule = has_capability('moodle/course:manageactivities', $contexts->lowest()) + ? update_module_button($cm->id, $COURSE->id, get_string('modulename', $cm->modname)) : ""; $navlinks = array(); $navlinks[] = array('name' => get_string('modulenameplural', $cm->modname), 'link' => "$CFG->wwwroot/mod/{$cm->modname}/index.php?id=$course->id", 'type' => 'activity'); @@ -87,119 +65,80 @@ $navlinks = array(); $navlinks[] = array('name' => $txt->exportquestions, 'link' => '', 'type' => 'title'); $navigation = build_navigation($navlinks); - + print_header_simple($txt->exportquestions, '', $navigation); // print tabs $currenttab = 'export'; include('tabs.php'); } - if (!empty($format)) { /// Filename + $exportfilename = default_export_filename($COURSE, $category); + $export_form = new question_export_form($thispageurl, array('contexts'=>$contexts->having_one_edit_tab_cap('export'), 'defaultcategory'=>$pagevars['cat'], + 'defaultfilename'=>$exportfilename)); - if (!confirm_sesskey()) { - print_error( 'sesskey' ); - } - if (! is_readable("format/$format/format.php")) { - error( "Format not known ($format)" ); } + if ($from_form = $export_form->get_data()) { /// Filename + + + if (! is_readable("format/$from_form->format/format.php")) { + error("Format not known ($from_form->format)"); + } // load parent class for import/export - require("format.php"); + require_once("format.php"); // and then the class for the selected format - require("format/$format/format.php"); + require_once("format/$from_form->format/format.php"); - $classname = "qformat_$format"; + $classname = "qformat_$from_form->format"; $qformat = new $classname(); + $qformat->setContexts($contexts->having_one_edit_tab_cap('export')); + $qformat->setCategory($category); + $qformat->setCourse($COURSE); - $qformat->setCategory( $category ); - $qformat->setCourse( $course ); - $qformat->setFilename( $exportfilename ); - $qformat->setCattofile( $cattofile ); + if (empty($from_form->exportfilename)) { + $from_form->exportfilename = default_export_filename($COURSE, $category); + } + $qformat->setFilename($from_form->exportfilename); + $qformat->setCattofile(!empty($from_form->cattofile)); + $qformat->setContexttofile(!empty($from_form->contexttofile)); if (! $qformat->exportpreprocess()) { // Do anything before that we need to - error( $txt->exporterror, $thispageurl->out()); + error($txt->exporterror, $thispageurl->out()); } if (! $qformat->exportprocess()) { // Process the export data - error( $txt->exporterror, $thispageurl->out()); + error($txt->exporterror, $thispageurl->out()); } if (! $qformat->exportpostprocess()) { // In case anything needs to be done after - error( $txt->exporterror, $thispageurl->out()); + error($txt->exporterror, $thispageurl->out()); } echo "
"; // link to download the finished file $file_ext = $qformat->export_file_extension(); if ($CFG->slasharguments) { - $efile = "{$CFG->wwwroot}/file.php/".$qformat->question_get_export_dir()."/$exportfilename".$file_ext."?forcedownload=1"; + $efile = "{$CFG->wwwroot}/file.php/".$qformat->question_get_export_dir()."/$from_form->exportfilename".$file_ext."?forcedownload=1"; } else { - $efile = "{$CFG->wwwroot}/file.php?file=/".$qformat->question_get_export_dir()."/$exportfilename".$file_ext."&forcedownload=1"; + $efile = "{$CFG->wwwroot}/file.php?file=/".$qformat->question_get_export_dir()."/$from_form->exportfilename".$file_ext."&forcedownload=1"; } echo "

"; echo "

$txt->downloadextra

"; print_continue("edit.php?".$thispageurl->get_query_string()); - print_footer($course); + print_footer($COURSE); exit; } - /// Display upload form + /// Display export form - // get valid formats to generate dropdown list - $fileformatnames = get_import_export_formats( 'export' ); - - // get filename - if (empty($exportfilename)) { - $exportfilename = default_export_filename($course, $category); - } print_heading_with_help($txt->exportquestions, 'export', 'quiz'); - print_simple_box_start('center'); -?> -
-
- - hidden_params_out(array(), 3); ?> - - - - - - - - - - - - - - - - - -
category; ?>: - id, true, false, $category->id); - echo $txt->tofile; ?> - - exportcategory, 'quiz'); ?> -
fileformat; ?>: - exportquestions, 'quiz'); ?> -
exportname; ?>: - -
- -  
-
-
- display(); + + print_footer($COURSE); ?> diff --git a/question/format.php b/question/format.php index 0cfd17bfd4..0222c6c4b1 100644 --- a/question/format.php +++ b/question/format.php @@ -1,4 +1,4 @@ -course = $course; } + /** + * set an array of contexts. + * @param array $contexts Moodle course variable + */ + function setContexts($contexts) { + $this->contexts = $contexts; + $this->translator = new context_to_string_translator($this->contexts); + } /** * set the filename @@ -73,14 +85,29 @@ class qformat_default { function setCatfromfile( $catfromfile ) { $this->catfromfile = $catfromfile; } - + + /** + * set contextfromfile + * @param bool $contextfromfile allow contexts embedded in import file + */ + function setContextfromfile($contextfromfile) { + $this->contextfromfile = $contextfromfile; + } + /** * set cattofile * @param bool cattofile exports categories within export file */ function setCattofile( $cattofile ) { $this->cattofile = $cattofile; - } + } + /** + * set contexttofile + * @param bool cattofile exports categories within export file + */ + function setContexttofile($contexttofile) { + $this->contexttofile = $contexttofile; + } /** * set stoponerror @@ -112,7 +139,7 @@ class qformat_default { $this->importerrors++; } - /** + /** * Import for questiontype plugins * Do not override. * @param data mixed The segment of data containing the question @@ -135,8 +162,8 @@ class qformat_default { return $question; } } - } - return false; + } + return false; } /** @@ -153,13 +180,14 @@ class qformat_default { * @return boolean success */ function importprocess() { + global $USER; // reset the timer in case file upload was slow @set_time_limit(); // STAGE 1: Parse the file notify( get_string('parsingquestions','quiz') ); - + if (! $lines = $this->readdata($this->filename)) { notify( get_string('cannotread','quiz') ); return false; @@ -186,7 +214,7 @@ class qformat_default { foreach ($questions as $question) { // Process and store each question - // reset the php timeout + // reset the php timeout @set_time_limit(); // check for category modifiers @@ -194,12 +222,12 @@ class qformat_default { if ($this->catfromfile) { // find/create category object $catpath = $question->category; - $newcategory = create_category_path( $catpath, '/', $this->course->id ); + $newcategory = $this->create_category_path( $catpath, '/'); if (!empty($newcategory)) { $this->category = $newcategory; } } - continue; + continue; } $count++; @@ -231,6 +259,9 @@ class qformat_default { $question->category = $this->category->id; $question->stamp = make_unique_id_code(); // Set the unique code (not to be changed) + $question->createdby = $USER->id; + $question->timecreated = time(); + if (!$question->id = insert_record("question", $question)) { error( get_string('cannotinsert','quiz') ); } @@ -258,7 +289,57 @@ class qformat_default { } return true; } - + /** + * find and/or create the category described by a delimited list + * e.g. $course$/tom/dick/harry or tom/dick/harry + * + * removes any context string no matter whether $getcontext is set + * but if $getcontext is set then ignore the context and use selected category context. + * + * @param string catpath delimited category path + * @param string delimiter path delimiting character + * @param int courseid course to search for categories + * @return mixed category object or null if fails + */ + function create_category_path($catpath, $delimiter='/') { + $catpath = clean_param($catpath, PARAM_PATH); + $catnames = explode($delimiter, $catpath); + $parent = 0; + $category = null; + if (FALSE !== preg_match('/^\$([a-z]+)\$$/', $catnames[0], $matches)){ + $contextid = $this->translator->string_to_context($matches[1]); + array_shift($catnames); + } else { + $contextid = FALSE; + } + if ($this->contextfromfile && ($contextid !== FALSE)){ + $context = get_context_instance_by_id($contextid); + require_capability('moodle/question:add', $context); + } else { + $context = get_context_instance_by_id($this->category->contextid); + } + foreach ($catnames as $catname) { + if ($category = get_record( 'question_categories', 'name', $catname, 'contextid', $context->id, 'parent', $parent)) { + $parent = $category->id; + } else { + require_capability('moodle/question:managecategory', $context); + // create the new category + $category = new object; + $category->contextid = $context->id; + $category->name = $catname; + $category->info = ''; + $category->parent = $parent; + $category->sortorder = 999; + $category->stamp = make_unique_id_code(); + if (!($id = insert_record('question_categories', $category))) { + error( "cannot create new category - $catname" ); + } + $category->id = $id; + $parent = $id; + } + } + return $category; + } /** * Return complete file within an array, one item per line * @param string filename name of file @@ -279,9 +360,9 @@ class qformat_default { } /** - * Parses an array of lines into an array of questions, - * where each item is a question object as defined by - * readquestion(). Questions are defined as anything + * Parses an array of lines into an array of questions, + * where each item is a question object as defined by + * readquestion(). Questions are defined as anything * between blank lines. * * If your format does not use blank lines as a delimiter @@ -291,7 +372,7 @@ class qformat_default { * @return array array of question objects */ function readquestions($lines) { - + $questions = array(); $currentquestion = array(); @@ -325,12 +406,12 @@ class qformat_default { * by import but are required db fields. * This should not be overridden. * @return object default question - */ + */ function defaultquestion() { global $CFG; - + $question = new stdClass(); - $question->shuffleanswers = $CFG->quiz_shuffleanswers; + $question->shuffleanswers = $CFG->quiz_shuffleanswers; $question->defaultgrade = 1; $question->image = ""; $question->usecase = 0; @@ -351,8 +432,8 @@ class qformat_default { } /** - * Given the data known to define a question in - * this format, this function converts it into a question + * Given the data known to define a question in + * this format, this function converts it into a question * object suitable for processing and insertion into Moodle. * * If your format does not use blank lines to delimit questions @@ -396,7 +477,7 @@ class qformat_default { check_dir_exists($destination, true, true ); // detect and fix any filename collision - get unique filename - $newfiles = resolve_filename_collisions( $destination, array($file) ); + $newfiles = resolve_filename_collisions( $destination, array($file) ); $newfile = $newfiles[0]; // convert and save file contents @@ -421,11 +502,11 @@ class qformat_default { * EXPORT FUNCTIONS *******************/ - /** + /** * Provide export functionality for plugin questiontypes * Do not override * @param name questiontype name - * @param question object data to export + * @param question object data to export * @param extra mixed any addition format specific data needed * @return string the data to append to export or false if error (or unhandled) */ @@ -503,7 +584,7 @@ class qformat_default { // results are first written into string (and then to a file) // so create/initialize the string here $expout = ""; - + // track which category questions are in // if it changes we will record the category change in the output // file if selected. 0 means that it will get printed before the 1st question @@ -511,7 +592,7 @@ class qformat_default { // iterate through questions foreach($questions as $question) { - + // do not export hidden questions if (!empty($question->hidden)) { continue; @@ -521,13 +602,13 @@ class qformat_default { if ($question->qtype==RANDOM) { continue; } - + // check if we need to record category change if ($this->cattofile) { if ($question->category != $trackcategory) { - $trackcategory = $question->category; - $categoryname = get_category_path( $trackcategory ); - + $trackcategory = $question->category; + $categoryname = $this->get_category_path($trackcategory, '/', $this->contexttofile); + // create 'dummy' question for category export $dummyquestion = new object; $dummyquestion->qtype = 'category'; @@ -536,8 +617,8 @@ class qformat_default { $dummyquestion->id = 0; $dummyquestion->questiontextformat = ''; $expout .= $this->writequestion( $dummyquestion ) . "\n"; - } - } + } + } // export the question displaying message $count++; @@ -547,7 +628,7 @@ class qformat_default { // final pre-process on exported data $expout = $this->presave_process( $expout ); - + // write file $filepath = $path."/".$this->filename . $this->export_file_extension(); if (!$fh=fopen($filepath,"w")) { @@ -559,6 +640,35 @@ class qformat_default { fclose($fh); return true; } + /** + * get the category as a path (e.g., tom/dick/harry) + * @param int id the id of the most nested catgory + * @param string delimiter the delimiter you want + * @return string the path + */ + function get_category_path($id, $delimiter='/', $includecontext = true) { + $path = ''; + if (!$firstcategory = get_record('question_categories','id',$id)) { + print_error( "Error getting category record from db - $id" ); + } + $category = $firstcategory; + $contextstring = $this->translator->context_to_string($category->contextid); + do { + $name = $category->name; + $id = $category->parent; + if (!empty($path)) { + $path = "{$name}{$delimiter}{$path}"; + } + else { + $path = $name; + } + } while ($category = get_record( 'question_categories','id',$id )); + + if ($includecontext){ + $path = '$'.$contextstring.'$'."{$delimiter}{$path}"; + } + return $path; + } /** * Do an post-processing that may be required @@ -579,12 +689,11 @@ class qformat_default { // if not overidden, then this is an error. $formatnotimplemented = get_string( 'formatnotimplemented','quiz' ); echo "

$formatnotimplemented

"; - return NULL; } /** - * get directory into which export is going + * get directory into which export is going * @return string file path */ function question_get_export_dir() { @@ -608,6 +717,8 @@ class qformat_default { } return format_text(stripslashes($question->questiontext), $format, $formatoptions); } + + } ?> diff --git a/question/format/coursetestmanager/format.php b/question/format/coursetestmanager/format.php index b80cf24d4f..e8de1dc8e5 100755 --- a/question/format/coursetestmanager/format.php +++ b/question/format/coursetestmanager/format.php @@ -25,8 +25,8 @@ class qformat_coursetestmanager extends qformat_default { } function importprocess($filename) { - global $CFG,$strimportquestions,$form,$question_category,$category,$course, - $hostname, $mdapath, $mdbpath; + global $CFG, $USER, $strimportquestions,$form,$question_category,$category,$course, + $hostname, $mdapath, $mdbpath; if ((PHP_OS == "Linux") and isset($hostname)) { $hostname = trim($hostname); // test the ODBC socket server connection @@ -261,6 +261,8 @@ class qformat_coursetestmanager extends qformat_default { echo "

$count. ".stripslashes($question->questiontext)."

"; $question->category = $this->category->id; $question->stamp = make_unique_id_code(); // Set the unique code (not to be changed) + $question->createdby = $USER->id; + $question->timecreated = time(); if (!$question->id = insert_record("question", $question)) { error("Could not insert new question!"); } diff --git a/question/import.php b/question/import.php index 7946756fdb..153f7031d2 100644 --- a/question/import.php +++ b/question/import.php @@ -10,92 +10,53 @@ */ require_once("../config.php"); - require_once("editlib.php" ); + require_once("editlib.php"); require_once($CFG->libdir . '/uploadlib.php'); require_once($CFG->libdir . '/questionlib.php'); + require_once("import_form.php"); - list($thispageurl, $courseid, $cmid, $cm, $module, $pagevars) = question_edit_setup(false, false); + list($thispageurl, $contexts, $cmid, $cm, $module, $pagevars) = question_edit_setup('import', false, false); - // get parameters - $params = new stdClass; - $params->choosefile = optional_param('choosefile','',PARAM_PATH); - $catfromfile = optional_param('catfromfile', 0, PARAM_BOOL ); - $format = optional_param('format','',PARAM_FILE); - $params->matchgrades = optional_param('matchgrades','',PARAM_ALPHA); - $params->stoponerror = optional_param('stoponerror', 0, PARAM_BOOL); - $params->category = optional_param( 'category', 0, PARAM_INT ); - - // get display strings + // get display strings $txt = new stdClass(); - $txt->category = get_string('category','quiz'); - $txt->choosefile = get_string('choosefile','quiz'); - $txt->file = get_string('file'); - $txt->fileformat = get_string('fileformat','quiz'); - $txt->fromfile = get_string('fromfile','quiz'); - $txt->importcategory = get_string('importcategory','quiz'); $txt->importerror = get_string('importerror','quiz'); - $txt->importfilearea = get_string('importfilearea','quiz'); - $txt->importfileupload = get_string('importfileupload','quiz'); - $txt->importfromthisfile = get_string('importfromthisfile','quiz'); $txt->importquestions = get_string("importquestions", "quiz"); - $txt->matchgrades = get_string('matchgrades','quiz'); - $txt->matchgradeserror = get_string('matchgradeserror','quiz'); - $txt->matchgradesnearest = get_string('matchgradesnearest','quiz'); - $txt->modulename = get_string('modulename','quiz'); - $txt->modulenameplural = get_string('modulenameplural','quiz'); - $txt->onlyteachersimport = get_string('onlyteachersimport','quiz'); - $txt->questions = get_string("questions", "quiz"); - $txt->quizzes = get_string('modulenameplural', 'quiz'); - $txt->stoponerror = get_string('stoponerror', 'quiz'); - $txt->upload = get_string('upload'); - $txt->uploadproblem = get_string('uploadproblem'); - $txt->uploadthisfile = get_string('uploadthisfile'); - - // matching options - $matchgrades = array(); - $matchgrades['error'] = $txt->matchgradeserror; - $matchgrades['nearest'] = $txt->matchgradesnearest; - - // not sure where $pagevars['cat'] comes from, but it doesn't respect - // the user's choice on the form - so this bodge - if (empty($params->category)) { - $params->category = $pagevars['cat']; - } - if (!$category = get_record("question_categories", "id", $params->category)) { - // if no valid category was given, use the default category + list($catid, $catcontext) = explode(',', $pagevars['cat']); + if (!$category = get_record("question_categories", "id", $catid)) { print_error('nocategory','quiz'); } - // check category is valid (against THIS courseid, before we change it) - $validcats = question_category_options( $cmid, false, true ); - if (!array_key_exists( $params->category, $validcats )) { - print_error( 'invalidcategory', 'quiz' ); - } - - $localcourseid = $cmid; - $courseid = $category->course; - - if (!$course = get_record("course", "id", $courseid)) { - error("Invalid course!"); + //this page can be called without courseid or cmid in which case + //we get the context from the category object. + if ($contexts === null) { // need to get the course from the chosen category + $contexts = new question_edit_contexts(get_context_instance_by_id($category->contextid)); + $thiscontext = $contexts->lowest(); + if ($thiscontext->contextlevel == CONTEXT_COURSE){ + require_login($thiscontext->instanceid, false); + } elseif ($thiscontext->contextlevel == CONTEXT_MODULE){ + list($module, $cm) = get_module_from_cmid($thiscontext->instanceid); + require_login($cm->course, false, $cm); + } + $contexts->require_one_edit_tab_cap($edittab); } - require_login($course->id, false); - - $context = get_context_instance(CONTEXT_COURSE, $course->id); - require_capability('moodle/question:import', $context); - // ensure the files area exists for this course - make_upload_directory( "$course->id" ); + make_upload_directory("$COURSE->id"); + $import_form = new question_import_form($thispageurl, array('contexts'=>$contexts->having_one_edit_tab_cap('import'), + 'defaultcategory'=>$pagevars['cat'])); + if ($import_form->is_cancelled()){ + redirect($thispageurl); + } //========== // PAGE HEADER //========== if ($cm!==null) { - $strupdatemodule = has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $course->id)) - ? update_module_button($cm->id, $course->id, get_string('modulename', $cm->modname)) + $strupdatemodule = has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $COURSE->id)) + ? update_module_button($cm->id, $COURSE->id, get_string('modulename', $cm->modname)) : ""; $navlinks = array(); $navlinks[] = array('name' => get_string('modulenameplural', $cm->modname), 'link' => "$CFG->wwwroot/mod/{$cm->modname}/index.php?id=$course->id", 'type' => 'activity'); @@ -113,7 +74,7 @@ $navlinks = array(); $navlinks[] = array('name' => $txt->importquestions, 'link' => '', 'type' => 'title'); $navigation = build_navigation($navlinks); - + print_header_simple($txt->importquestions, '', $navigation); // print tabs $currenttab = 'import'; @@ -122,165 +83,78 @@ // file upload form sumitted - if (!empty($format) and confirm_sesskey() ) { + if ($form = $import_form->get_data()) { // file checks out ok $fileisgood = false; - // work out if this is an uploaded file + // work out if this is an uploaded file // or one from the filesarea. - if (!empty($params->choosefile)) { - $importfile = "{$CFG->dataroot}/{$course->id}/{$params->choosefile}"; + if (!empty($form->choosefile)) { + $importfile = "{$CFG->dataroot}/{$COURSE->id}/{$form->choosefile}"; if (file_exists($importfile)) { $fileisgood = true; - } - else { - notify($txt->uploadproblem); + } else { + error(get_string('uploadproblem', 'moodle', $form->choosefile)); } } else { // must be upload file - if (empty($_FILES['newfile'])) { - notify( $txt->uploadproblem ); - } - else if ((!is_uploaded_file($_FILES['newfile']['tmp_name']) or $_FILES['newfile']['size'] == 0)) { - notify( $txt->uploadproblem ); - } - else { - $importfile = $_FILES['newfile']['tmp_name']; + if (!$importfile = $import_form->get_importfile_name()) { + error(get_string('uploadproblem', 'moodle')); + }else { $fileisgood = true; } } // process if we are happy file is ok - if ($fileisgood) { + if ($fileisgood) { - if (! is_readable("format/$format/format.php")) { - error( get_string('formatnotfound','quiz', $format) ); + if (! is_readable("format/$form->format/format.php")) { + error(get_string('formatnotfound','quiz', $form->format)); } - require("format.php"); // Parent class - require("format/$format/format.php"); + require_once("format.php"); // Parent class + require_once("format/$form->format/format.php"); - $classname = "qformat_$format"; + $classname = "qformat_$form->format"; $qformat = new $classname(); // load data into class - $qformat->setCategory( $category ); - $qformat->setCourse( $course ); - $qformat->setFilename( $importfile ); - $qformat->setMatchgrades( $params->matchgrades ); - $qformat->setCatfromfile( $catfromfile ); - $qformat->setStoponerror( $params->stoponerror ); + $qformat->setCategory($category); + $qformat->setContexts($contexts->having_one_edit_tab_cap('import')); + $qformat->setCourse($COURSE); + $qformat->setFilename($importfile); + $qformat->setMatchgrades($form->matchgrades); + $qformat->setCatfromfile(!empty($form->catfromfile)); + $qformat->setContextfromfile(!empty($form->contextfromfile)); + $qformat->setStoponerror($form->stoponerror); // Do anything before that we need to - if (! $qformat->importpreprocess()) { - error( $txt->importerror, $thispageurl->out(false, array('category'=>$category->id))); + if (! $qformat->importpreprocess()) { + error($txt->importerror, $thispageurl->out()); } // Process the uploaded file - if (! $qformat->importprocess() ) { - error( $txt->importerror, $thispageurl->out(false, array('category'=>$category->id))); + if (! $qformat->importprocess()) { + error($txt->importerror, $thispageurl->out()); } // In case anything needs to be done after if (! $qformat->importpostprocess()) { - error( $txt->importerror, $thispageurl->out(false, array('category'=>$category->id))); + error($txt->importerror, $thispageurl->out()); } echo "
"; - print_continue("edit.php?".$thispageurl->get_query_string()); - print_footer($course); + print_continue("edit.php?".($thispageurl->get_query_string(array('category'=>"{$qformat->category->id},{$qformat->category->contextid}")))); + print_footer($COURSE); exit; } } - /// Print upload form - - // get list of available import formats - $fileformatnames = get_import_export_formats( 'import' ); - print_heading_with_help($txt->importquestions, "import", "quiz"); - /// Get all the existing categories now - $catmenu = question_category_options($course->id, false, true); - - //========== - // DISPLAY - //========== - - ?> - -
-
- - - hidden_params_out(array(), 3); ?> - - - - - - - - - - - - - - - - - - - -
category; ?>:id, ""); ?> - fromfile; ?> - - importcategory, 'quiz'); ?>
fileformat; ?>:importquestions, 'quiz'); ?>
matchgrades; ?>matchgradeserror,'' ); - helpbutton('matchgrades', $txt->matchgrades, 'quiz'); ?>
stoponerror; ?> - stoponerror, 'quiz'); ?>
- - importfileupload; ?> - - - - - - - - - - -
upload; ?>:maxbytes,0,false); ?>
 
- - importfilearea; ?> - - - - - - - - - - -
file; ?>:
 id}&choose=form.choosefile", - "coursefiles", $txt->choosefile, 500, 750, $txt->choosefile); ?> -
- -
-
- - display(); + print_footer($COURSE); ?> diff --git a/question/preview.php b/question/preview.php index ffd5b24f2d..8922ac69d6 100644 --- a/question/preview.php +++ b/question/preview.php @@ -38,12 +38,7 @@ $continue = false; } - require_login(); - // this might break things in the future - if (!isteacherinanycourse()) { - error('This page is for teachers only'); - } if (!$continue) { // Start a new attempt; delete the old session @@ -57,20 +52,23 @@ $url .= '&continue=1'; redirect($url); } - + // Load the question information + if (!$questions = get_records('question', 'id', $id)) { + error('Could not load question'); + } if (empty($quizid)) { $quiz = new cmoptions; $quiz->id = 0; $quiz->review = $CFG->quiz_review; - + require_login_in_context($questions[$id]->contextid); } else if (!$quiz = get_record('quiz', 'id', $quizid)) { error("Quiz id $quizid does not exist"); + } else { + require_login($quiz->course, false, get_coursemodule_from_instance('quiz', $quizid, $quiz->course)); } - // Load the question information - if (!$questions = get_records('question', 'id', $id)) { - error('Could not load question'); - } + + if ($maxgrade = get_field('quiz_question_instances', 'grade', 'quiz', $quiz->id, 'question', $id)) { $questions[$id]->maxgrade = $maxgrade; } else { @@ -84,10 +82,12 @@ error("This question doesn't belong to a valid category!"); } - if (!has_capability('moodle/question:manage', get_context_instance(CONTEXT_COURSE, $category->course)) and !$category->publish) { + if (!question_has_capability_on($questions[$id], 'use', $questions[$id]->category)){ error("You can't preview these questions!"); } - $quiz->course = $category->course; + if (isset($COURSE)){ + $quiz->course = $COURSE->id; + } // Load the question type specific information if (!get_question_options($questions)) { @@ -197,7 +197,7 @@ echo '
'; - + echo '
'; echo "\n"; echo "\n"; diff --git a/question/question.php b/question/question.php index 45e8ac9963..8079937c9b 100644 --- a/question/question.php +++ b/question/question.php @@ -13,23 +13,43 @@ require_once(dirname(__FILE__) . '/editlib.php'); require_once($CFG->libdir . '/filelib.php'); require_once($CFG->libdir . '/formslib.php'); -$returnurl = optional_param('returnurl', 0, PARAM_LOCALURL); - // Read URL parameters telling us which question to edit. $id = optional_param('id', 0, PARAM_INT); // question id -$cmid = optional_param('cmid', 0, PARAM_INT); $qtype = optional_param('qtype', '', PARAM_FILE); $categoryid = optional_param('category', 0, PARAM_INT); + +$cmid = optional_param('cmid', 0, PARAM_INT); +$courseid = optional_param('courseid', 0, PARAM_INT); $wizardnow = optional_param('wizardnow', '', PARAM_ALPHA); +$movecontext = optional_param('movecontext', 0, PARAM_BOOL);//switch to make question + //uneditable - form is displayed to edit question only +$returnurl = optional_param('returnurl', 0, PARAM_LOCALURL); + +if ($movecontext && !$id){ + print_error('questiondoesnotexist', 'question', $returnurl); +} if ($cmid){ - list($module, $cm) = get_module_from_cmid($cmid); -} else { + list($module, $cm) = get_module_from_cmid($cmid); + require_login($cm->course, false, $cm); + $thiscontext = get_context_instance(CONTEXT_MODULE, $cmid); +} elseif ($courseid) { + require_login($courseid, false); + $thiscontext = get_context_instance(CONTEXT_COURSE, $courseid); $module = null; $cm = null; +} else { + error('Need to pass courseid or cmid to this script.'); } +$contexts = new question_edit_contexts($thiscontext); + + +if (!$returnurl) { + $returnurl = "{$CFG->wwwroot}/question/edit.php?courseid={$COURSE->id}"; +} + + -// Validate the URL parameters. if ($id) { if (!$question = get_record('question', 'id', $id)) { print_error('questiondoesnotexist', 'question', $returnurl); @@ -47,79 +67,139 @@ if ($id) { if (!$category = get_record('question_categories', 'id', $question->category)) { print_error('categorydoesnotexist', 'question', $returnurl); } -if (!$returnurl) { - $returnurl = "{$CFG->wwwroot}/question/edit.php?courseid={$category->course}"; + +//permissions +$question->formoptions = new object(); +$permissionstrs = array(); + +$categorycontext = get_context_instance_by_id($category->contextid); +$addpermission = has_capability('moodle/question:add', $categorycontext); + +if ($id) { + $canview = question_has_capability_on($question, 'view'); + if ($movecontext){ + $question->formoptions->canedit = false; + $question->formoptions->canmove = (question_has_capability_on($question, 'move') && $contexts->have_cap('moodle/question:add')); + $question->formoptions->cansaveasnew = false; + $question->formoptions->repeatelements = false; + $question->formoptions->movecontext = true; + $formeditable = true; + question_require_capability_on($question, 'view'); + } else { + $question->formoptions->canedit = question_has_capability_on($question, 'edit'); + $question->formoptions->canmove = (question_has_capability_on($question, 'move') && $addpermission); + $question->formoptions->cansaveasnew = (($canview ||question_has_capability_on($question, 'edit')) && $addpermission); + $question->formoptions->repeatelements = ($question->formoptions->canedit || $question->formoptions->cansaveasnew); + $formeditable = $question->formoptions->canedit || $question->formoptions->cansaveasnew || $question->formoptions->canmove; + $question->formoptions->movecontext = false; + if (!$formeditable){ + question_require_capability_on($question, 'view'); + } + } + + +} else { // creating a new question + require_capability('moodle/question:add', $categorycontext); + $formeditable = true; + $question->formoptions->repeatelements = true; + $question->formoptions->movecontext = false; } +$question->category = "$category->id,$category->contextid"; +if ($formeditable && $id){ + $question->categorymoveto = $question->category; +} // Validate the question type. if (!isset($QTYPES[$question->qtype])) { print_error('unknownquestiontype', 'question', $returnurl, $question->qtype); } $CFG->pagepath = 'question/type/' . $question->qtype; -// Check the user is logged in and has enough premissions. -require_login($category->course, false); -$coursecontext = get_context_instance(CONTEXT_COURSE, $category->course); -require_capability('moodle/question:manage', $coursecontext); // Create the question editing form. -if ($wizardnow!==''){ +if ($wizardnow!=='' && !$movecontext){ if (!method_exists($QTYPES[$question->qtype], 'next_wizard_form')){ print_error('missingimportantcode', 'question', $returnurl, 'wizard form definition'); } else { - $mform = $QTYPES[$question->qtype]->next_wizard_form('question.php', $question, $wizardnow); + $mform = $QTYPES[$question->qtype]->next_wizard_form('question.php', $question, $wizardnow, $formeditable); } } else { - $mform = $QTYPES[$question->qtype]->create_editing_form('question.php', $question); + $mform = $QTYPES[$question->qtype]->create_editing_form('question.php', $question, $category, $contexts, $formeditable); } - if ($mform === null) { print_error('missingimportantcode', 'question', $returnurl, 'question editing form definition for "'.$question->qtype.'"'); } $toform = $question; // send the question object and a few more parameters to the form $toform->returnurl = $returnurl; +$toform->movecontext = $movecontext; if ($cm !== null){ $toform->cmid = $cm->id; + $toform->courseid = $cm->course; +} else { + $toform->courseid = $COURSE->id; } $mform->set_data($toform); if ($mform->is_cancelled()){ redirect($returnurl); } elseif ($data = $mform->get_data()){ + $returnurl = new moodle_url($returnurl); + //select category that question has been saved in / moved to when we return to question bank + if (!empty($data->categorymoveto)){ + $returnurl->param('category', $data->categorymoveto); + } else if (!empty($data->category)){ + $returnurl->param('category', $data->category); + } + $returnurl = $returnurl->out(); if (!empty($data->makecopy)) { $question->id = 0; // causes a new question to be created. $question->hidden = 0; // Copies should not be hidden } + if ($movecontext){ + list($tocatid, $tocontextid) = explode(',', $data->categorymoveto); + $tocontext = get_context_instance_by_id($tocontextid); + require_capability('moodle/question:add', $tocontext); + if (get_filesdir_from_context($categorycontext) != get_filesdir_from_context($tocontext)){ + $movecontexturl = new moodle_url($CFG->wwwroot.'/question/contextmoveq.php', + array('returnurl' => $returnurl, + 'ids'=>$question->id, + 'tocatid'=> $tocatid)); + if ($cmid){ + $movecontexturl->param('cmid', $cmid); + } else { + $movecontexturl->param('courseid', $COURSE->id); + } + redirect($movecontexturl->out()); + } + } $question = $QTYPES[$question->qtype]->save_question($question, $data, $COURSE, $wizardnow); - if ($QTYPES[$qtype]->finished_edit_wizard($data)){ + if ($QTYPES[$qtype]->finished_edit_wizard($data) || $movecontext){ + if (optional_param('inpopup', 0, PARAM_BOOL)) { notify(get_string('changessaved'), ''); close_window(3); } else { redirect($returnurl); } - die; } else { - //useful for passing data to the next page which is not saved in the database - $queryappend = ''; - if (isset($data->nextpageparam)){ - foreach ($data->nextpageparam as $key => $param){ - $queryappend .= "&".urlencode($key).'='.urlencode($param); - } - } + $nexturlparams = array('returnurl'=>$returnurl) + + $data->nextpageparam;//useful for passing data to the next page which is not saved in the database if ($question->id) { - $nexturl = "question.php?id=$question->id&returnurl=" . urlencode($returnurl); + $nexturlparams['id'] = $question->id; } else { // only for creating new questions - $nexturl = "question.php?category=$question->category&qtype=$question->qtype&returnurl=".urlencode($returnurl); + $nexturlparams['category'] = $question->category; + $nexturlparams['qtype'] =$question->qtype; } - redirect($nexturl.'&wizardnow='.$data->wizard.$queryappend, '', 20); + $nexturlparams['wizardnow'] = $data->wizard; + $nexturl = new moodle_url('question.php', $nexturlparams); + redirect($nexturl); } } else { list($streditingquestion,) = $QTYPES[$question->qtype]->get_heading(); if ($cm !== null) { - $strupdatemodule = has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $category->course)) - ? update_module_button($cm->id, $category->course, get_string('modulename', $cm->modname)) + $strupdatemodule = has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $COURSE->id)) + ? update_module_button($cm->id, $cm->course, get_string('modulename', $cm->modname)) : ""; $navlinks = array(); $navlinks[] = array('name' => get_string('modulenameplural', $cm->modname), 'link' => "$CFG->wwwroot/mod/{$cm->modname}/index.php?id=$category->course", 'type' => 'activity'); @@ -128,7 +208,7 @@ if ($mform->is_cancelled()){ $navlinks[] = array('name' => $streditingquestion, 'link' => '', 'type' => 'title'); $navigation = build_navigation($navlinks); print_header_simple($streditingquestion, '', $navigation, "", "", true, $strupdatemodule); - + } else { $navlinks = array(); $navlinks[] = array('name' => get_string('editquestions', "quiz"), 'link' => $returnurl, 'type' => 'title'); @@ -139,10 +219,10 @@ if ($mform->is_cancelled()){ print_header_simple($streditingquestion, '', $navigation); } + // Display a heading, question editing form and possibly some extra content needed for // for this question type. $QTYPES[$question->qtype]->display_question_editing_page($mform, $question, $wizardnow); - print_footer($COURSE); } ?> diff --git a/question/restorelib.php b/question/restorelib.php index 2c00b47e18..fa48d6ddef 100644 --- a/question/restorelib.php +++ b/question/restorelib.php @@ -85,82 +85,210 @@ include_once($CFG->libdir.'/questionlib.php'); - function restore_question_categories($category,$restore) { - - global $CFG; + /** + * Returns the best question category (id) found to restore one + * question category from a backup file. Works by stamp. + * + * @param object $restore preferences for restoration + * @param array $contextinfo fragment of decoded xml + * @return object best context instance for this category to be in + */ + function restore_question_get_best_category_context($restore, $contextinfo) { + switch ($contextinfo['LEVEL'][0]['#']) { + case 'module': + $instanceinfo = backup_getid($restore->backup_unique_code, 'course_modules', $contextinfo['INSTANCE'][0]['#']); + $tocontext = get_context_instance(CONTEXT_MODULE, $instanceinfo->new_id); + break; + case 'course': + $tocontext = get_context_instance(CONTEXT_COURSE, $restore->course_id); + break; + case 'coursecategory': + //search COURSECATEGORYLEVEL steps up the course cat tree or + //to the top of the tree if steps are exhausted. + $catno = $contextinfo['COURSECATEGORYLEVEL'][0]['#']; + $catid = get_field('course', 'parent', 'id', $restore->course_id); + while ($catno > 1){ + $nextcatid = get_field('course_categories', 'parent', 'id', $catid); + if ($nextcatid == 0){ + break; + } + $catid == $nextcatid; + $catno--; + } + $tocontext = get_context_instance(CONTEXT_COURSECAT, $catid); + break; + case 'system': + $tocontext = get_context_instance(CONTEXT_SYSTEM); + break; + } + return $tocontext; + } + function restore_question_categories($info, $restore) { $status = true; - - //Hook to call Moodle < 1.5 Quiz Restore - if ($restore->backup_version < 2005043000) { - include_once($CFG->dirroot.'/mod/quiz/restorelibpre15.php'); - return quiz_restore_pre15_question_categories($category,$restore); + //Iterate over each category + foreach ($info as $category) { + $status = $status && restore_question_category($category, $restore); } + $status = $status && restore_recode_category_parents($restore); + return $status; + } - //Get record from backup_ids - $data = backup_getid($restore->backup_unique_code,"question_categories",$category->id); - - if ($data) { - //Now get completed xmlized object - $info = $data->info; - //traverse_xmlize($info); //Debug - //print_object ($GLOBALS['traverse_array']); //Debug - //$GLOBALS['traverse_array']=""; //Debug + function restore_question_category($category, $restore){ + $status = true; + //Skip empty categories (some backups can contain them) + if (!empty($category->id)) { + //Get record from backup_ids + $data = backup_getid($restore->backup_unique_code, "question_categories", $category->id); + + if ($data) { + //Now get completed xmlized object + $info = $data->info; + //traverse_xmlize($info); //Debug + //print_object ($GLOBALS['traverse_array']); //Debug + //$GLOBALS['traverse_array']=""; //Debug - //Now, build the question_categories record structure - $question_cat = new stdClass; - $question_cat->course = $restore->course_id; - $question_cat->name = backup_todb($info['QUESTION_CATEGORY']['#']['NAME']['0']['#']); - $question_cat->info = backup_todb($info['QUESTION_CATEGORY']['#']['INFO']['0']['#']); - $question_cat->publish = backup_todb($info['QUESTION_CATEGORY']['#']['PUBLISH']['0']['#']); - $question_cat->stamp = backup_todb($info['QUESTION_CATEGORY']['#']['STAMP']['0']['#']); - $question_cat->parent = backup_todb($info['QUESTION_CATEGORY']['#']['PARENT']['0']['#']); - $question_cat->sortorder = backup_todb($info['QUESTION_CATEGORY']['#']['SORTORDER']['0']['#']); - - if ($catfound = restore_get_best_question_category($question_cat, $restore->course_id)) { - $newid = $catfound; - } else { + //Now, build the question_categories record structure + $question_cat = new stdClass; + $question_cat->name = backup_todb($info['QUESTION_CATEGORY']['#']['NAME']['0']['#']); + $question_cat->info = backup_todb($info['QUESTION_CATEGORY']['#']['INFO']['0']['#']); + $question_cat->stamp = backup_todb($info['QUESTION_CATEGORY']['#']['STAMP']['0']['#']); + //parent is fixed after all categories are restored and we know all the new ids. + $question_cat->parent = backup_todb($info['QUESTION_CATEGORY']['#']['PARENT']['0']['#']); + $question_cat->sortorder = backup_todb($info['QUESTION_CATEGORY']['#']['SORTORDER']['0']['#']); if (!$question_cat->stamp) { $question_cat->stamp = make_unique_id_code(); } - $newid = insert_record ("question_categories",$question_cat); - } + if (isset($info['QUESTION_CATEGORY']['#']['PUBLISH'])) { + $course = $restore->course_id; + $publish = backup_todb($info['QUESTION_CATEGORY']['#']['PUBLISH']['0']['#']); + if ($publish){ + $tocontext = get_context_instance(CONTEXT_SYSTEM); + } else { + $tocontext = get_context_instance(CONTEXT_COURSE, $course); + } + } else { + $tocontext = restore_question_get_best_category_context($restore, $info['QUESTION_CATEGORY']['#']['CONTEXT']['0']['#']); + } + $question_cat->contextid = $tocontext->id; + + //does cat exist ?? if it does we check if the cat and questions already exist whether we have + //add permission or not if we have no permission to add questions to SYSTEM or COURSECAT context + //AND the question does not already exist then we create questions in COURSE context. + if (!$fcat = get_record('question_categories','contextid', $question_cat->contextid, 'stamp', $question_cat->stamp)){ + //no preexisting cat + if ((($tocontext->contextlevel == CONTEXT_SYSTEM) || ($tocontext->contextlevel == CONTEXT_COURSECAT)) + && !has_capability('moodle/question:add', $tocontext)){ + //no preexisting cat and no permission to create questions here + //must restore to course. + $tocontext = get_context_instance(CONTEXT_COURSE, $restore->course_id); + } + $question_cat->contextid = $tocontext->id; + if (!$fcat = get_record('question_categories','contextid', $question_cat->contextid, 'stamp', $question_cat->stamp)){ + $question_cat->id = insert_record ("question_categories", $question_cat); + } else { + $question_cat = $fcat; + } + //we'll be restoring all questions here. + backup_putid($restore->backup_unique_code, "question_categories", $category->id, $question_cat->id); + } else { + $question_cat = $fcat; + //we found an existing best category + //but later if context is above course need to check if there are questions need creating in category + //if we do need to create questions and permissions don't allow it create new category in course + } - //Do some output - if ($newid) { + //Do some output if (!defined('RESTORE_SILENTLY')) { echo "
  • ".get_string('category', 'quiz')." \"".$question_cat->name."\"
    "; } - } else { - //We must never arrive here !! + + backup_flush(300); + + //start with questions + if ($question_cat->id) { + //We have the newid, update backup_ids + //Now restore question + $status = restore_questions($category->id, $question_cat, $info, $restore); + } else { + $status = false; + } if (!defined('RESTORE_SILENTLY')) { - echo "
  • ".get_string('category', 'quiz')." \"".$question_cat->name."\" Error!
    "; + echo '
  • '; } - $status = false; + } else { + echo 'Could not get backup info for question category'. $category->id; } - backup_flush(300); + } + return $status; + } - //Here category has been created or selected, so save results in backup_ids and start with questions - if ($newid and $status) { - //We have the newid, update backup_ids - backup_putid($restore->backup_unique_code,"question_categories", - $category->id, $newid); - //Now restore question - $status = restore_questions ($category->id, $newid,$info,$restore); - } else { - $status = false; + function restore_recode_category_parents($restore){ + global $CFG; + $status = true; + //Now we have to recode the parent field of each restored category + $categories = get_records_sql("SELECT old_id, new_id + FROM {$CFG->prefix}backup_ids + WHERE backup_code = $restore->backup_unique_code AND + table_name = 'question_categories'"); + if ($categories) { + //recode all parents to point at their old parent cats no matter what context the parent is now in + foreach ($categories as $category) { + $restoredcategory = get_record('question_categories','id',$category->new_id); + if ($restoredcategory->parent != 0) { + $updateobj = new object(); + $updateobj->id = $restoredcategory->id; + $idcat = backup_getid($restore->backup_unique_code,'question_categories',$restoredcategory->parent); + if ($idcat->new_id) { + $updateobj->parent = $idcat->new_id; + } else { + $updateobj->parent = 0; + } + $status = $status && update_record('question_categories', $updateobj); + } } - if (!defined('RESTORE_SILENTLY')) { - echo ''; + //now we have recoded all parents, check through all parents and set parent to be + //grand parent / great grandparent etc where there is one in same context + //or else set parent to 0 (top level category). + $toupdate = array(); + foreach ($categories as $category) { + $restoredcategory = get_record('question_categories','id',$category->new_id); + if ($restoredcategory->parent != 0) { + $nextparentid = $restoredcategory->parent; + do { + if (!$parent = get_record('question_categories', 'id', $nextparentid)){ + if (!defined('RESTORE_SILENTLY')) { + echo 'Could not find parent for question category '. $category->id.' recoding as top category item.
    '; + } + break;//record fetch failed finish loop + } else { + $nextparentid = $nextparent->parent; + } + } while (($nextparentid != 0) && ($parent->contextid != $restoredcategory->contextid)); + if (!$parent || ($parent->id != $restoredcategory->parent)){ + //change needs to be made to the parent field. + if ($parent && ($parent->contextid == $restoredcategory->contextid)){ + $toupdate[$restoredcategory->id] = $parent->id; + } else { + //searched up the tree till we came to the top and did not find cat in same + //context or there was an error getting next parent record + $toupdate[$restoredcategory->id] = 0; + } + } + } + } + //now finally do the changes to parent field. + foreach ($toupdate as $id => $parent){ + $updateobj = new object(); + $updateobj->id = $id; + $updateobj->parent = $parent; + $status = $status && update_record('question_categories', $updateobj); } - } else { - echo 'Could not get backup info for question category'. $category->id; } - return $status; } - function restore_questions ($old_category_id,$new_category_id,$info,$restore) { + function restore_questions ($old_category_id, $best_question_cat, $info, $restore) { global $CFG, $QTYPES; @@ -186,7 +314,6 @@ //Now, build the question record structure $question = new object; - $question->category = $new_category_id; $question->parent = backup_todb($que_info['#']['PARENT']['0']['#']); $question->name = backup_todb($que_info['#']['NAME']['0']['#']); $question->questiontext = backup_todb($que_info['#']['QUESTIONTEXT']['0']['#']); @@ -204,6 +331,10 @@ $question->stamp = backup_todb($que_info['#']['STAMP']['0']['#']); $question->version = backup_todb($que_info['#']['VERSION']['0']['#']); $question->hidden = backup_todb($que_info['#']['HIDDEN']['0']['#']); + $question->timecreated = backup_todb($que_info['#']['TIMECREATED']['0']['#']); + $question->timemodified = backup_todb($que_info['#']['TIMEMODIFIED']['0']['#']); + $question->createdby = backup_todb($que_info['#']['CREATEDBY']['0']['#']); + $question->modifiedby = backup_todb($que_info['#']['MODIFIEDBY']['0']['#']); if ($restore->backup_version < 2006032200) { // The qtype was an integer that now needs to be converted to the name @@ -213,36 +344,65 @@ $question->qtype = $qtypenames[$question->qtype]; } - //Check if the question exists - //by category, stamp, and version - $question_exists = get_record ("question","category",$question->category, - "stamp",$question->stamp,"version",$question->version); - + //Check if the question exists by category, stamp, and version + //first check for the question in the context specified in backup + $existingquestion = get_record ("question", "category", $best_question_cat->id, "stamp", $question->stamp,"version",$question->version); //If the question exists, only record its id - if ($question_exists) { - $newid = $question_exists->id; + //always use existing question, no permissions check here + if ($existingquestion) { + $question = $existingquestion; $creatingnewquestion = false; - //Else, create a new question } else { - //The structure is equal to the db, so insert the question - $newid = insert_record ("question",$question); - $creatingnewquestion = true; + //then if context above course level check permissions and if no permission + //to restore above course level then restore to cat in course context. + $bestcontext = get_context_instance_by_id($best_question_cat->contextid); + if (($bestcontext->contextlevel == CONTEXT_SYSTEM || $bestcontext->contextlevel == CONTEXT_COURSECAT) + && !has_capability('moodle/question:add', $bestcontext)){ + if (!isset($course_question_cat)) { + $coursecontext = get_context_instance(CONTEXT_COURSE, $restore->course_id); + $course_question_cat = clone($best_question_cat); + $course_question_cat->contextid = $coursecontext->id; + //create cat if it doesn't exist + if (!$fcat = get_record('question_categories','contextid', $course_question_cat->contextid, 'stamp', $course_question_cat->stamp)){ + $course_question_cat->id = insert_record ("question_categories", $course_question_cat); + backup_putid($restore->backup_unique_code, "question_categories", $old_category_id, $course_question_cat->id); + } else { + $course_question_cat = $fcat; + } + //will fix category parents after all questions and categories restored. Will set parent to 0 if + //no parent in same context. + } + $question->category = $course_question_cat->id; + //does question already exist in course cat + $existingquestion = get_record ("question", "category", $question->category, "stamp", $question->stamp, "version", $question->version); + } else { + //permissions ok, restore to best cat + $question->category = $best_question_cat->id; + } + if (!$existingquestion){ + //The structure is equal to the db, so insert the question + $question->id = insert_record ("question", $question); + $creatingnewquestion = true; + } else { + $question = $existingquestion; + $creatingnewquestion = false; + } } //Save newid to backup tables - if ($newid) { + if ($question->id) { //We have the newid, update backup_ids - backup_putid($restore->backup_unique_code,"question",$oldid, - $newid); + backup_putid($restore->backup_unique_code, "question", $oldid, $question->id); } $restored_questions[$i] = new stdClass; - $restored_questions[$i]->newid = $newid; + $restored_questions[$i]->newid = $question->id; $restored_questions[$i]->oldid = $oldid; $restored_questions[$i]->qtype = $question->qtype; - $restored_questions[$i]->parent = $question->parent; + $restored_questions[$i]->parent = $question->parent; $restored_questions[$i]->is_new = $creatingnewquestion; } + backup_flush(300); // Loop again, now all the question id mappings exist, so everything can // be restored. @@ -270,7 +430,7 @@ echo 'Could not recode parent '.$question->parent.' for question '.$oldid.'
    '; } } - + //Now, restore every question_answers in this question $status = question_restore_answers($oldid,$newid,$que_info,$restore); // Restore questiontype specific data @@ -314,7 +474,7 @@ $status = true; $qtype = backup_todb($info['#']['QTYPE']['0']['#']); - + //Get the answers array if (isset($info['#']['ANSWERS']['0']['#']['ANSWER'])) { $answers = $info['#']['ANSWERS']['0']['#']['ANSWER']; @@ -449,20 +609,20 @@ //print_object ($GLOBALS['traverse_array']); //Debug //$GLOBALS['traverse_array']=""; //Debug - // Check to see if this until already exists in the database, which it might, for + // Check to see if this until already exists in the database, which it might, for // Historical reasons. $unit = backup_todb($nu_info['#']['UNIT']['0']['#']); if (!record_exists('question_numerical_units', 'question', $new_question_id, 'unit', $unit)) { - + //Now, build the question_numerical_UNITS record structure. $numerical_unit = new stdClass; $numerical_unit->question = $new_question_id; $numerical_unit->multiplier = backup_todb($nu_info['#']['MULTIPLIER']['0']['#']); $numerical_unit->unit = $unit; - + //The structure is equal to the db, so insert the question_numerical_units $newid = insert_record("question_numerical_units", $numerical_unit); - + if (!$newid) { $status = false; } @@ -701,9 +861,9 @@ if ($res_info['#']['MANUALCOMMENT']['0']['#']) { $session->manualcomment = backup_todb($res_info['#']['MANUALCOMMENT']['0']['#']); } else { // pre 1.7 backups - $session->manualcomment = backup_todb($res_info['#']['COMMENT']['0']['#']); + $session->manualcomment = backup_todb($res_info['#']['COMMENT']['0']['#']); } - + //We have to recode the question field $question = backup_getid($restore->backup_unique_code,"question",$session->questionid); if ($question) { @@ -757,12 +917,23 @@ } $extraprocessing = array(); + $coursemodulecontexts = array(); + $context = get_context_instance(CONTEXT_COURSE, $restore->course_id); + $coursemodulecontexts[] = $context->id; + $cms = get_records('course_modules', 'course', $restore->course_id, '', 'id'); + if ($cms){ + foreach ($cms as $cm){ + $context = get_context_instance(CONTEXT_MODULE, $cm->id); + $coursemodulecontexts[] = $context->id; + } + } + $coursemodulecontextslist = join($coursemodulecontexts, ','); // Decode links in questions. - if ($questions = get_records_sql('SELECT q.id, q.qtype, q.questiontext, q.generalfeedback - FROM ' . $CFG->prefix . 'question q, - ' . $CFG->prefix . 'question_categories qc - WHERE q.category = qc.id - AND qc.course = ' . $restore->course_id)) { + if ($questions = get_records_sql('SELECT q.id, q.qtype, q.questiontext, q.generalfeedback '. + 'FROM ' . $CFG->prefix . 'question q, '. + $CFG->prefix . 'question_categories qc '. + 'WHERE q.category = qc.id '. + 'AND qc.contextid IN (' .$coursemodulecontextslist.')')) { foreach ($questions as $question) { $questiontext = restore_decode_content_links_worker($question->questiontext, $restore); @@ -800,8 +971,8 @@ ' . $CFG->prefix . 'question q, ' . $CFG->prefix . 'question_categories qc WHERE qa.question = q.id - AND q.category = qc.id - AND qc.course = ' . $restore->course_id)) { + AND q.category = qc.id '. + 'AND qc.contextid IN ('.$coursemodulecontextslist.')')) { foreach ($answers as $answer) { $feedback = restore_decode_content_links_worker($answer->feedback, $restore); diff --git a/question/tabs.php b/question/tabs.php index da5847a38b..a64ace1b56 100644 --- a/question/tabs.php +++ b/question/tabs.php @@ -11,16 +11,17 @@ if (!isset($currenttab)) { $currenttab = ''; } - if (!isset($course)) { + if (!isset($COURSE)) { error('No course specified'); } $tabs = array(); $inactive = array(); $row = array(); - questionbank_navigation_tabs($row, $context, 'courseid='.$course->id); + questionbank_navigation_tabs($row, $contexts, 'courseid='.$COURSE->id); $tabs[] = $row; print_tabs($tabs, $currenttab, array()); + ?> diff --git a/question/type/calculated/edit_calculated_form.php b/question/type/calculated/edit_calculated_form.php index 255f4312b7..30c2c3a1cc 100644 --- a/question/type/calculated/edit_calculated_form.php +++ b/question/type/calculated/edit_calculated_form.php @@ -34,14 +34,14 @@ class question_edit_calculated_form extends question_edit_form { $addfieldsname='updatecategory'; $addstring=get_string("updatecategory", "qtype_calculated"); $mform->registerNoSubmitButton($addfieldsname); - + $mform->insertElementBefore( $mform->createElement('submit', $addfieldsname, $addstring),'listcategory'); $repeated = array(); $repeated[] =& $mform->createElement('header', 'answerhdr', get_string('answerhdr', 'qtype_calculated', '{no}')); $repeated[] =& $mform->createElement('text', 'answers', get_string('correctanswerformula', 'quiz').'=', array('size' => 50)); - $repeatedoptions['answers']['type'] = PARAM_NOTAGS; + $repeatedoptions['answers']['type'] = PARAM_NOTAGS; $creategrades = get_grade_options(); $gradeoptions = $creategrades->gradeoptions; @@ -59,7 +59,8 @@ class question_edit_calculated_form extends question_edit_form { $answerlengthformats = array('1' => get_string('decimalformat', 'quiz'), '2' => get_string('significantfiguresformat', 'quiz')); $repeated[] =& $mform->createElement('select', 'correctanswerformat', get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats); - $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz')); + $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz'), + array('course' => $this->coursefilesid)); $repeatedoptions['feedback']['type'] = PARAM_RAW; if (isset($this->question->options)){ @@ -67,10 +68,14 @@ class question_edit_calculated_form extends question_edit_form { } else { $count = 0; } - $repeatsatstart = $count + 1; + if ($this->question->formoptions->repeatelements){ + $repeatsatstart = $count + 1; + } else { + $repeatsatstart = $count; + } $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions, 'noanswers', 'addanswers', 1, get_string('addmoreanswerblanks', 'qtype_calculated')); - $repeated = array(); + $repeated = array(); $repeated[] =& $mform->createElement('header', 'unithdr', get_string('unithdr', 'qtype_numerical', '{no}')); $repeated[] =& $mform->createElement('text', 'unit', get_string('unit', 'quiz')); @@ -84,7 +89,11 @@ class question_edit_calculated_form extends question_edit_form { } else { $countunits = 0; } - $repeatsatstart = $countunits + 1; + if ($this->question->formoptions->repeatelements){ + $repeatsatstart = $countunits + 1; + } else { + $repeatsatstart = $countunits; + } $this->repeat_elements($repeated, $repeatsatstart, array(), 'nounits', 'addunits', 2, get_string('addmoreunitblanks', 'qtype_calculated', '{no}')); $firstunit =& $mform->getElement('multiplier[0]'); @@ -117,7 +126,7 @@ class question_edit_calculated_form extends question_edit_form { } $units = array_values($question->options->units); // make sure the default unit is at index 0 - usort($units, create_function('$a, $b', + usort($units, create_function('$a, $b', 'if (1.0 === (float)$a->multiplier) { return -1; } else '. 'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }')); if (count($units)) { @@ -131,21 +140,21 @@ class question_edit_calculated_form extends question_edit_form { } $default_values['submitbutton'] = get_string('nextpage', 'qtype_calculated'); $default_values['makecopy'] = get_string('makecopynextpage', 'qtype_calculated'); - /* set the wild cards category display given that on loading the category element is - unselected when processing this function but have a valid value when processing the + /* set the wild cards category display given that on loading the category element is + unselected when processing this function but have a valid value when processing the update category button. The value can be obtain by $qu->category =$this->_form->_elements[$this->_form->_elementIndex['category']]->_values[0]; but is coded using existing functions - */ + */ $qu = new stdClass; $el = new stdClass; - /* no need to call elementExists() here */ - $el=$this->_form->getElement('category'); + /* no need to call elementExists() here */ + $el=$this->_form->getElement('category'); if($value =$el->getSelected()) { $qu->category =$value[0]; }else { $qu->category=$question->category;// on load $question->category is set by question.php - } + } $html2 = $this->qtypeobj->print_dataset_definitions_category($qu); $this->_form->_elements[$this->_form->_elementIndex['listcategory']]->_text = $html2 ; $question = (object)((array)$question + $default_values); @@ -158,7 +167,7 @@ class question_edit_calculated_form extends question_edit_form { } function validation($data){ - $errors = array(); + $errors = parent::validation($data); //verifying for errors in {=...} in question text; $qtext = ""; $qtextremaining = $data['questiontext'] ; @@ -166,7 +175,7 @@ class question_edit_calculated_form extends question_edit_form { foreach ($possibledatasets as $name => $value) { $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining); } - // echo "numericalquestion qtextremaining
    ";print_r($possibledatasets); 
    +    //     echo "numericalquestion qtextremaining 
    ";print_r($possibledatasets);
             while  (ereg('\{=([^[:space:]}]*)}', $qtextremaining, $regs1)) {
                 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
                 $qtext =$qtext.$qtextsplits[0];
    @@ -176,9 +185,9 @@ class question_edit_calculated_form extends question_edit_form {
                         $errors['questiontext'] = $formulaerrors.':'.$regs1[1] ;
                     }else {
                         $errors['questiontext'] .= '
    '.$formulaerrors.':'.$regs1[1]; - } + } } - } + } $answers = $data['answers']; $answercount = 0; $maxgrade = false; @@ -186,13 +195,13 @@ class question_edit_calculated_form extends question_edit_form { $mandatorydatasets = array(); foreach ($answers as $key => $answer){ $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer); - } + } if ( count($mandatorydatasets )==0){ // $errors['questiontext']=get_string('atleastonewildcard', 'qtype_datasetdependent'); foreach ($answers as $key => $answer){ $errors['answers['.$key.']'] = get_string('atleastonewildcard', 'qtype_datasetdependent'); - } - } + } + } foreach ($answers as $key => $answer){ //check no of choices // the * for everykind of answer not actually implemented diff --git a/question/type/datasetdependent/abstractqtype.php b/question/type/datasetdependent/abstractqtype.php index 8e2bdd9db2..54edd15fc0 100644 --- a/question/type/datasetdependent/abstractqtype.php +++ b/question/type/datasetdependent/abstractqtype.php @@ -197,16 +197,16 @@ class question_dataset_dependent_questiontype extends default_questiontype { /** * This method prepare the $datasets in a format similar to dadatesetdefinitions_form.php - * so that they can be saved + * so that they can be saved * using the function save_dataset_definitions($form) - * when creating a new calculated question or + * when creating a new calculated question or * whenediting an already existing calculated question - * or by function save_as_new_dataset_definitions($form, $initialid) + * or by function save_as_new_dataset_definitions($form, $initialid) * when saving as new an already existing calculated question - * + * * @param object $form * @param int $questionfromid default = '0' - */ + */ function preparedatasets(&$form , $questionfromid='0'){ // the dataset names present in the edit_question_form and edit_calculated_form are retrieved $possibledatasets = $this->find_dataset_names($form->questiontext); @@ -215,7 +215,7 @@ class question_dataset_dependent_questiontype extends default_questiontype { $mandatorydatasets += $this->find_dataset_names($answer); } // if there are identical datasetdefs already saved in the original question. - // either when editing a question or saving as new + // either when editing a question or saving as new // they are retrieved using $questionfromid if ($questionfromid!='0'){ $form->id = $questionfromid ; @@ -237,7 +237,7 @@ class question_dataset_dependent_questiontype extends default_questiontype { // they will defined and stored with datasetdefinitions_form.php // the $options are not used here if ($questionfromid!='0'){ - + foreach ($possibledatasets as $datasetname) { if (!isset($datasets[$datasetname])) { list($options, $selected) = @@ -253,15 +253,15 @@ class question_dataset_dependent_questiontype extends default_questiontype { /** * this version save the available data at the different steps of the question editing process * without using global $SESSION as storage between steps - * at the first step $wizardnow = 'question' + * at the first step $wizardnow = 'question' * when creating a new question * when modifying a question * when copying as a new question * the general parameters and answers are saved using parent::save_question - * then the datasets are prepared and saved - * at the second step $wizardnow = 'datasetdefinitions' + * then the datasets are prepared and saved + * at the second step $wizardnow = 'datasetdefinitions' * the datadefs final type are defined as private, category or not a datadef - * at the third step $wizardnow = 'datasetitems' + * at the third step $wizardnow = 'datasetitems' * the datadefs parameters and the data items are created or defined * * @param object question @@ -272,11 +272,11 @@ class question_dataset_dependent_questiontype extends default_questiontype { function save_question($question, $form, $course) { $wizardnow = optional_param('wizardnow', '', PARAM_ALPHA); $id = optional_param('id', 0, PARAM_INT); // question id - // in case 'question' - // for a new question $form->id is empty - // when saving as new question - // $question->id = 0, $form is $data from question2.php - // and $data->makecopy is defined as $data->id is the initial question id + // in case 'question' + // for a new question $form->id is empty + // when saving as new question + // $question->id = 0, $form is $data from question2.php + // and $data->makecopy is defined as $data->id is the initial question id // edit case. If it is a new question we don't necessarily need to // return a valid question object @@ -290,23 +290,24 @@ class question_dataset_dependent_questiontype extends default_questiontype { $this->preparedatasets($form); $form->id = $question->id; $this->save_dataset_definitions($form); - } else if (!empty($form->makecopy)){ + } else if (!empty($form->makecopy)){ $questionfromid = $form->id ; $question = parent::save_question($question, $form, $course); //prepare the datasets - $this->preparedatasets($form,$questionfromid); + $this->preparedatasets($form,$questionfromid); $form->id = $question->id; $this->save_as_new_dataset_definitions($form,$questionfromid ); - } else {// editing a question + } else {// editing a question $question = parent::save_question($question, $form, $course); //prepare the datasets $this->preparedatasets($form,$question->id); - $form->id = $question->id; + $form->id = $question->id; $this->save_dataset_definitions($form); } break; case 'datasetdefinitions': - $this->save_dataset_definitions($form); + + $this->save_dataset_definitions($form); break; case 'datasetitems': $this->save_dataset_items($question, $form); @@ -369,7 +370,7 @@ class question_dataset_dependent_questiontype extends default_questiontype { $datasetdef = &$datasetdefinitions[$defid]; if (isset($datasetdef->id)) { if (!isset($tmpdatasets[$defid])) { - // This dataset is not used any more, delete it + // This dataset is not used any more, delete it delete_records('question_datasets', 'question', $form->id, 'datasetdefinition', $datasetdef->id); @@ -441,12 +442,12 @@ class question_dataset_dependent_questiontype extends default_questiontype { } /** This function create a copy of the datasets ( definition and dataitems) * from the preceding question if they remain in the new question - * otherwise its create the datasets that have been added as in the + * otherwise its create the datasets that have been added as in the * save_dataset_definitions() */ function save_as_new_dataset_definitions($form, $initialid) { global $CFG ; - // Get the datasets from the intial question + // Get the datasets from the intial question $datasetdefinitions = $this->get_dataset_definitions($initialid, $form->dataset); // $tmpdatasets contains those of the new question $tmpdatasets = array_flip($form->dataset); @@ -469,7 +470,7 @@ class question_dataset_dependent_questiontype extends default_questiontype { 'question_dataset_definitions', $datasetdef)) { error("Unable to create dataset $defid"); } - //copy the dataitems + //copy the dataitems $olditems = get_records_sql( // Use number as key!! " SELECT itemnumber, value FROM {$CFG->prefix}question_dataset_items @@ -481,15 +482,15 @@ class question_dataset_dependent_questiontype extends default_questiontype { if (!insert_record('question_dataset_items', $item)) { error("Unable to insert dataset item $item->itemnumber with $item->value for $datasetdef->name"); } - $itemcount++; + $itemcount++; } //update item count $datasetdef->itemcount =$itemcount; - update_record('question_dataset_definitions', $datasetdef); - } // end of copy the dataitems + update_record('question_dataset_definitions', $datasetdef); + } // end of copy the dataitems }// end of copy the datasetdef - // Create relation to the new question with this - // copy as new datasetdef from the initial question + // Create relation to the new question with this + // copy as new datasetdef from the initial question $questiondataset = new stdClass; $questiondataset->question = $form->id; $questiondataset->datasetdefinition = $datasetdef->id; diff --git a/question/type/datasetdependent/datasetdefinitions_form.php b/question/type/datasetdependent/datasetdefinitions_form.php index c2c7bc4f11..d6ae8c72a3 100644 --- a/question/type/datasetdependent/datasetdefinitions_form.php +++ b/question/type/datasetdependent/datasetdefinitions_form.php @@ -101,6 +101,15 @@ class question_dataset_dependent_definitions_form extends moodleform { $mform->addElement('hidden', 'category'); $mform->setType('category', PARAM_INT); $mform->addElement('hidden', 'id'); + + $mform->addElement('hidden', 'courseid'); + $mform->setType('courseid', PARAM_INT); + $mform->setDefault('courseid', 0); + + $mform->addElement('hidden', 'cmid'); + $mform->setType('cmid', PARAM_INT); + $mform->setDefault('cmid', 0); + $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'wizard', 'datasetitems'); $mform->setType('wizard', PARAM_ALPHA); diff --git a/question/type/datasetdependent/datasetitems_form.php b/question/type/datasetdependent/datasetitems_form.php index ce9853e9e5..c9defa801e 100644 --- a/question/type/datasetdependent/datasetitems_form.php +++ b/question/type/datasetdependent/datasetitems_form.php @@ -156,6 +156,15 @@ class question_dataset_dependent_items_form extends moodleform { $mform->setType('qtype', PARAM_ALPHA); $mform->addElement('hidden', 'category'); $mform->setType('category', PARAM_INT); + + $mform->addElement('hidden', 'courseid'); + $mform->setType('courseid', PARAM_INT); + $mform->setDefault('courseid', 0); + + $mform->addElement('hidden', 'cmid'); + $mform->setType('cmid', PARAM_INT); + $mform->setDefault('cmid', 0); + $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'wizard', 'datasetitems'); diff --git a/question/type/description/questiontype.php b/question/type/description/questiontype.php index 7244bf8698..1ba123698f 100644 --- a/question/type/description/questiontype.php +++ b/question/type/description/questiontype.php @@ -19,7 +19,7 @@ class description_qtype extends default_questiontype { function name() { return 'description'; } - + function is_usable_by_random() { return false; } @@ -46,7 +46,7 @@ class description_qtype extends default_questiontype { // For editing teachers print a link to an editing popup window $editlink = ''; - if (has_capability('moodle/question:manage', get_context_instance(CONTEXT_COURSE, $cmoptions->course))) { + if (question_has_capability_on($question, 'edit')) { $stredit = get_string('edit'); $linktext = ''.$stredit.''; $editlink = link_to_popup_window('/question/question.php?id='.$question->id, $stredit, $linktext, 450, 550, $stredit, '', true); diff --git a/question/type/edit_question_form.php b/question/type/edit_question_form.php index 3a08e8bb92..e105bb1679 100644 --- a/question/type/edit_question_form.php +++ b/question/type/edit_question_form.php @@ -14,7 +14,7 @@ * all question types need. Question types should define their own * class that inherits from this one, and implements the definition_inner() * method. - * + * * @package questionbank * @subpackage questiontypes */ @@ -28,9 +28,25 @@ class question_edit_form extends moodleform { */ var $question; - function question_edit_form($submiturl, $question){ + var $contexts; + var $category; + var $categorycontext; + var $coursefilesid; + + function question_edit_form($submiturl, $question, $category, $contexts, $formeditable = true){ + $this->question = $question; - parent::moodleform($submiturl); + + $this->contexts = $contexts; + + $this->category = $category; + $this->categorycontext = get_context_instance_by_id($category->contextid); + + //course id or site id depending on question cat context + $this->coursefilesid = get_filesdir_from_context(get_context_instance_by_id($category->contextid)); + + parent::moodleform($submiturl, null, 'post', '', null, $formeditable); + } /** @@ -51,22 +67,53 @@ class question_edit_form extends moodleform { // Standard fields at the start of the form. $mform->addElement('header', 'generalheader', get_string("general", 'form')); - $mform->addElement('questioncategory', 'category', get_string('category', 'quiz'), null, - array('courseid' => $COURSE->id, 'published' => true, 'only_editable' => true)); + if (!isset($this->question->id)){ + //adding question + $mform->addElement('questioncategory', 'category', get_string('category', 'quiz'), + array('contexts' => array($this->categorycontext))); + } elseif (!($this->question->formoptions->canmove || $this->question->formoptions->cansaveasnew)){ + //editing question with no permission to move from category. + $mform->addElement('questioncategory', 'category', get_string('category', 'quiz'), + array('contexts' => array($this->categorycontext))); + } elseif ($this->question->formoptions->movecontext){ + //moving question to another context. + $mform->addElement('questioncategory', 'categorymoveto', get_string('category', 'quiz'), + array('contexts' => $this->contexts->having_cap('moodle/question:add'))); + + } else { + //editing question with permission to move from category or save as new q + $currentgrp = array(); + $currentgrp[0] =& $mform->createElement('questioncategory', 'category', get_string('categorycurrent', 'question'), + array('contexts' => array($this->categorycontext))); + if ($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew){ + //not move only form + $currentgrp[1] =& $mform->createElement('checkbox', 'usecurrentcat', '', get_string('categorycurrentuse', 'question')); + $mform->setDefault('usecurrentcat', 1); + } + $currentgrp[0]->freeze(); + $currentgrp[0]->setPersistantFreeze(false); + $mform->addGroup($currentgrp, 'currentgrp', get_string('categorycurrent', 'question'), null, false); + + $mform->addElement('questioncategory', 'categorymoveto', get_string('categorymoveto', 'question'), + array('contexts' => array($this->categorycontext))); + if ($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew){ + //not move only form + $mform->disabledIf('categorymoveto', 'usecurrentcat', 'checked'); + } + } - $mform->addElement('text', 'name', get_string('questionname', 'quiz'), - array('size' => 50)); + $mform->addElement('text', 'name', get_string('questionname', 'quiz'), array('size' => 50)); $mform->setType('name', PARAM_TEXT); $mform->addRule('name', null, 'required', null, 'client'); $mform->addElement('htmleditor', 'questiontext', get_string('questiontext', 'quiz'), - array('rows' => 15, 'course' => $COURSE->id)); + array('rows' => 15, 'course' => $this->coursefilesid)); $mform->setType('questiontext', PARAM_RAW); $mform->setHelpButton('questiontext', array(array('questiontext', get_string('questiontext', 'quiz'), 'quiz'), 'richtext'), false, 'editorhelpbutton'); $mform->addElement('format', 'questiontextformat', get_string('format')); - make_upload_directory("$COURSE->id"); // Just in case - $coursefiles = get_directory_list("$CFG->dataroot/$COURSE->id", $CFG->moddata); + make_upload_directory($this->coursefilesid); // Just in case + $coursefiles = get_directory_list("$CFG->dataroot/$this->coursefilesid", $CFG->moddata); foreach ($coursefiles as $filename) { if (mimeinfo("icon", $filename) == "image.gif") { $images["$filename"] = $filename; @@ -92,13 +139,33 @@ class question_edit_form extends moodleform { $mform->setDefault('penalty', 0.1); $mform->addElement('htmleditor', 'generalfeedback', get_string('generalfeedback', 'quiz'), - array('rows' => 10, 'course' => $COURSE->id)); + array('rows' => 10, 'course' => $this->coursefilesid)); $mform->setType('generalfeedback', PARAM_RAW); $mform->setHelpButton('generalfeedback', array('generalfeedback', get_string('generalfeedback', 'quiz'), 'quiz')); // Any questiontype specific fields. $this->definition_inner($mform); + + if (!empty($this->question->id)){ + $mform->addElement('header', 'createdmodifiedheader', get_string('createdmodifiedheader', 'question')); + $a = new object(); + if (!empty($this->question->createdby)){ + $a->time = userdate($this->question->timecreated); + $a->user = fullname(get_record('user', 'id', $this->question->createdby)); + } else { + $a->time = get_string('unknown', 'question'); + $a->user = get_string('unknown', 'question'); + } + $mform->addElement('static', 'created', get_string('created', 'question'), get_string('byandon', 'question', $a)); + if (!empty($this->question->modifiedby)){ + $a = new object(); + $a->time = userdate($this->question->timemodified); + $a->user = fullname(get_record('user', 'id', $this->question->modifiedby)); + $mform->addElement('static', 'modified', get_string('modified', 'question'), get_string('byandon', 'question', $a)); + } + } + // Standard fields at the end of the form. $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); @@ -112,22 +179,46 @@ class question_edit_form extends moodleform { $mform->addElement('hidden', 'versioning'); $mform->setType('versioning', PARAM_BOOL); + $mform->addElement('hidden', 'movecontext'); + $mform->setType('movecontext', PARAM_BOOL); + $mform->addElement('hidden', 'cmid'); $mform->setType('cmid', PARAM_INT); $mform->setDefault('cmid', 0); + $mform->addElement('hidden', 'courseid'); + $mform->setType('courseid', PARAM_INT); + $mform->setDefault('courseid', 0); + $mform->addElement('hidden', 'returnurl'); $mform->setType('returnurl', PARAM_LOCALURL); - $mform->setDefault('returnurl', ''); + $mform->setDefault('returnurl', 0); $buttonarray = array(); - $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges')); - if (!empty($this->question->id)) { - $buttonarray[] = &$mform->createElement('submit', 'makecopy', get_string('makecopy', 'quiz')); + if (!empty($this->question->id)){ + //editing / moving question + if ($this->question->formoptions->movecontext){ + $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('moveq', 'question')); + } elseif ($this->question->formoptions->canedit || $this->question->formoptions->canmove ||$this->question->formoptions->movecontext){ + $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges')); + } + if ($this->question->formoptions->cansaveasnew){ + $buttonarray[] = &$mform->createElement('submit', 'makecopy', get_string('makecopy', 'quiz')); + } + $buttonarray[] = &$mform->createElement('cancel'); + } else { + // adding new question + $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges')); + $buttonarray[] = &$mform->createElement('cancel'); } - $buttonarray[] = &$mform->createElement('cancel'); $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false); $mform->closeHeaderBefore('buttonar'); + + if ($this->question->formoptions->movecontext){ + $mform->hardFreezeAllVisibleExcept(array('categorymoveto', 'buttonar')); + } elseif ((!empty($this->question->id)) && (!($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew))){ + $mform->hardFreezeAllVisibleExcept(array('categorymoveto', 'buttonar', 'currentgrp')); + } } /** @@ -167,6 +258,7 @@ class question_edit_form extends moodleform { function qtype() { return ''; } + } ?> \ No newline at end of file diff --git a/question/type/essay/edit_essay_form.php b/question/type/essay/edit_essay_form.php index 4b989c08bc..60972b11ef 100644 --- a/question/type/essay/edit_essay_form.php +++ b/question/type/essay/edit_essay_form.php @@ -19,7 +19,8 @@ class question_edit_essay_form extends question_edit_form { * @param MoodleQuickForm $mform the form being built. */ function definition_inner(&$mform) { - $mform->addElement('htmleditor', 'feedback', get_string("feedback", "quiz")); + $mform->addElement('htmleditor', 'feedback', get_string("feedback", "quiz"), + array('course' => $this->coursefilesid)); $mform->setType('feedback', PARAM_RAW); $mform->addElement('hidden', 'fraction', 0); diff --git a/question/type/match/edit_match_form.php b/question/type/match/edit_match_form.php index 97373b24b1..c05302993b 100644 --- a/question/type/match/edit_match_form.php +++ b/question/type/match/edit_match_form.php @@ -36,8 +36,12 @@ class question_edit_match_form extends question_edit_form { } else { $countsubquestions = 0; } - $repeatsatstart = (QUESTION_NUMANS_START > ($countsubquestions + QUESTION_NUMANS_ADD))? - QUESTION_NUMANS_START : ($countsubquestions + QUESTION_NUMANS_ADD); + if ($this->question->formoptions->repeatelements){ + $repeatsatstart = (QUESTION_NUMANS_START > ($countsubquestions + QUESTION_NUMANS_ADD))? + QUESTION_NUMANS_START : ($countsubquestions + QUESTION_NUMANS_ADD); + } else { + $repeatsatstart = $countsubquestions; + } $mform->setType('subanswer', PARAM_TEXT); $mform->setType('subquestion', PARAM_TEXT); @@ -67,7 +71,7 @@ class question_edit_match_form extends question_edit_form { } function validation($data){ - $errors = array(); + $errors = parent::validation($data); $answers = $data['subanswers']; $questions = $data['subquestions']; $questioncount = 0; diff --git a/question/type/match/questiontype.php b/question/type/match/questiontype.php index 352c3c5fad..baa600b28e 100644 --- a/question/type/match/questiontype.php +++ b/question/type/match/questiontype.php @@ -616,7 +616,7 @@ class question_match_qtype extends default_questiontype { /** * Decode links in question type specific tables. * @return bool success or failure. - */ + */ function decode_content_links_caller($questionids, $restore, &$i) { $status = true; @@ -646,6 +646,38 @@ class question_match_qtype extends default_questiontype { return $status; } + + function find_file_links($question, $courseid){ + // find links in the question_match_sub table. + $urls = array(); + foreach ($question->options->subquestions as $subquestion) { + $urls += question_find_file_links_from_html($subquestion->questiontext, $courseid); + } + + //set all the values of the array to the question object + if ($urls){ + $urls = array_combine(array_keys($urls), array_fill(0, count($urls), array($question->id))); + } + $urls = array_merge_recursive($urls, parent::find_file_links($question, $courseid)); + return $urls; + } + + function replace_file_links($question, $fromcourseid, $tocourseid, $url, $destination){ + parent::replace_file_links($question, $fromcourseid, $tocourseid, $url, $destination); + // replace links in the question_match_sub table. + if (isset($question->options->subquestions)){ + foreach ($question->options->subquestions as $subquestion) { + $subquestionchanged = false; + $subquestion->questiontext = question_replace_file_links_in_html($subquestion->questiontext, $fromcourseid, $tocourseid, $url, $destination, $subquestionchanged); + if ($subquestionchanged){//need to update rec in db + if (!update_record('question_match_sub', addslashes_recursive($subquestion))) { + error('Couldn\'t update \'question_match_sub\' record '.$subquestion->id); + } + + } + } + } + } } //// END OF CLASS //// diff --git a/question/type/missingtype/edit_missingtype_form.php b/question/type/missingtype/edit_missingtype_form.php index 6cef3ad894..396f0616e7 100644 --- a/question/type/missingtype/edit_missingtype_form.php +++ b/question/type/missingtype/edit_missingtype_form.php @@ -25,15 +25,20 @@ class question_edit_missingtype_form extends question_edit_form { $repeated[] =& $mform->createElement('header', 'choicehdr', get_string('choiceno', 'qtype_multichoice', '{no}')); $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz')); $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions); - $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz')); + $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz'), + array('course' => $this->coursefilesid)); if (isset($this->question->options)){ $countanswers = count($this->question->options->answers); } else { $countanswers = 0; } - $repeatsatstart = (QUESTION_NUMANS_START > ($countanswers + QUESTION_NUMANS_ADD))? - QUESTION_NUMANS_START : ($countanswers + QUESTION_NUMANS_ADD); + if ($this->question->formoptions->repeatelements){ + $repeatsatstart = (QUESTION_NUMANS_START > ($countanswers + QUESTION_NUMANS_ADD))? + QUESTION_NUMANS_START : ($countanswers + QUESTION_NUMANS_ADD); + } else { + $repeatsatstart = $countanswers; + } $repeatedoptions = array(); $repeatedoptions['fraction']['default'] = 0; $mform->setType('answer', PARAM_NOTAGS); @@ -62,7 +67,7 @@ class question_edit_missingtype_form extends question_edit_form { } function validation($data){ - $errors = array(); + $errors = parent::validation($data); $answers = $data['answer']; $answercount = 0; diff --git a/question/type/multianswer/edit_multianswer_form.php b/question/type/multianswer/edit_multianswer_form.php index 683b3df6c7..6c784480f4 100644 --- a/question/type/multianswer/edit_multianswer_form.php +++ b/question/type/multianswer/edit_multianswer_form.php @@ -76,7 +76,7 @@ class question_edit_multianswer_form extends question_edit_form { function validation($data){ //TODO would be nice to parse the question text here and output some error //messages if there is a problem with the text. - $errors = array(); + $errors = parent::validation($data); //extra check to make sure there is something in the htmlarea besides a
    $questiontext= trim(strip_tags($data['questiontext'])); if ($questiontext==''){ diff --git a/question/type/multianswer/questiontype.php b/question/type/multianswer/questiontype.php index 77bc15572b..51697a1875 100644 --- a/question/type/multianswer/questiontype.php +++ b/question/type/multianswer/questiontype.php @@ -671,6 +671,7 @@ function qtype_multianswer_extract_question($text) { } else if(!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE])) { $wrapped->qtype = 'multichoice'; $wrapped->single = 1; + $wrapped->answernumbering = 0; $wrapped->correctfeedback = ''; $wrapped->partiallycorrectfeedback = ''; $wrapped->incorrectfeedback = ''; diff --git a/question/type/multichoice/edit_multichoice_form.php b/question/type/multichoice/edit_multichoice_form.php index 3879986a75..2e0fc3b1e5 100644 --- a/question/type/multichoice/edit_multichoice_form.php +++ b/question/type/multichoice/edit_multichoice_form.php @@ -20,7 +20,7 @@ class question_edit_multichoice_form extends question_edit_form { */ function definition_inner(&$mform) { global $QTYPES; - + $menu = array(get_string('answersingleno', 'qtype_multichoice'), get_string('answersingleyes', 'qtype_multichoice')); $mform->addElement('select', 'single', get_string('answerhowmany', 'qtype_multichoice'), $menu); $mform->setDefault('single', 1); @@ -46,15 +46,20 @@ class question_edit_multichoice_form extends question_edit_form { $repeated[] =& $mform->createElement('header', 'choicehdr', get_string('choiceno', 'qtype_multichoice', '{no}')); $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz'), array('size' => 50)); $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions); - $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz')); + $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz'), + array('course' => $this->coursefilesid)); if (isset($this->question->options)){ $countanswers = count($this->question->options->answers); } else { $countanswers = 0; } - $repeatsatstart = (QUESTION_NUMANS_START > ($countanswers + QUESTION_NUMANS_ADD))? - QUESTION_NUMANS_START : ($countanswers + QUESTION_NUMANS_ADD); + if ($this->question->formoptions->repeatelements){ + $repeatsatstart = (QUESTION_NUMANS_START > ($countanswers + QUESTION_NUMANS_ADD))? + QUESTION_NUMANS_START : ($countanswers + QUESTION_NUMANS_ADD); + } else { + $repeatsatstart = $countanswers; + } $repeatedoptions = array(); $repeatedoptions['fraction']['default'] = 0; $mform->setType('answer', PARAM_RAW); @@ -62,13 +67,16 @@ class question_edit_multichoice_form extends question_edit_form { $mform->addElement('header', 'overallfeedbackhdr', get_string('overallfeedback', 'qtype_multichoice')); - $mform->addElement('htmleditor', 'correctfeedback', get_string('correctfeedback', 'qtype_multichoice')); + $mform->addElement('htmleditor', 'correctfeedback', get_string('correctfeedback', 'qtype_multichoice'), + array('course' => $this->coursefilesid)); $mform->setType('correctfeedback', PARAM_RAW); - $mform->addElement('htmleditor', 'partiallycorrectfeedback', get_string('partiallycorrectfeedback', 'qtype_multichoice')); + $mform->addElement('htmleditor', 'partiallycorrectfeedback', get_string('partiallycorrectfeedback', 'qtype_multichoice'), + array('course' => $this->coursefilesid)); $mform->setType('partiallycorrectfeedback', PARAM_RAW); - $mform->addElement('htmleditor', 'incorrectfeedback', get_string('incorrectfeedback', 'qtype_multichoice')); + $mform->addElement('htmleditor', 'incorrectfeedback', get_string('incorrectfeedback', 'qtype_multichoice'), + array('course' => $this->coursefilesid)); $mform->setType('incorrectfeedback', PARAM_RAW); } @@ -101,7 +109,7 @@ class question_edit_multichoice_form extends question_edit_form { } function validation($data){ - $errors = array(); + $errors = parent::validation($data); $answers = $data['answer']; $answercount = 0; diff --git a/question/type/multichoice/questiontype.php b/question/type/multichoice/questiontype.php index 5dd0910d12..965921006d 100644 --- a/question/type/multichoice/questiontype.php +++ b/question/type/multichoice/questiontype.php @@ -395,7 +395,7 @@ class question_multichoice_qtype extends default_questiontype { function response_summary($question, $state, $length = 80) { return implode(',', $this->get_actual_response($question, $state)); } - + /// BACKUP FUNCTIONS //////////////////////////// /* @@ -555,7 +555,7 @@ class question_multichoice_qtype extends default_questiontype { /** * Decode links in question type specific tables. * @return bool success or failure. - */ + */ function decode_content_links_caller($questionids, $restore, &$i) { $status = true; @@ -601,7 +601,7 @@ class question_multichoice_qtype extends default_questiontype { function get_numbering_styles() { return array('abc', 'ABC', '123', 'none'); } - + function number_html($qnum) { return '' . $qnum . '. '; } @@ -625,6 +625,36 @@ class question_multichoice_qtype extends default_questiontype { return 'ERR'; } } + + function find_file_links($question, $courseid){ + $urls = array(); + $urls = parent::find_file_links($question, $courseid); + // find links in the question_match_sub table. + foreach ($question->options->subquestions as $subquestion) { + $urls += question_find_file_links_from_html($subquestion->questiontext, $courseid); + + } + //set all the values of the array to the question id + if ($urls){ + $urls = array_combine(array_keys($urls), array_fill(0, count($urls), array($question->id))); + } + $urls = array_merge_recursive($urls, parent::find_file_links($question, $courseid)); + return $urls; + } + + function replace_file_links($question, $fromcourseid, $tocourseid, $url, $destination){ + parent::replace_file_links($question, $fromcourseid, $tocourseid, $url, $destination); + // replace links in the question_match_sub table. + $optionschanged = false; + $question->options->correctfeedback = question_replace_file_links_in_html($question->options->correctfeedback, $fromcourseid, $tocourseid, $url, $destination, $optionschanged); + $question->options->partiallycorrectfeedback = question_replace_file_links_in_html($question->options->partiallycorrectfeedback, $fromcourseid, $tocourseid, $url, $destination, $optionschanged); + $question->options->incorrectfeedback = question_replace_file_links_in_html($question->options->incorrectfeedback, $fromcourseid, $tocourseid, $url, $destination, $optionschanged); + if ($optionschanged){ + if (!update_record('question_multichoice', addslashes_recursive($question->options))) { + error('Couldn\'t update \'question_multichoice\' record '.$question->options->id); + } + } + } } // Register this question type with the question bank. diff --git a/question/type/numerical/edit_numerical_form.php b/question/type/numerical/edit_numerical_form.php index 102369d862..3b256b0291 100644 --- a/question/type/numerical/edit_numerical_form.php +++ b/question/type/numerical/edit_numerical_form.php @@ -36,7 +36,8 @@ class question_edit_numerical_form extends question_edit_form { $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions); $repeatedoptions['fraction']['default'] = 0; - $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz')); + $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz'), + array('course' => $this->coursefilesid)); $mform->setType('feedback', PARAM_RAW); @@ -45,9 +46,12 @@ class question_edit_numerical_form extends question_edit_form { } else { $countanswers = 0; } - $repeatsatstart = (QUESTION_NUMANS_START > ($countanswers + 1))? - QUESTION_NUMANS_START : ($countanswers + 1); - + if ($this->question->formoptions->repeatelements){ + $repeatsatstart = (QUESTION_NUMANS_START > ($countanswers + 1))? + QUESTION_NUMANS_START : ($countanswers + 1); + } else { + $repeatsatstart = $countanswers; + } $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions, 'noanswers', 'addanswers', 2, get_string('addmoreanswerblanks', 'qtype_numerical')); //------------------------------------------------------------------------------------------ @@ -65,7 +69,12 @@ class question_edit_numerical_form extends question_edit_form { } else { $countunits = 0; } - $repeatsatstart = $countunits + 2; + + if ($this->question->formoptions->repeatelements){ + $repeatsatstart = $countunits + 2; + } else { + $repeatsatstart = $countunits; + } $this->repeat_elements($repeated, $repeatsatstart, array(), 'nounits', 'addunits', 2, get_string('addmoreunitblanks', 'qtype_numerical')); $firstunit =& $mform->getElement('multiplier[0]'); @@ -99,7 +108,7 @@ class question_edit_numerical_form extends question_edit_form { parent::set_data($question); } function validation($data){ - $errors = array(); + $errors = parent::validation($data); // Check the answers. $answercount = 0; diff --git a/question/type/questiontype.php b/question/type/questiontype.php index 9c6499f7c3..a7ad604cd3 100644 --- a/question/type/questiontype.php +++ b/question/type/questiontype.php @@ -9,7 +9,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package questionbank * @subpackage questiontypes - *//** */ + */ require_once($CFG->libdir . '/questionlib.php'); @@ -25,7 +25,7 @@ require_once($CFG->libdir . '/questionlib.php'); * experiences of the first few question type implementors, and improve the * interface to meet their needs, rather the freeze the API prematurely and * condem everyone to working round a clunky interface for ever afterwards. - * + * * @package questionbank * @subpackage questiontypes */ @@ -75,7 +75,7 @@ class default_questiontype { } /** - * @return whether the question_answers.answer field needs to have + * @return whether the question_answers.answer field needs to have * restore_decode_content_links_worker called on it. */ function has_html_answers() { @@ -114,7 +114,7 @@ class default_questiontype { * @param string $submiturl passed on to the constructor call. * @return object an instance of the form definition, or null if one could not be found. */ - function create_editing_form($submiturl, $question) { + function create_editing_form($submiturl, $question, $category, $contexts, $formeditable) { global $CFG; require_once("{$CFG->dirroot}/question/type/edit_question_form.php"); $definition_file = $CFG->dirroot.'/question/type/'.$this->name().'/edit_'.$this->name().'_form.php'; @@ -126,7 +126,7 @@ class default_questiontype { if (!class_exists($classname)) { return null; } - return new $classname($submiturl, $question); + return new $classname($submiturl, $question, $category, $contexts, $formeditable); } /** @@ -154,25 +154,51 @@ class default_questiontype { * @param string $wizardnow is '' for first page. */ function display_question_editing_page(&$mform, $question, $wizardnow){ - list($heading, $langmodule) = $this->get_heading(); + list($heading, $langmodule) = $this->get_heading(empty($question->id)); print_heading_with_help($heading, $this->name(), $langmodule); + $permissionstrs = array(); + if (!empty($question->id)){ + if ($question->formoptions->canedit){ + $permissionstrs[] = get_string('permissionedit', 'question'); + } + if ($question->formoptions->canmove){ + $permissionstrs[] = get_string('permissionmove', 'question'); + } + if ($question->formoptions->cansaveasnew){ + $permissionstrs[] = get_string('permissionsaveasnew', 'question'); + } + } + if (!$question->formoptions->movecontext && count($permissionstrs)){ + print_heading(get_string('permissionto', 'question'), 'center', 3); + $html = '
      '; + foreach ($permissionstrs as $permissionstr){ + $html .= '
    • '.$permissionstr.'
    • '; + } + $html .= '
    '; + print_box($html, 'boxwidthnarrow boxaligncenter generalbox'); + } $mform->display(); } - + /** * Method called by display_question_editing_page and by question.php to get heading for breadcrumbs. - * + * * @return array a string heading and the langmodule in which it was found. */ - function get_heading(){ + function get_heading($adding = false){ $name = $this->name(); $langmodule = 'qtype_' . $name; - $strheading = get_string('editing' . $name, $langmodule); + if (!$adding){ + $strtoget = 'editing' . $name; + } else { + $strtoget = 'adding' . $name; + } + $strheading = get_string($strtoget, $langmodule); if ($strheading[0] == '[') { // Legacy behavior, if the string was not in the proper qtype_name // language file, look it up in the quiz one. $langmodule = 'quiz'; - $strheading = get_string('editing' . $name, $langmodule); + $strheading = get_string($strtoget, $langmodule); } return array($strheading, $langmodule); } @@ -204,14 +230,11 @@ class default_questiontype { * is itself an object, shown next to the form fields. */ function save_question($question, $form, $course) { + global $USER; // This default implementation is suitable for most // question types. // First, save the basic question itself - if (!record_exists('question_categories', 'id', $form->category)) { - print_error('categorydoesnotexist', 'question'); - } - $question->category = $form->category; $question->name = trim($form->name); $question->questiontext = trim($form->questiontext); $question->questiontextformat = $form->questiontextformat; @@ -247,16 +270,28 @@ class default_questiontype { } if (!empty($question->id)) { // Question already exists + if (isset($form->categorymoveto)){ + question_require_capability_on($question, 'move'); + list($question->categorymoveto, $movetocontextid) = explode(',', $form->categorymoveto); + } + if (isset($question->qtype) && $question->qtype != RANDOM){ + $question->category = $question->categorymoveto; + } // keep existing unique stamp code $question->stamp = get_field('question', 'stamp', 'id', $question->id); - if (!update_record("question", $question)) { - error("Could not update question!"); + $question->modifiedby = $USER->id; + $question->timemodified = time(); + if (!update_record('question', $question)) { + error('Could not update question!'); } } else { // Question is a new one // Set the unique code + list($question->category,$contextid) = explode(',', $form->category); $question->stamp = make_unique_id_code(); - if (!$question->id = insert_record("question", $question)) { - error("Could not insert new question!"); + $question->createdby = $USER->id; + $question->timecreated = time(); + if (!$question->id = insert_record('question', $question)) { + error('Could not insert new question!'); } } @@ -305,7 +340,7 @@ class default_questiontype { if (is_array($extra_question_fields)) { $question_extension_table = array_shift($extra_question_fields); - + $function = 'update_record'; $options = get_record($question_extension_table, 'questionid', $question->id); if (!$options) { @@ -322,7 +357,7 @@ class default_questiontype { } $options->$field = $question->$field; } - + if (!$function($question_extension_table, $options)) { $result = new stdClass; $result->error = 'Could not save question options for ' . @@ -333,7 +368,7 @@ class default_questiontype { $extra_answer_fields = $this->extra_answer_fields(); // TODO save the answers, with any extra data. - + return null; } @@ -687,13 +722,13 @@ class default_questiontype { * then this method will return an array of tags that reference * those stylesheets. This function will also call require_js() * from ajaxlib.php, to get any necessary JavaScript linked in too. - * + * * The two parameters match the first two parameters of print_question. - * + * * @param object $question The question object. * @param object $state The state object. - * - * @return an array of bits of HTML to add to the head of pages where + * + * @return an array of bits of HTML to add to the head of pages where * this question is print_question-ed in the body. The array should use * integer array keys, which have no significance. */ @@ -703,13 +738,14 @@ class default_questiontype { // Core question types should not use this mechanism. Their styles // should be included in the standard theme. - // We only do this once + + // We only do this once // for this question type, no matter how often this method is called. if ($this->already_done) { return array(); } $this->already_done = true; - + $plugindir = $this->plugin_dir(); $baseurl = $this->plugin_baseurl(); $stylesheets = array(); @@ -732,7 +768,7 @@ class default_questiontype { } return $contributions; } - + /** * Prints the question including the number, grading details, content, * feedback and interactions @@ -782,7 +818,7 @@ class default_questiontype { // For editing teachers print a link to an editing popup window $editlink = ''; - if ($context && has_capability('moodle/question:manage', $context)) { + if (question_has_capability_on($question, 'edit')) { $stredit = get_string('edit'); $linktext = ''.$stredit.''; $editlink = link_to_popup_window('/question/question.php?inpopup=1&id='.$question->id, 'editquestion', $linktext, 450, 550, $stredit, '', true); @@ -1040,7 +1076,7 @@ class default_questiontype { if (($cmoptions->optionflags & QUESTION_ADAPTIVE) and !$options->readonly) { echo 'id, "'; return true;", '" />'; } } @@ -1345,6 +1381,111 @@ class default_questiontype { return format_text($text, $textformat, $formatoptions, $cmoptions === NULL ? NULL : $cmoptions->course); } + /* + * Find all course / site files linked from a question. + * + * Need to check for links to files in question_answers.answer and feedback + * and in question table in generalfeedback and questiontext fields. Methods + * on child classes will also check extra question specific fields. + * + * Needs to be overriden for child classes that have extra fields containing + * html. + * + * @param string html the html to search + * @param int courseid search for files for courseid course or set to siteid for + * finding site files. + * @return array of url, relative url is key and array with one item = question id as value + * relative url is relative to course/site files directory root. + */ + function find_file_links($question, $courseid){ + $urls = array(); + if ($question->image != ''){ + if (substr(strtolower($question->image), 0, 7) == 'http://') { + $matches = array(); + + //support for older questions where we have a complete url in image field + if (preg_match('!^'.question_file_links_base_url($courseid).'(.*)!i', $question->image, $matches)){ + if ($cleanedurl = question_url_check($urls[$matches[2]])){ + $urls[$cleanedurl] = null; + } + } + } else { + if ($question->image != ''){ + if ($cleanedurl = question_url_check($question->image)){ + $urls[$cleanedurl] = null;//will be set later + } + } + + } + + } + $urls += question_find_file_links_from_html($question->questiontext, $courseid); + $urls += question_find_file_links_from_html($question->generalfeedback, $courseid); + if ($this->has_html_answers() && isset($question->options->answers)){ + foreach ($question->options->answers as $answerkey => $answer){ + $thisurls= question_find_file_links_from_html($answer->answer, $courseid); + if ($thisurls){ + $urls += $thisurls; + } + } + } + //set all the values of the array to the question object + if ($urls){ + $urls = array_combine(array_keys($urls), array_fill(0, count($urls), array($question->id))); + } + return $urls; + } + /* + * Find all course / site files linked from a question. + * + * Need to check for links to files in question_answers.answer and feedback + * and in question table in generalfeedback and questiontext fields. Methods + * on child classes will also check extra question specific fields. + * + * Needs to be overriden for child classes that have extra fields containing + * html. + * + * @param string html the html to search + * @param int course search for files for courseid course or set to siteid for + * finding site files. + * @return array of files, file name is key and array with one item = question id as value + */ + function replace_file_links($question, $fromcourseid, $tocourseid, $url, $destination){ + global $CFG; + $updateqrec = false; + if (!empty($question->image)){ + //support for older questions where we have a complete url in image field + if (substr(strtolower($question->image), 0, 7) == 'http://') { + $questionimage = preg_replace('!^'.question_file_links_base_url($fromcourseid).preg_quote($url, '!').'$!i', $destination, $question->image, 1); + } else { + $questionimage = preg_replace('!^'.preg_quote($url, '!').'$!i', $destination, $question->image, 1); + } + if ($questionimage != $question->image){ + $question->image = $questionimage; + $updateqrec = true; + } + } + $question->questiontext = question_replace_file_links_in_html($question->questiontext, $fromcourseid, $tocourseid, $url, $destination, $updateqrec); + $question->generalfeedback = question_replace_file_links_in_html($question->generalfeedback, $fromcourseid, $tocourseid, $url, $destination, $updateqrec); + if ($updateqrec){ + if (!update_record('question', addslashes_recursive($question))){ + error ('Couldn\'t update question '.$question->name); + } + } + + if ($this->has_html_answers() && isset($question->options->answers)){ + //answers that do not need updating have been unset + foreach ($question->options->answers as $answer){ + $answerchanged = false; + $answer->answer = question_replace_file_links_in_html($answer->answer, $fromcourseid, $tocourseid, $url, $destination, $answerchanged); + if ($answerchanged){ + if (!update_record('question_answers', addslashes_recursive($answer))){ + error ('Couldn\'t update question ('.$question->name.') answer '.$answer->id); + } + } + } + } + } /** * @return the best link to pass to print_error. * @param $cmoptions as passed in from outside. diff --git a/question/type/random/edit_random_form.php b/question/type/random/edit_random_form.php index 0da3436d25..2a078a52a5 100644 --- a/question/type/random/edit_random_form.php +++ b/question/type/random/edit_random_form.php @@ -31,8 +31,8 @@ class question_edit_random_form extends question_edit_form { // Standard fields at the start of the form. $mform->addElement('header', 'generalheader', get_string("general", 'form')); - $mform->addElement('questioncategory', 'category', get_string('category', 'quiz'), - array('courseid' => $COURSE->id, 'published' => true, 'only_editable' => true)); + $mform->addElement('questioncategory', 'category', get_string('category', 'quiz'), + array('contexts' => $this->contexts->having_cap('moodle/question:useall'))); $mform->addElement('text', 'name', get_string('questionname', 'quiz'), array('size' => 50)); @@ -57,6 +57,18 @@ class question_edit_random_form extends question_edit_form { $mform->addElement('hidden', 'versioning'); $mform->setType('versioning', PARAM_BOOL); + $mform->addElement('hidden', 'cmid'); + $mform->setType('cmid', PARAM_INT); + $mform->setDefault('cmid', 0); + + $mform->addElement('hidden', 'courseid'); + $mform->setType('courseid', PARAM_INT); + $mform->setDefault('courseid', 0); + + $mform->addElement('hidden', 'returnurl'); + $mform->setType('returnurl', PARAM_LOCALURL); + $mform->setDefault('returnurl', 0); + $buttonarray = array(); $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges')); diff --git a/question/type/randomsamatch/edit_randomsamatch_form.php b/question/type/randomsamatch/edit_randomsamatch_form.php index ce22a4d68b..6786842384 100644 --- a/question/type/randomsamatch/edit_randomsamatch_form.php +++ b/question/type/randomsamatch/edit_randomsamatch_form.php @@ -49,7 +49,7 @@ class question_edit_randomsamatch_form extends question_edit_form { function validation($data){ global $QTYPES; - $errors = array(); + $errors = parent::validation($data); $saquestions = $QTYPES['randomsamatch']->get_sa_candidates($data['category']); $numberavailable = count($saquestions); if ($saquestions === false){ diff --git a/question/type/shortanswer/edit_shortanswer_form.php b/question/type/shortanswer/edit_shortanswer_form.php index 6fdb563e1f..89a8d181b9 100644 --- a/question/type/shortanswer/edit_shortanswer_form.php +++ b/question/type/shortanswer/edit_shortanswer_form.php @@ -31,15 +31,20 @@ class question_edit_shortanswer_form extends question_edit_form { $repeated[] =& $mform->createElement('header', 'answerhdr', get_string('answerno', 'qtype_shortanswer', '{no}')); $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz'), array('size' => 54)); $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions); - $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz')); + $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz'), + array('course' => $this->coursefilesid)); if (isset($this->question->options)){ $countanswers = count($this->question->options->answers); } else { $countanswers = 0; } - $repeatsatstart = (QUESTION_NUMANS_START > ($countanswers + QUESTION_NUMANS_ADD))? - QUESTION_NUMANS_START : ($countanswers + QUESTION_NUMANS_ADD); + if ($this->question->formoptions->repeatelements){ + $repeatsatstart = (QUESTION_NUMANS_START > ($countanswers + QUESTION_NUMANS_ADD))? + QUESTION_NUMANS_START : ($countanswers + QUESTION_NUMANS_ADD); + } else { + $repeatsatstart = $countanswers; + } $repeatedoptions = array(); $mform->setType('answer', PARAM_RAW); $repeatedoptions['fraction']['default'] = 0; @@ -65,7 +70,7 @@ class question_edit_shortanswer_form extends question_edit_form { parent::set_data($question); } function validation($data){ - $errors = array(); + $errors = parent::validation($data); $answers = $data['answer']; $answercount = 0; $maxgrade = false; diff --git a/question/type/truefalse/edit_truefalse_form.php b/question/type/truefalse/edit_truefalse_form.php index 1e13002268..0567e9d191 100644 --- a/question/type/truefalse/edit_truefalse_form.php +++ b/question/type/truefalse/edit_truefalse_form.php @@ -23,10 +23,12 @@ class question_edit_truefalse_form extends question_edit_form { $mform->addElement('select', 'correctanswer', get_string('correctanswer', 'qtype_truefalse'), array(0 => get_string('false', 'qtype_truefalse'), 1 => get_string('true', 'qtype_truefalse'))); - $mform->addElement('htmleditor', 'feedbacktrue', get_string('feedbacktrue', 'qtype_truefalse')); + $mform->addElement('htmleditor', 'feedbacktrue', get_string('feedbacktrue', 'qtype_truefalse'), + array('course' => $this->coursefilesid));; $mform->setType('feedbacktrue', PARAM_RAW); - $mform->addElement('htmleditor', 'feedbackfalse', get_string('feedbackfalse', 'qtype_truefalse')); + $mform->addElement('htmleditor', 'feedbackfalse', get_string('feedbackfalse', 'qtype_truefalse'), + array('course' => $this->coursefilesid)); $mform->setType('feedbackfalse', PARAM_RAW); // Fix penalty factor at 1. diff --git a/question/upgrade.php b/question/upgrade.php index e9d1cd2b28..efcc2335dd 100644 --- a/question/upgrade.php +++ b/question/upgrade.php @@ -8,18 +8,18 @@ * @author T.J.Hunt@open.ac.uk * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package package_name - *//** */ + */ /** * This test is becuase the RQP question type was included in core * up to and including Moodle 1.8, and was removed before Moodle 1.9. - * + * * Therefore, we want to check whether any rqp questions exist in the database - * before doing the upgrade. However, the check is not relevant if that - * question type was never installed, or if the person has chosen to + * before doing the upgrade. However, the check is not relevant if that + * question type was never installed, or if the person has chosen to * manually reinstall the rqp question type from contrib. - * - * @param $version the version to test. + * + * @param $result the result object that can be modified. * @return null if the test is irrelevant, or true or false depending on whether the test passes. */ function question_check_no_rqp_questions($result) { @@ -37,24 +37,24 @@ function question_remove_rqp_qtype() { global $CFG; $result = true; - + // Only remove the question type if the code is gone. if (!is_dir($CFG->dirroot . '/question/type/rqp')) { $table = new XMLDBTable('question_rqp_states'); $result = $result && drop_table($table); - + $table = new XMLDBTable('question_rqp'); $result = $result && drop_table($table); - + $table = new XMLDBTable('question_rqp_types'); $result = $result && drop_table($table); - + $table = new XMLDBTable('question_rqp_servers'); $result = $result && drop_table($table); - + $result = $result && unset_config('qtype_rqp_version'); } - + return $result; } @@ -67,7 +67,257 @@ function question_remove_rqp_qtype_config_string() { if (!empty($CFG->qtype_rqp_version) && !is_dir($CFG->dirroot . '/question/type/rqp')) { $result = $result && unset_config('qtype_rqp_version'); } - + + return $result; +} + +/** + * @param $result the result object that can be modified. + * @return null if the test is irrelevant, or true or false depending on whether the test passes. + */ +function question_random_check($result){ + global $CFG; + if ($CFG->version >= 2007081000){ + return null;//no test after upgrade seperates question cats into contexts. + } + if (!$toupdate = question_cwqpfs_to_update()){ + $result->setStatus(true);//pass test + } else { + //set the feedback string here and not in xml file since we need something + //more complex than just a string picked from admin.php lang file + $a = new object(); + $a->reporturl = "{$CFG->wwwroot}/{$CFG->admin}/report/question/"; + $lang = str_replace('_utf8', '', current_language()); + $a->docsurl = "{$CFG->docroot}/$lang/admin/report/question/index"; + $result->feedback_str = get_string('questioncwqpfscheck', 'admin', $a); + $result->setStatus(false);//fail test + } + return $result; +} +/* + * Delete all 'random' questions that are not been used in a quiz. + */ +function question_delete_unused_random(){ + global $CFG; + $tofix = array(); + $result = true; + //delete all 'random' questions that are not been used in a quiz. + if ($qqis = get_records_sql("SELECT q.* FROM {$CFG->prefix}question as q LEFT JOIN ". + "({$CFG->prefix}quiz_question_instances as qqi) ". + "ON (q.id = qqi.question) WHERE q.qtype='random' AND qqi.question IS NULL")){ + $qqilist = join(array_keys($qqis), ','); + $result = $result && delete_records_select('question', "id IN ($qqilist)"); + } + return $result; +} +function question_cwqpfs_to_update($categories = null){ + global $CFG; + + $tofix = array(); + $result = true; + + //any cats with questions picking from subcats? + if (!$cwqpfs = get_records_sql_menu("SELECT DISTINCT qc.id, 1 ". + "FROM {$CFG->prefix}question as q, {$CFG->prefix}question_categories as qc ". + "WHERE q.qtype='random' AND qc.id = q.category AND q.questiontext = 1")){ + return array(); + } else { + if ($categories === null){ + $categories = get_records('question_categories'); + } + $categorychildparents = array(); + foreach ($categories as $id => $category){ + $categorychildparents[$category->course][$id] = $category->parent; + } + foreach ($categories as $id => $category){ + if (FALSE !== array_key_exists($category->parent, $categorychildparents[$category->course])){ + //this is not a top level cat + continue;//go to next category + } else{ + $tofix += question_cwqpfs_check_children($id, $categories, $categorychildparents[$category->course], $cwqpfs); + } + } + } + + return $tofix; +} + +function question_cwqpfs_check_children($checkid, $categories, $categorychildparents, $cwqpfs){ + $tofix = array(); + if (array_key_exists($checkid, $cwqpfs)){//cwqpfs in this cat + $getchildren = array(); + $getchildren[] = $checkid; + //search down tree and find all children + while ($nextid = array_shift($getchildren)){//repeat until $getchildren + //empty; + $childids = array_keys($categorychildparents, $nextid); + foreach ($childids as $childid){ + if ($categories[$childid]->publish != $categories[$checkid]->publish){ + $tofix[$childid] = $categories[$checkid]->publish; + } + } + $getchildren = array_merge($getchildren, $childids); + } + } else { // check children for cwqpfs + $childrentocheck = array_keys($categorychildparents, $checkid); + foreach ($childrentocheck as $childtocheck){ + $tofix += question_cwqpfs_check_children($childtocheck, $categories, $categorychildparents, $cwqpfs); + } + } + return $tofix; +} + +function question_category_next_parent_in($contextid, $question_categories, $id){ + $nextparent = $question_categories[$id]->parent; + if ($nextparent == 0){ + return 0; + } elseif (!array_key_exists($nextparent, $question_categories)){ + //finished searching up the category hierarchy. For some reason + //the top level items is not 0. We'll return 0 though. + return 0; + } elseif ($contextid == $question_categories[$nextparent]->contextid){ + return $nextparent; + } else { + //parent is not in the same context look further up. + return question_category_next_parent_in($contextid, $question_categories, $nextparent); + } +} + + +/** + * Check that either category parent is 0 or a category shared in the same context. + * Fix any categories to point to grand or grand grand parent etc in the same context or 0. + */ +function question_category_checking($question_categories){ + //make an array that is easier to search + $newparents = array(); + foreach ($question_categories as $id => $category){ + $newparents[$id] = question_category_next_parent_in($category->contextid, $question_categories, $id); + } + foreach (array_Keys($question_categories) as $id){ + $question_categories[$id]->parent = $newparents[$id]; + } + return $question_categories; +} + +function question_upgrade_context_etc(){ + global $CFG; + $result = true; + $result = $result && question_delete_unused_random(); + + $question_categories = get_records('question_categories'); + + $tofix = question_cwqpfs_to_update($question_categories); + foreach ($tofix as $catid => $publish){ + $question_categories[$catid]->publish = $publish; + } + + foreach ($question_categories as $id => $question_category){ + $course = $question_categories[$id]->course; + unset($question_categories[$id]->course); + if ($question_categories[$id]->publish){ + $context = get_context_instance(CONTEXT_SYSTEM); + } else { + $context = get_context_instance(CONTEXT_COURSE, $course); + } + $question_categories[$id]->contextid = $context->id; + unset($question_categories[$id]->publish); + } + + $question_categories = question_category_checking($question_categories); + +/// Define index course (not unique) to be dropped form question_categories + $table = new XMLDBTable('question_categories'); + $index = new XMLDBIndex('course'); + $index->setAttributes(XMLDB_INDEX_NOTUNIQUE, array('course')); + +/// Launch drop index course + $result = $result && drop_index($table, $index); + +/// Define field course to be dropped from question_categories + $field = new XMLDBField('course'); + +/// Launch drop field course + $result = $result && drop_field($table, $field); + +/// Define field context to be added to question_categories + $field = new XMLDBField('contextid'); + $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'name'); + $field->comment = 'context that this category is shared in'; + +/// Launch add field context + $result = $result && add_field($table, $field); + +/// Define index context (not unique) to be added to question_categories + $index = new XMLDBIndex('contextid'); + $index->setAttributes(XMLDB_INDEX_NOTUNIQUE, array('contextid')); + $index->comment = 'links to context table'; + +/// Launch add index context + $result = $result && add_index($table, $index); + + $field = new XMLDBField('publish'); + +/// Launch drop field publish + $result = $result && drop_field($table, $field); + + + /// update table contents with previously calculated new contents. + + foreach ($question_categories as $question_category){ + if (!$result = update_record('question_categories', $question_category)){ + notify('Couldn\'t update question_categories "'. $question_category->name .'"!'); + } + } + +/// Define field timecreated to be added to question + $table = new XMLDBTable('question'); + $field = new XMLDBField('timecreated'); + $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'hidden'); + +/// Launch add field timecreated + $result = $result && add_field($table, $field); + +/// Define field timemodified to be added to question + $table = new XMLDBTable('question'); + $field = new XMLDBField('timemodified'); + $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'timecreated'); + +/// Launch add field timemodified + $result = $result && add_field($table, $field); + +/// Define field createdby to be added to question + $table = new XMLDBTable('question'); + $field = new XMLDBField('createdby'); + $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null, 'timemodified'); + +/// Launch add field createdby + $result = $result && add_field($table, $field); + +/// Define field modifiedby to be added to question + $table = new XMLDBTable('question'); + $field = new XMLDBField('modifiedby'); + $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null, 'createdby'); + +/// Launch add field modifiedby + $result = $result && add_field($table, $field); + +/// Define key createdby (foreign) to be added to question + $table = new XMLDBTable('question'); + $key = new XMLDBKey('createdby'); + $key->setAttributes(XMLDB_KEY_FOREIGN, array('createdby'), 'user', array('id')); + +/// Launch add key createdby + $result = $result && add_key($table, $key); + +/// Define key modifiedby (foreign) to be added to question + $table = new XMLDBTable('question'); + $key = new XMLDBKey('modifiedby'); + $key->setAttributes(XMLDB_KEY_FOREIGN, array('modifiedby'), 'user', array('id')); + +/// Launch add key modifiedby + $result = $result && add_key($table, $key); + return $result; } ?> diff --git a/version.php b/version.php index 2a0609028f..577c94a3be 100644 --- a/version.php +++ b/version.php @@ -6,7 +6,7 @@ // This is compared against the values stored in the database to determine // whether upgrades should be performed (see lib/db/*.php) - $version = 2007080903; // YYYYMMDD = date + $version = 2007081000; // YYYYMMDD = date // XY = increments within a single day $release = '1.9 dev'; // Human-friendly version name -- 2.39.5