]> git.mjollnir.org Git - moodle.git/commitdiff
merging MOODLE_19_QUESTIONS with HEAD
authorjamiesensei <jamiesensei>
Thu, 9 Aug 2007 21:51:09 +0000 (21:51 +0000)
committerjamiesensei <jamiesensei>
Thu, 9 Aug 2007 21:51:09 +0000 (21:51 +0000)
47 files changed:
admin/report/question/index.php [new file with mode: 0644]
lib/db/access.php
lib/db/upgrade.php
lib/form/questioncategory.php
mod/quiz/backuplib.php
mod/quiz/db/install.xml
mod/quiz/db/upgrade.php
mod/quiz/edit.php
mod/quiz/editlib.php
mod/quiz/index.php
mod/quiz/locallib.php
mod/quiz/tabs.php
question/backuplib.php
question/category.php
question/category_class.php
question/edit.php
question/editlib.php
question/export.php
question/format.php
question/format/coursetestmanager/format.php
question/import.php
question/preview.php
question/question.php
question/restorelib.php
question/tabs.php
question/type/calculated/edit_calculated_form.php
question/type/datasetdependent/abstractqtype.php
question/type/datasetdependent/datasetdefinitions_form.php
question/type/datasetdependent/datasetitems_form.php
question/type/description/questiontype.php
question/type/edit_question_form.php
question/type/essay/edit_essay_form.php
question/type/match/edit_match_form.php
question/type/match/questiontype.php
question/type/missingtype/edit_missingtype_form.php
question/type/multianswer/edit_multianswer_form.php
question/type/multianswer/questiontype.php
question/type/multichoice/edit_multichoice_form.php
question/type/multichoice/questiontype.php
question/type/numerical/edit_numerical_form.php
question/type/questiontype.php
question/type/random/edit_random_form.php
question/type/randomsamatch/edit_randomsamatch_form.php
question/type/shortanswer/edit_shortanswer_form.php
question/type/truefalse/edit_truefalse_form.php
question/upgrade.php
version.php

diff --git a/admin/report/question/index.php b/admin/report/question/index.php
new file mode 100644 (file)
index 0000000..e5f94c4
--- /dev/null
@@ -0,0 +1,72 @@
+<?php // $Id$
+
+    require_once('../../../config.php');
+    require_once($CFG->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 .=('<p>'.get_string('cwrqpfsinfo', 'question').'</p>');
+            $probstr .= '<ul>';
+            $catlist = join(array_keys($updates), ',');
+            //get info about cateogries and no of questions used outside category's course
+            $categories = get_records_sql('SELECT qc.*, c.fullname as coursename FROM '.$CFG->prefix.'question_categories as qc, '
+                        .$CFG->prefix.'course as c WHERE qc.course = c.id AND qc.id IN ('.$catlist.')');
+            foreach ($updates as $id => $publish){
+                $categories[$id]->caturl = "$CFG->wwwroot/question/category.php?sesskey=".sesskey().
+                            "&amp;edit=$id&amp;courseid=".$categories[$id]->course;
+                if ($categories[$id]->publish){
+                    $categories[$id]->changefrom = get_string('published', 'question');
+                    $categories[$id]->changeto = get_string('unpublished', 'question');
+                } else {
+                    $categories[$id]->changefrom = get_string('unpublished', 'question');
+                    $categories[$id]->changeto = get_string('published', 'question');
+                }
+                $probstr .= '<li>'.get_string('changepublishstatuscat', 'question', $categories[$id]);
+                if ($questions = get_records_sql('SELECT q.*, qui.id as quizid, qui.name as quizname, cm.id as cmid, '
+                               .'qui.course, c.fullname as coursename FROM '.$CFG->prefix.'question as q, '
+                               .$CFG->prefix.'quiz_question_instances as qqi, '
+                               .$CFG->prefix.'quiz as qui, '
+                               .$CFG->prefix.'course_modules as cm, '
+                               .$CFG->prefix.'modules as m, '
+                               .$CFG->prefix.'course as c '
+                            .'WHERE (q.category = '.$id.' AND qqi.question = q.id '
+                            .'AND qqi.quiz = qui.id '
+                            .'AND qui.course = c.id '
+                            .'AND cm.instance = qui.id '
+                            .'AND cm.module = m.id '
+                            .'AND m.name = \'quiz\''
+                            .'AND ('.$categories[$id]->course.' <> qui.course)) ORDER BY qui.id ASC')){
+
+                    $probstr .= '<ul>';
+                    foreach ($questions as $question){
+                        $question->quizurl = "$CFG->wwwroot/mod/quiz/edit.php?cmid=".$question->cmid;
+                        $question->qurl = "$CFG->wwwroot/question/question.php?cmid={$question->cmid}&amp;id={$question->id}&amp;returnurl=".urlencode($FULLME);
+                        $probstr .= '<li>'.get_string('questionaffected', 'question', $question).'</li>';
+                    }
+                    $probstr .= '</ul>';
+                }
+                $probstr .= '</li>';
+            }
+            $probstr .= '</ul>';
+        } else {
+            $probstr .=('<p>'.get_string('cwrqpfsnoprob', 'question').'</p>');
+        }
+    }
+    if ($probstr) {
+        print_box($probstr);
+    } else {
+        print_box(get_string('noprobs', 'question'), 'boxwidthnarrow boxaligncenter generalbox');
+    }
+    admin_externalpage_print_footer();
+?>
index 9ca5b0a72c6e0dea0779f45804e3a7fe199cd152..88873635180464788ab3f04a8a45457f04596b6d 100644 (file)
@@ -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(
 
index 26d392c43da0902569baeed5d886b9abeb019eb0..802afaf171e45a8e3726a42a22c571ef1390c98b 100644 (file)
@@ -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;
 }
+
+
 ?>
index 04a90596dd14d9f55baeaf88fe2caee335904f64..2347af82c8064c6ddd829423c7bd121d74f9f640 100644 (file)
@@ -2,20 +2,22 @@
 /**
  * A moodle form field type for question categories.
  *
- * @copyright &copy; 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
index 3bfb63e48d8e6f9d36b28116c4191f70e89e089a..bcb6cfff5b581b69807606c671f955e24cb14a74 100644 (file)
     // 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,
                     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;
                 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;
                 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);
    ////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) {
index 84fdd257313e2fe745260cd8d79a29b18c1068f8..a717b9993306a0d3bde6a2ebfee451b1daa86dc0 100755 (executable)
@@ -1,17 +1,16 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="mod/quiz/db" VERSION="20070228" COMMENT="XMLDB file for Moodle mod/quiz"
+<XMLDB PATH="mod/quiz/db" VERSION="20070522" COMMENT="XMLDB file for Moodle mod/quiz"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
 >
   <TABLES>
     <TABLE NAME="question_categories" COMMENT="Categories are for grouping questions" NEXT="question">
       <FIELDS>
-        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="course"/>
-        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="name"/>
-        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="course" NEXT="info"/>
-        <FIELD NAME="info" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="name" NEXT="publish"/>
-        <FIELD NAME="publish" TYPE="int" LENGTH="4" NOTNULL="true" UNSIGNED="false" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="info" NEXT="stamp"/>
-        <FIELD NAME="stamp" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="publish" NEXT="parent"/>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="name"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="contextid"/>
+        <FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="context that this category is shared in" PREVIOUS="name" NEXT="info"/>
+        <FIELD NAME="info" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="contextid" NEXT="stamp"/>
+        <FIELD NAME="stamp" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="info" NEXT="parent"/>
         <FIELD NAME="parent" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="stamp" NEXT="sortorder"/>
         <FIELD NAME="sortorder" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="999" SEQUENCE="false" ENUM="false" PREVIOUS="parent"/>
       </FIELDS>
@@ -20,7 +19,7 @@
         <KEY NAME="parent" TYPE="foreign" FIELDS="parent" REFTABLE="question_categories" REFFIELDS="id" COMMENT="note that to make this recursive FK working someday, the parent field must be declared NULL" PREVIOUS="primary"/>
       </KEYS>
       <INDEXES>
-        <INDEX NAME="course" UNIQUE="false" FIELDS="course"/>
+        <INDEX NAME="contextid" UNIQUE="false" FIELDS="contextid" COMMENT="links to context table"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="question" COMMENT="The questions themselves" PREVIOUS="question_categories" NEXT="question_answers">
         <FIELD NAME="length" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="1" SEQUENCE="false" ENUM="false" PREVIOUS="qtype" NEXT="stamp"/>
         <FIELD NAME="stamp" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="length" NEXT="version"/>
         <FIELD NAME="version" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="stamp" NEXT="hidden"/>
-        <FIELD NAME="hidden" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="version"/>
+        <FIELD NAME="hidden" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="version" NEXT="timecreated"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="time question was created" PREVIOUS="hidden" NEXT="timemodified"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="time that question was last modified" PREVIOUS="timecreated" NEXT="createdby"/>
+        <FIELD NAME="createdby" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="userid of person who created this question" PREVIOUS="timemodified" NEXT="modifiedby"/>
+        <FIELD NAME="modifiedby" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="userid of person who last edited this question" PREVIOUS="createdby"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for question" NEXT="category"/>
         <KEY NAME="category" TYPE="foreign" FIELDS="category" REFTABLE="question_categories" REFFIELDS="id" PREVIOUS="primary" NEXT="parent"/>
-        <KEY NAME="parent" TYPE="foreign" FIELDS="parent" REFTABLE="question" REFFIELDS="id" COMMENT="note that to make this recursive FK working someday, the parent field must be declared NULL" PREVIOUS="category"/>
+        <KEY NAME="parent" TYPE="foreign" FIELDS="parent" REFTABLE="question" REFFIELDS="id" COMMENT="note that to make this recursive FK working someday, the parent field must be declared NULL" PREVIOUS="category" NEXT="createdby"/>
+        <KEY NAME="createdby" TYPE="foreign" FIELDS="createdby" REFTABLE="user" REFFIELDS="id" COMMENT="foreign (createdby) references user (id)" PREVIOUS="parent" NEXT="modifiedby"/>
+        <KEY NAME="modifiedby" TYPE="foreign" FIELDS="modifiedby" REFTABLE="user" REFFIELDS="id" COMMENT="foreign (modifiedby) references user (id)" PREVIOUS="createdby"/>
       </KEYS>
     </TABLE>
     <TABLE NAME="question_answers" COMMENT="Answers, with a fractional grade (0-1) and feedback" PREVIOUS="question" NEXT="question_dataset_definitions">
index 31a05191d56ccc848acbe3f028f2480473a32aa2..aaa42c1d0ba9ee5d8f7e1b0630b79a3db7278618 100644 (file)
@@ -1,6 +1,6 @@
 <?php  //$Id$
 
-// This file keeps track of upgrades to 
+// This file keeps track of upgrades to
 // the quiz module
 //
 // Sometimes, changes between versions involve
 
 function xmldb_quiz_upgrade($oldversion=0) {
 
-    global $CFG, $THEME, $db;
+    global $CFG, $THEME;
 
     $result = true;
 
-/// And upgrade begins here. For each one, you'll need one 
-/// block of code similar to the next one. Please, delete 
+/// And upgrade begins here. For each one, you'll need one
+/// block of code similar to the next one. Please, delete
 /// this comment lines once this file start handling proper
 /// upgrade code.
 
@@ -68,7 +68,7 @@ function xmldb_quiz_upgrade($oldversion=0) {
 
     // Separate control for when overall feedback is displayed, independant of the question feedback settings.
     if ($result && $oldversion < 2007072600) {
-        
+
         // Adjust the quiz review options so that overall feedback is displayed whenever feedback is.
         $result = $result && execute_sql('UPDATE ' . $CFG->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;
 }
 
index 4930d296d5f457fb7b9afe7f54c1808619a364f8..dfe341e9650bc5fdbe98aca7448834a8fd6140ee 100644 (file)
     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 = "<a title=\"$straddtoquiz\" href=\"edit.php?".$pageurl->get_query_string()."&amp;addquestion=$questionid&amp;sesskey=".sesskey()."\"><img
                   src=\"$CFG->pixpath/t/moveleft.gif\" alt=\"$straddtoquiz\" /></a>&nbsp;";
         }
     }
     /**
-     * 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 = "<input type=\"submit\" name=\"add\" value=\"{$THEME->larrow} $straddtoquiz\" />\n";
-            $out .= '</td><td>';
-            return $out;
-        } else {
-            return '';
-        }
+        $straddtoquiz = get_string("addtoquiz", "quiz");
+        $out = "<input type=\"submit\" name=\"add\" value=\"{$THEME->larrow} $straddtoquiz\" />\n";
+        echo '<br />';
+        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;
             }
             $out = '<br />';
             $out .= get_string('addrandom', 'quiz', choose_from_menu($randomcount, 'randomcount', '1', '', '', '', true));
             $out .= '<input type="hidden" name="recurse" value="'.$recurse.'" />';
-            $out .= "<input type=\"hidden\" name=\"categoryid\" value=\"$categoryid\" />";
+            $out .= "<input type=\"hidden\" name=\"categoryid\" value=\"$category->id\" />";
             $out .= ' <input type="submit" name="addrandom" value="'. get_string('add') .'" />';
             $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
     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);
-    
+
 
 
 
     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.
         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;
         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;
             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);
     }
                 $questions[$value] = $oldquestions[$key];
             }
         }
-        
+
         // If ordering info was given, reorder the questions
         if ($questions) {
             ksort($questions);
         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);
 
         $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 "<div class=\"attemptsnotice\">\n";
         echo "<a href=\"report.php?mode=overview&amp;id=$cm->id\">".get_string('numattempts', 'quiz', $a)."</a><br />".get_string("attemptsexist","quiz");
         echo "</div><br />\n";
     }
 
     // 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';
 
     echo '</td><td style="width:50%" valign="top">';
 
-    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 '</td></tr>';
index f75a8e95cf93f00b137c7f25028e7ef5fa1e4a8e..bcfde9aa3fb58f2e52f674e7285234c31041a387 100644 (file)
@@ -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 '<td colspan="2">&nbsp;</td>';
             }
             $count++;
-            // missing </tr> here, if loop is broken, need to close the </tr> from line 199/201
+            // missing </tr> here, if loop is broken, need to close the </tr>
             echo "</tr>";
             continue;
         }
         $question = $questions[$qnum];
-        $canedit = has_capability('moodle/question:manage', get_context_instance(CONTEXT_COURSE, $question->course));
 
         echo "<td>";
         if ($count != 0) {
@@ -276,17 +276,20 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete=true, $showbreak
         }
         echo '</td><td align="center">';
 
-        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 "<a title=\"$stredit\" href=\"".$questionurl->out()."\">
                     <img src=\"$CFG->pixpath/t/edit.gif\" class=\"iconsmall\" alt=\"$stredit\" /></a>";
+        } elseif (question_has_capability_on($question, 'view', $question->category)){
+            echo "<a title=\"$strview\" href=\"".$questionurl->out(false, array('id'=>$question->id))."\"><img
+                    src=\"$CFG->pixpath/i/info.gif\" alt=\"$strview\" /></a>&nbsp;";
         }
-        if ($allowdelete) {
+        if ($allowdelete && question_has_capability_on($question, 'use', $question->category)) { // remove from quiz, not question delete.
             echo "<a title=\"$strremove\" href=\"".$pageurl->out_action(array('delete'=>$count))."\">
                     <img src=\"$CFG->pixpath/t/removeright.gif\" class=\"iconsmall\" alt=\"$strremove\" /></a>";
         }
index d7d53c4ebd11063d164446e447c07b9b5a91063d..60aa60448a74f9607716b51cebe121c49e898dde 100644 (file)
@@ -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 =
                 "<form target=\"_parent\" method=\"get\" action=\"$CFG->wwwroot/question/edit.php\">
                    <div>
@@ -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));
 
index d0ef3ec790e07e74d8ef493f469eb2f2aca89a87..eab2d7441dddffbf8e5edd608b4148968f879e60 100644 (file)
@@ -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 . '&amp;quizid=' . $quiz->id, 'questionpreview',
             "<img src=\"$CFG->pixpath/t/preview.gif\" class=\"iconsmall\" alt=\"$strpreview\" />",
index 059e435941e5e7164bc44daffc8689c27e97fefd..4bc6c1b5427760b762a11a19e6df0a96e90fbac9 100644 (file)
@@ -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);
index 3637f944f27dadc6723b0410a3cd6aeaaff6369b..14de28b4ebeb79d9f7399efe12ad4d224cd633ef 100644 (file)
 
     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;
         //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;
         // 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));
                 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) {
                 }
             }
             //Write end tag
-            $status = fwrite ($bf,end_tag("QUESTIONS",$level,true));
+            $status = $status && fwrite ($bf,end_tag("QUESTIONS",$level,true));
         }
         return $status;
     }
         $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;
     }
         $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;
                     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));
                 }
         //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));
                 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));
         }
     }
 
         //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;
     }
 
         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;
     }
-
 ?>
index 190f5d0ae71814bfa2dbac0c374c6a114cf906d7..fea7f1cda98707fcd2d6aa24238349090cab7c31 100644 (file)
@@ -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();
 
     $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);
 ?>
index d6a9864e1d7fd2517b038e8b9c2cab2517dcf394..07b457d4dfe776189a5f45666b28fc253955a1f9 100644 (file)
@@ -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 = '<a ' . $linkcss . ' title="' . $str->edit. '" href="'.$this->parentlist->pageurl->out_action(array('edit'=>$this->id)).'">
-            <img src="' . $pixpath . '/t/edit.gif" class="iconsmall"
-            alt="' .$str->edit. '" /> ' . $category->name . '('.$category->questioncount.')'. '</a>';
+        $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 = "<a title=\"{$str->edit}\" href=\"$catediturl\">".$category->name ."</a> <a title=\"$editqestions\" href=\"$questionbankurl\">".'('.$category->questioncount.')</a>';
 
         $item .= '&nbsp;'. $category->info;
 
 
-        if (!empty($category->publish)) {
-            $item .= '<a title="' . $str->hide . '" href="'.$this->parentlist->pageurl->out_action(array('hide'=>$this->id)).'">
-              <img src="' . $pixpath . '/t/hide.gif" class="iconsmall" alt="' .$str->hide. '" /></a> ';
-        } else {
-            $item .= '<a title="' . $str->publish . '" href="'.$this->parentlist->pageurl->out_action(array('publish'=>$this->id)).'">
-              <img src="' . $pixpath . '/t/show.gif" class="iconsmall" alt="' .$str->publish. '" /></a> ';
-        }
-
-        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 .=  '<a title="' . $str->delete . '" href="'.$this->parentlist->pageurl->out_action(array('delete'=>$this->id)).'">
                     <img src="' . $pixpath . '/t/delete.gif" class="iconsmall" alt="' .$str->delete. '" /></a> ';
         }
@@ -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('&nbsp;', $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 '<br />';
         /// Interface for adding a new category:
-        print_heading_with_help($this->str->addcategory, 'categories_edit', 'quiz');
         $this->output_new_table();
         echo '<br />';
 
     }
 
-
-    /**
-     * 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'] = '<input type="text" name="newcategory" value="" size="15" />';
-
-        $this->newtable->align['info'] =  "left";
-        $this->newtable->wrap['info'] = "nowrap";
-        $row['info'] = '<input type="text" name="newinfo" value="" size="50" />';
-
-        $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'] = '<input type="submit" value="' . $this->str->add . '" />';
-
-
-        $this->newtable->data[] = $row;
-
-        // wrap the table in a form and output it
-        echo '<form action="category.php" method="post">';
-        echo '<fieldset class="invisiblefieldset" style="display: block">';
-        echo "<input type=\"hidden\" name=\"sesskey\" value=\"$USER->sesskey\" />";
-        echo $this->pageurl->hidden_params_out();
-        echo '<input type="hidden" name="addcategory" value="true" />';
-        print_table($this->newtable);
-        echo '</fieldset>';
-        echo '</form>';
+        $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 '<div class="centerpara">';
-            print_single_button($CFG->wwwroot . '/question/category.php',
-                    $this->pageurl->params, $this->str->cancel);
-            echo '</div>';
-            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'] = '<input type="text" name="updatename" value="' . format_string($category->name) . '" size="15" />';
-
-        $edittable->align['info'] =  "left";
-        $edittable->wrap['info'] = "nowrap";
-        $row['info'] = '<input type="text" name="updateinfo" value="' . $category->info . '" size="50" />';
-
-        $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'] = '<input type="submit" value="' . $strupdate . '" />';
-
-        $edittable->data[] = $row;
-
-        // wrap the table in a form and output it
-        echo '<p><form action="category.php" method="post">';
-        echo '<fieldset class="invisiblefieldset" style="display: block;">';
-        echo '<input type="hidden" name="sesskey" value="' . $USER->sesskey . '" />';
-        echo $this->pageurl->hidden_params_out();
-        echo '<input type="hidden" name="updateid" value="' . $category->id . '" />';
-        print_table($edittable);
-        echo '</fieldset>';
-        echo '</form></p>';
-    }
-
-    /**
-     * 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 "<p><div align=\"center\"><form action=\"category.php\" method=\"get\">";
-                echo '<fieldset class="invisiblefieldset">';
-                echo "<input type=\"hidden\" name=\"sesskey\" value=\"$USER->sesskey\" />";
-                echo "<input type=\"hidden\" name=\"id\" value=\"{$COURSE->id}\" />";
-                echo "<input type=\"hidden\" name=\"delete\" value=\"$category->id\" />";
-                choose_from_menu($categorystrings, "confirm", "", "");
-                echo "<input type=\"submit\" value=\"". get_string("categorymoveto", "quiz") . "\" />";
-                echo "<input type=\"submit\" name=\"cancel\" value=\"{$this->str->cancel}\" />";
-                echo '</fieldset>';
-                echo "</form></div></p>";
-                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 '<p>'.get_string('needtomovethesefilesincat','question').'</p>';
+        } else {
+            echo '<p>'.get_string('needtomovethesefilesinquestion','question').'</p>';
+        }
+    }
+
+
+
+
 }
 
 ?>
index 9edd5cd7ff6f5344f3ca29a31cdfb8c5f67b6c1d..08949243fe179e2ffbae186f83c2664cbe72000e 100644 (file)
     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');
         $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 '<table class="boxaligncenter" border="0" cellpadding="2" cellspacing="0">';
     echo '<tr><td valign="top">';
 
-    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 '</td></tr>';
     echo '</table>';
 
-    print_footer($course);
+    print_footer($COURSE);
 ?>
index 9f40765838dbe553114221975063da7ed3dd81ca..abead698da4d9d972fe3dbc438c9fb71985c5ff1 100644 (file)
@@ -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()."&amp;category=", $catmenu, "catmenu", $current, "", "", "", false, "self", "<strong>$strcategory</strong>");
+    popup_form ('edit.php?'.$pageurl->get_query_string().'&amp;category=', $catmenu, 'catmenu', $current, '', '', '', false, 'self', "<strong>$strcategory</strong>");
 
     echo '<form method="get" action="edit.php" id="displayoptions">';
     echo "<fieldset class='invisiblefieldset'>";
@@ -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 '<div class="boxaligncenter">';
     $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 '<table><tr>';
 
-    // check if editing questions in this category is allowed
-    if ($canedit) {
+    if ($canadd) {
         echo '<td valign="top" align="right">';
-        $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().'&amp;qtype=', $QTYPE_MENU, "addquestion", "", "choose", "", "", false, "self", "<strong>$strcreatenewquestion</strong>");
+        popup_form ($questionurl->out(false, array('category' => $category->id)).'&amp;qtype=', $qtypemenu, "addquestion", "", "choose", "", "", false, "self", "<strong>$strcreatenewquestion</strong>");
         echo '</td><td valign="top" align="right">';
         helpbutton("questiontypes", $strcreatenewquestion, "quiz");
         echo '</td>';
     }
     else {
         echo '<td>';
-        print_string("publishedit","quiz");
+        print_string('nopermissionadd', 'question');
         echo '</td>';
     }
 
@@ -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 '<form method="post" action="edit.php">';
     echo '<fieldset class="invisiblefieldset" style="display: block;">';
     echo '<input type="hidden" name="sesskey" value="'.$USER->sesskey.'" />';
     echo $pageurl->hidden_params_out(array('qsortorder'));
     echo '<table id="categoryquestions" style="width: 100%"><tr>';
     echo "<th style=\"white-space:nowrap;\" class=\"header\" scope=\"col\">$straction</th>";
-    
-    $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 .= '<noscript><div><input type="submit" value="'.get_string("sortsubmit", "quiz").'" /></div></noscript>';
-    echo "<th style=\"white-space:nowrap; text-align: left;\" class=\"header\" scope=\"col\">$strquestionname $orderselect</th>
+
+    echo "<th style=\"white-space:nowrap; text-align: left;\" class=\"header\" scope=\"col\">$strquestionname</th>
     <th style=\"white-space:nowrap; text-align: right;\" class=\"header\" scope=\"col\">$strtype</th>";
     echo "</tr>\n";
     foreach ($questions as $question) {
@@ -296,25 +326,35 @@ function question_list($course, $pageurl, $categoryid, $cm = null,
         if ($textclass) {
             $textclass = 'class="' . $textclass . '"';
         }
-        
+
         echo "<tr>\n<td style=\"white-space:nowrap;\" $nameclass>\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 . '&amp;quizid=' . $quizid, 'questionpreview',
-                "<img src=\"$CFG->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 . '&amp;quizid=' . $quizid, 'questionpreview',
+                    "<img src=\"$CFG->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 "<a title=\"$stredit\" href=\"".$questionurl->out()."\"><img
+        if (question_has_capability_on($question, 'edit', $question->category) || question_has_capability_on($question, 'move', $question->category)) {
+            echo "<a title=\"$stredit\" href=\"".$questionurl->out(false, array('id'=>$question->id))."\"><img
                     src=\"$CFG->pixpath/t/edit.gif\" alt=\"$stredit\" /></a>&nbsp;";
+        } elseif (question_has_capability_on($question, 'view', $question->category)){
+            echo "<a title=\"$strview\" href=\"".$questionurl->out(false, array('id'=>$question->id))."\"><img
+                    src=\"$CFG->pixpath/i/info.gif\" alt=\"$strview\" /></a>&nbsp;";
+        }
+
+        if (question_has_capability_on($question, 'move', $question->category) && question_has_capability_on($question, 'view', $question->category)) {
+            echo "<a title=\"$strmove\" href=\"".$questionurl->out(false, array('id'=>$question->id, 'movecontext'=>1))."\"><img
+                    src=\"$CFG->pixpath/t/move.gif\" alt=\"$strmove\" /></a>&nbsp;";
+        }
+
+        if (question_has_capability_on($question, 'edit', $question->category)) {
             // hide-feature
             if($question->hidden) {
                 echo "<a title=\"$strrestore\" href=\"edit.php?".$pageurl->get_query_string()."&amp;unhide=$question->id&amp;sesskey=$USER->sesskey\"><img
@@ -324,7 +364,9 @@ function question_list($course, $pageurl, $categoryid, $cm = null,
                         src=\"$CFG->pixpath/t/delete.gif\" alt=\"$strdelete\" /></a>";
             }
         }
-        echo "&nbsp;<input title=\"$strselect\" type=\"checkbox\" name=\"q$question->id\" value=\"1\" />";
+        if ($caneditall || $canmoveall || $canuseall){
+            echo "&nbsp;<input title=\"$strselect\" type=\"checkbox\" name=\"q$question->id\" value=\"1\" />";
+        }
         echo "</td>\n";
 
         echo "<td $nameclass>" . format_string($question->name) . "</td>\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 "</td></tr>\n";
         }
     }
     echo "</table>\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 = '<a href="edit.php?'.$pageurl->get_query_string(array('qperpage'=>1000)).'">'.get_string('showall', 'moodle', $totalnumber).'</a>';
@@ -356,68 +397,108 @@ function question_list($course, $pageurl, $categoryid, $cm = null,
             $paging = substr($paging, 0, strrpos($paging, '</div>'));
             $paging .= "<br />$showall</div>";
         } else {
-            $paging = "<div class='paging'>$showall</div>"; 
+            $paging = "<div class='paging'>$showall</div>";
         }
     }
     echo $paging;
 
-    echo '<table class="quiz-edit-selected"><tr><td colspan="2">';
-    echo '<a href="javascript:select_all_in(\'TABLE\', null, \'categoryquestions\');">'.$strselectall.'</a> /'.
-     ' <a href="javascript:deselect_all_in(\'TABLE\', null, \'categoryquestions\');">'.$strselectnone.'</a>'.
-     '</td><td align="right"><b>&nbsp;'.get_string('withselected', 'quiz').':</b></td></tr><tr><td>';
+    if ($caneditall || $canmoveall || $canuseall){
+        echo '<a href="javascript:select_all_in(\'TABLE\', null, \'categoryquestions\');">'.$strselectall.'</a> /'.
+         ' <a href="javascript:deselect_all_in(\'TABLE\', null, \'categoryquestions\');">'.$strselectnone.'</a>';
+        echo '<br />';
+        echo '<strong>&nbsp;'.get_string('withselected', 'quiz').':</strong><br />';
 
-    if (function_exists('module_specific_buttons')) {
-        echo module_specific_buttons($cm->id);
-    }
-    // print delete and move selected question
-    if ($canedit) {
-        echo '<input type="submit" name="deleteselected" value="'.$strdelete."\" /></td><td>\n";
-        echo '<input type="submit" name="move" value="'.get_string('moveto', 'quiz')."\" />\n";
-        question_category_select_menu($course->id, false, true, $category->id);
-    }
-    echo "</td></tr></table>";
+        if (function_exists('module_specific_buttons')) {
+            echo module_specific_buttons($cm->id);
+        }
+        // print delete and move selected question
+        if ($caneditall) {
+            echo '<input type="submit" name="deleteselected" value="'.$strdelete."\" />\n";
+        }
+        if ($canmoveall && count($addcontexts)) {
+            echo '<input type="submit" name="move" value="'.get_string('moveto', 'quiz')."\" />\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 '</fieldset>';
     echo "</form>\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 = "<div class=\"mdl-align\">";
+    $html .= '<form method="post" action="edit.php">';
+    $html .= '<fieldset class="invisiblefieldset" style="display: block;">';
+    $html .= '<input type="hidden" name="sesskey" value="'.$USER->sesskey.'" />';
+    $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 .=  '<noscript><div><input type="submit" value="'.get_string("sortsubmit", "quiz").'" /></div></noscript>';
+    $html .= '</fieldset>';
+    $html .= "</form>\n";
+    $html .= "</div>\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 '</td></tr>';
-                echo '</table>';
-                echo '</div>';
                 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 <br /> 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).'<br />';
-                }
-            }
-            if (!$questionlist) { // no questions were selected
-                redirect($pageurl->out());
-            }
-            $questionlist = rtrim($questionlist, ',');
 
-            // Add an explanation about questions in use
-            if ($inuse) {
-                $questionnames .= '<br />'.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 '</td></tr>';
-            echo '</table>';
-            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 <br /> 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).'<br />';
+            }
+        }
+        if (!$questionlist) { // no questions were selected
+            redirect($pageurl->out());
+        }
+        $questionlist = rtrim($questionlist, ',');
+
+        // Add an explanation about questions in use
+        if ($inuse) {
+            $questionnames .= '<br />'.get_string('questionsinuse', 'quiz');
+        }
+        notice_yesno(get_string("deletequestionscheck", "quiz", $questionnames),
+                    $pageurl->out_action(array('deleteselected'=>$questionlist, 'confirm'=>md5($questionlist))),
+                    $pageurl->out_action());
+
+        echo '</td></tr>';
+        echo '</table>';
+        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
index 57d0ede8e092dee88bc1d74ee747687749a2658a..5af47350cfaf47b193fd3dbf6bf2d7ccea1a8b97 100644 (file)
  */
 
     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');
         $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 "<hr />";
 
         // 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 "<p><div class=\"boxaligncenter\"><a href=\"$efile\">$txt->download</a></div></p>";
         echo "<p><div class=\"boxaligncenter\"><font size=\"-1\">$txt->downloadextra</font></div></p>";
 
         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');
-?>
 
-    <form enctype="multipart/form-data" method="post" action="export.php">
-        <fieldset class="invisiblefieldset" style="display: block;">
-            <input type="hidden" name="sesskey" value="<?php echo sesskey(); ?>" />
-            <?php echo $thispageurl->hidden_params_out(array(), 3); ?>
-            <table cellpadding="5">
-                <tr>
-                    <td align="right"><?php echo $txt->category; ?>:</td>
-                    <td>
-                        <?php
-                        question_category_select_menu($course->id, true, false, $category->id);
-                        echo $txt->tofile; ?>
-                        <input name="cattofile" type="checkbox" />
-                        <?php helpbutton('exportcategory', $txt->exportcategory, 'quiz'); ?>
-                    </td>
-                </tr>
-                <tr>
-                    <td align="right"><?php echo $txt->fileformat; ?>:</td>
-                    <td>
-                        <?php choose_from_menu($fileformatnames, 'format', 'gift', '');
-                        helpbutton('export', $txt->exportquestions, 'quiz'); ?>
-                    </td>
-                </tr>
-                <tr>
-                    <td align="right"><?php echo $txt->exportname; ?>:</td>
-                    <td>
-                        <input type="text" size="40" name="exportfilename" value="<?php echo $exportfilename; ?>" />
-                    </td>
-                </tr>
-                <tr>
-                    <td align="center" >
-                        <input type="submit" name="save" value="<?php echo $txt->exportquestions; ?>" />
-                    </td>
-                    <td>&nbsp;</td>
-                </tr>
-            </table>
-        </fieldset>
-    </form>
-    <?php
-
-    print_simple_box_end();
-    print_footer($course);
+    $export_form->display();
+
+    print_footer($COURSE);
 ?>
 
index 0cfd17bfd465b657efd0805c68770f501ab1020b..0222c6c4b119e778a7fcb61d3030919ab1529715 100644 (file)
@@ -1,4 +1,4 @@
-<?php  // $Id$ 
+<?php  // $Id$
 /**
  * Base class for question import and export formats.
  *
@@ -16,10 +16,14 @@ class qformat_default {
     var $filename = '';
     var $matchgrades = 'error';
     var $catfromfile = 0;
+    var $contextfromfile = 0;
     var $cattofile = 0;
+    var $contexttofile = 0;
     var $questionids = array();
     var $importerrors = 0;
     var $stoponerror = true;
+    var $translator = null;
+
 
 // functions to indicate import/export functionality
 // override to return true if implemented
@@ -49,6 +53,14 @@ class qformat_default {
     function setCourse( $course ) {
         $this->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 "<p>$formatnotimplemented</p>";
-
         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);
     }
+
+
 }
 
 ?>
index b80cf24d4f90dba02d92b0fb63d884dc906e9018..e8de1dc8e51384603266c82ef5892a37678e4ca4 100755 (executable)
@@ -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 "<hr /><p><b>$count</b>. ".stripslashes($question->questiontext)."</p>";
             $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!");
             }
index 7946756fdb7029780c16fb1265dd6192b718025a..153f7031d22498dd17670836cf643c8859afb79c 100644 (file)
  */
 
     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');
         $navlinks = array();
         $navlinks[] = array('name' => $txt->importquestions, 'link' => '', 'type' => 'title');
         $navigation = build_navigation($navlinks);
-           
+
         print_header_simple($txt->importquestions, '', $navigation);
         // print tabs
         $currenttab = 'import';
 
 
     // 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 "<hr />";
-            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
-    //==========
-    ?>
-
-    <form id="form" enctype="multipart/form-data" method="post" action="import.php">
-        <fieldset class="invisiblefieldset" style="display: block;">
-            <input type="hidden" name="sesskey" value="<?php echo sesskey(); ?>" />
-            <input type="hidden" name="courseid" value="<?php echo $localcourseid ?>" />
-            <?php echo $thispageurl->hidden_params_out(array(), 3); ?>
-            <?php print_simple_box_start("center"); ?>
-            <table cellpadding="5">
-                <tr>
-                    <td align="right"><?php echo $txt->category; ?>:</td>
-                    <td><?php choose_from_menu($catmenu, "category", $category->id, ""); ?>
-                        <?php echo $txt->fromfile; ?>
-                        <input name="catfromfile" type="checkbox" />
-                        <?php helpbutton('importcategory', $txt->importcategory, 'quiz'); ?></td>
-                </tr>
-
-                <tr>
-                    <td align="right"><?php echo $txt->fileformat; ?>:</td>
-                    <td><?php choose_from_menu($fileformatnames, 'format', 'gift', '');
-                        helpbutton("import", $txt->importquestions, 'quiz'); ?></td>
-                </tr>
-                <tr>
-                    <td align="right"><?php echo $txt->matchgrades; ?></td>
-                    <td><?php choose_from_menu($matchgrades,'matchgrades',$txt->matchgradeserror,'' );
-                        helpbutton('matchgrades', $txt->matchgrades, 'quiz'); ?></td>
-                </tr>
-                <tr>
-                    <td align="right"><?php echo $txt->stoponerror; ?></td>
-                    <td><input name="stoponerror" type="checkbox" checked="checked" />
-                    <?php helpbutton('stoponerror', $txt->stoponerror, 'quiz'); ?></td>
-                </tr>
-            </table>
-            <?php
-            print_simple_box_end();
-
-            print_simple_box_start('center'); ?>
-            <?php echo $txt->importfileupload; ?>
-            <table cellpadding="5">
-                <tr>
-                    <td align="right"><?php echo $txt->upload; ?>:</td>
-                    <td><?php upload_print_form_fragment(1,array('newfile'),null,false,null,$course->maxbytes,0,false); ?></td>
-                </tr>
-
-                <tr>
-                    <td>&nbsp;</td>
-                    <td><input type="submit" name="save" value="<?php echo $txt->uploadthisfile; ?>" /></td>
-                </tr>
-            </table>
-            <?php
-            print_simple_box_end();
-
-            print_simple_box_start('center'); ?>
-            <?php echo $txt->importfilearea; ?>
-            <table cellpadding="5">
-                <tr>
-                    <td align="right"><?php echo $txt->file; ?>:</td>
-                    <td><input type="text" name="choosefile" size="50" /></td>
-                </tr>
-
-                <tr>
-                    <td>&nbsp;</td>
-                    <td><?php  button_to_popup_window ("/files/index.php?id={$course->id}&amp;choose=form.choosefile", 
-                        "coursefiles", $txt->choosefile, 500, 750, $txt->choosefile); ?>
-                        <input type="submit" name="save" value="<?php echo $txt->importfromthisfile; ?>" /></td>
-                </tr>
-            </table>
-            <?php 
-            print_simple_box_end(); ?>
-        </fieldset>
-    </form>
-
-    <?php
-    print_footer($course);
+    /// Print upload form
+    $import_form->display();
+    print_footer($COURSE);
 
 ?>
index ffd5b24f2d7f72d1b63bb27c055f68432da0fabb..8922ac69d6a7e0d16872f6585a62bb698beadd82 100644 (file)
         $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
         $url .= '&amp;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 {
         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)) {
     echo '<br />';
 
 
-    
+
     echo '<div class="controls">';
     echo "<input type=\"hidden\" name=\"id\" value=\"$id\" />\n";
     echo "<input type=\"hidden\" name=\"quizid\" value=\"$quizid\" />\n";
index 45e8ac9963ca3f941b2a11f348c36439eb2c54c1..8079937c9b665e7f139b83e5e5051d1ba7492bba 100644 (file)
@@ -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);
 }
 ?>
index 2c00b47e184f86f5ac6c8dd5638327bdb93aadb2..fa48d6ddef43b1dd79d1032c863803cb6c09ce99 100644 (file)
 
     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 "<li>".get_string('category', 'quiz')." \"".$question_cat->name."\"<br />";
                 }
-            } 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 "<li>".get_string('category', 'quiz')." \"".$question_cat->name."\" Error!<br />";
+                    echo '</li>';
                 }
-                $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 '</li>';
+            //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.<br />';
+                            }
+                            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;
 
 
             //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']['#']);
             $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
                 $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.
                         echo 'Could not recode parent '.$question->parent.' for question '.$oldid.'<br />';
                     }
                 }
-    
+
                 //Now, restore every question_answers in this question
                 $status = question_restore_answers($oldid,$newid,$que_info,$restore);
                 // Restore questiontype specific data
 
         $status = true;
         $qtype = backup_todb($info['#']['QTYPE']['0']['#']);
-        
+
         //Get the answers array
         if (isset($info['#']['ANSWERS']['0']['#']['ANSWER'])) {
             $answers = $info['#']['ANSWERS']['0']['#']['ANSWER'];
             //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;
                 }
             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) {
         }
         $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);
                     ' . $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);
index da5847a38b6d420d174d78fae390cf575deb45a7..a64ace1b56b998d9d525233a8b543370802aabfc 100644 (file)
     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());
+    
 
 ?>
index 255f4312b79d4d856aab14f382ae807c86bfa9ae..30c2c3a1cc3f7ffd3a6f3db628e5224d88ace1b0 100644 (file)
@@ -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 <pre>";print_r($possibledatasets); 
+    //     echo "numericalquestion qtextremaining <pre>";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'] .= '<br/>'.$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
index 8e2bdd9db2ac81f456ee65a62f375e69f545d4ee..54edd15fc0d98e0fb8814febc35b9e4620cee68d 100644 (file)
@@ -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;
index c2c7bc4f111417b89fffc08791c4a6b3a1c43609..d6ae8c72a387772c1070380ed86f9454d203527a 100644 (file)
@@ -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);
index ce9853e9e5e277df45a5d58b0d4f1f85b339dd33..c9defa801e4175089932c8489c3cedefa16a9548 100644 (file)
@@ -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');
index 7244bf86988416905d4c21fc4fcd2725931a3a9b..1ba123698f684c60af9dc700f8384b85256ce811 100644 (file)
@@ -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 = '<img src="'.$CFG->pixpath.'/t/edit.gif" alt="'.$stredit.'" />';
             $editlink = link_to_popup_window('/question/question.php?id='.$question->id, $stredit, $linktext, 450, 550, $stredit, '', true);
index 3a08e8bb92f7831f7afaa36b327a6a9324da18b3..e105bb1679b546cf5c8a9b283267a130636db04e 100644 (file)
@@ -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
index 4b989c08bc1019228701d916232d3b61bb341316..60972b11ef31deb254a6866f54199d018b7dd03a 100644 (file)
@@ -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);
index 97373b24b1a4e133e667375d526e8329eddf6b2e..c05302993bc2f6097c27438431dc3fce4949d4f0 100644 (file)
@@ -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;
index 352c3c5fada0811167caf2c690dfc3b7e5d1011e..baa600b28e834936db14af3d6ec6ead25bd859ac 100644 (file)
@@ -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 ////
 
index 6cef3ad8948398afe1b9eb0634a2306d976bb3aa..396f0616e75b5166b79737e6ef3ee4a8313a4610 100644 (file)
@@ -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;
 
index 683b3df6c76b3bce99cfadbc60f2cf2a3ffbf943..6c784480f495f8c2efa72a5a2910a5ac7f570b6d 100644 (file)
@@ -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 <br />
         $questiontext= trim(strip_tags($data['questiontext']));
         if ($questiontext==''){
index 77bc15572bb7493831b9a77f9b79313e9e538faa..51697a18753b3de7a10f6d185fb88d0c4eb9feeb 100644 (file)
@@ -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 = '';
index 3879986a7503466157c2ebd45dfc4734180354a5..2e0fc3b1e51bd2b8636972f1d42d5ac8d0b23d17 100644 (file)
@@ -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;
 
index 5dd0910d125e3953c31b01058bfb5a575f802d8f..965921006df9ab7fd4bcfe87a32aeb0fd920abe0 100644 (file)
@@ -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 '<span class="anun">' . $qnum . '<span class="anumsep">.</span></span> ';
     }
@@ -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.
index 102369d8620c4062f98f0691d28b2cac8053e881..3b256b02918a26e62bdc35a3c6f6e70ce18fd138 100644 (file)
@@ -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;
index 9c6499f7c306b42289fe31a18383e62b3d1be561..a7ad604cd3e0d0748479c104d8c457dca4c9a702 100644 (file)
@@ -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 = '<ul>';
+            foreach ($permissionstrs as $permissionstr){
+                $html .= '<li>'.$permissionstr.'</li>';
+            }
+            $html .= '</ul>';
+            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 <link ...> 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 = '<img src="'.$CFG->pixpath.'/t/edit.gif" alt="'.$stredit.'" />';
             $editlink = link_to_popup_window('/question/question.php?inpopup=1&amp;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 '<input type="submit" name="', $question->name_prefix, 'submit" value="',
-                    get_string('mark', 'quiz'), '" class="submit btn" onclick="', 
+                    get_string('mark', 'quiz'), '" class="submit btn" onclick="',
                     "form.action = form.action + '#q", $question->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.
index 0da3436d2565c7d7f9a82f17b149a6be8afabce2..2a078a52a5ff4fc58caf1b8bd357cfe8d70c4e27 100644 (file)
@@ -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'));
 
index ce22a4d68b2717ec21d7e0b97f39dc4f69d1ae40..6786842384f0a8cfb6e5cb7ea9ddf0b36a4a3f04 100644 (file)
@@ -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){
index 6fdb563e1f97fa9095d9752874810990ffa89e87..89a8d181b95c458e117affa006590cc234c027fe 100644 (file)
@@ -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;
index 1e13002268febdf5d820992732c8f0bc010ea137..0567e9d19145dcae3a0c7721b804171eae7f6044 100644 (file)
@@ -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.
index e9d1cd2b2869077d3aa96f731ba88a1f23c50908..efcc2335dd379834d66ce1bef2fd78a6c003c700 100644 (file)
@@ -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;
 }
 ?>
index 2a0609028fe41f9ae641b7889ec46782defc6ff4..577c94a3be68dd5da697fb8e8e209c411a6f7539 100644 (file)
@@ -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