Merge branch 'xhprof' of git://github.com/stronk7/moodle
authorPetr Skoda <commits@skodak.org>
Mon, 20 Dec 2010 21:59:20 +0000 (22:59 +0100)
committerPetr Skoda <commits@skodak.org>
Mon, 20 Dec 2010 21:59:20 +0000 (22:59 +0100)
44 files changed:
admin/mnet/services_form.php
calendar/renderer.php
config-dist.php
course/editcategory.php
course/editcategory_form.php
course/renderer.php
enrol/database/lib.php
enrol/ldap/lib.php
lib/ajax/ajaxlib.php
lib/blocklib.php
lib/datalib.php
lib/excellib.class.php
lib/simpletest/testsimpletestlib.php
lib/simpletest/testweblib.php
lib/simpletestlib.php
mod/data/data.js
mod/data/templates.php
mod/forum/lib.php
mod/glossary/approve.php
mod/glossary/backup/moodle2/backup_glossary_stepslib.php
mod/glossary/db/install.xml [changed mode: 0644->0755]
mod/glossary/db/upgrade.php
mod/glossary/deleteentry.php
mod/glossary/edit.php
mod/glossary/filter.php
mod/glossary/lang/en/glossary.php
mod/glossary/lib.php
mod/glossary/mod_form.php
mod/glossary/version.php
mod/lesson/continue.php
mod/lesson/db/upgrade.php
mod/lesson/locallib.php
mod/lesson/pagetypes/branchtable.php
mod/lesson/pagetypes/matching.php
mod/lesson/pagetypes/multichoice.php
mod/lesson/report.php
mod/lesson/version.php
mod/lesson/view.php
mod/lesson/view_form.php
mod/quiz/attempt.php
mod/quiz/review.php
mod/wiki/lang/en/wiki.php
mod/wiki/locallib.php
mod/wiki/pagelib.php

index 3bda961..af4d5b0 100644 (file)
@@ -60,13 +60,13 @@ class mnet_services_form extends moodleform {
             if (!empty($version['hostsubscribes'])) {
                 $pubstr .= ' <a class="notifysuccess" title="'.s(get_string('issubscribed','mnet', $mnet_peer->name)).'">&radic;</a> ';
             }
-            $mform->addElement('checkbox', 'publish[' . $version['serviceid'] . ']', $pubstr);
+            $mform->addElement('advcheckbox', 'publish[' . $version['serviceid'] . ']', $pubstr);
 
             $substr = get_string('subscribe','mnet');
             if (!empty($version['hostpublishes'])) {
                 $substr .= ' <a class="notifysuccess" title="'.s(get_string('ispublished','mnet', $mnet_peer->name)).'">&radic;</a> ';
             }
-            $mform->addElement('checkbox', 'subscribe[' . $version['serviceid']. ']', $substr);
+            $mform->addElement('advcheckbox', 'subscribe[' . $version['serviceid']. ']', $substr);
             $count++;
         }
         $this->add_action_buttons();
index be082ab..1c906d6 100644 (file)
@@ -176,7 +176,7 @@ class core_calendar_renderer extends plugin_renderer_base {
      * @param mixed $pos BLOCK_POS_RIGHT | BLOCK_POS_LEFT
      */
     public function add_pretend_calendar_block(block_contents $bc, $pos=BLOCK_POS_RIGHT) {
-        $this->page->blocks->add_pretend_block($bc, $pos);
+        $this->page->blocks->add_fake_block($bc, $pos);
     }
 
     /**
index a5409db..66f6bad 100644 (file)
@@ -233,7 +233,6 @@ $CFG->admin = 'admin';
 // like AOL.
 //      $CFG->tracksessionip = true;
 //
-//
 // The following lines are for handling email bounces.
 //      $CFG->handlebounces = true;
 //      $CFG->minbounces = 10;
@@ -356,7 +355,6 @@ $CFG->admin = 'admin';
 //
 //     $CFG->extramemorylimit = 1G;
 //
-//
 //=========================================================================
 // 8. SETTINGS FOR DEVELOPMENT SERVERS - not intended for production use!!!
 //=========================================================================
index 5233d32..3cff561 100644 (file)
@@ -77,6 +77,8 @@ if ($mform->is_cancelled()) {
         // Update an existing category.
         $newcategory->id = $category->id;
         if ($newcategory->parent != $category->parent) {
+            // check category manage capability if parent changed
+            require_capability('moodle/category:manage', get_category_or_system_context((int)$newcategory->parent));
             $parent_cat = $DB->get_record('course_categories', array('id' => $newcategory->parent));
             move_category($newcategory, $parent_cat);
         }
index 3f86854..6318146 100644 (file)
@@ -8,17 +8,23 @@ class editcategory_form extends moodleform {
 
     // form definition
     function definition() {
-        global $CFG;
+        global $CFG, $DB;
         $mform =& $this->_form;
         $category = $this->_customdata['category'];
         $editoroptions = $this->_customdata['editoroptions'];
 
         // get list of categories to use as parents, with site as the first one
-        $options = array(get_string('top'));
+        $options = array();
+        if (has_capability('moodle/category:manage', get_system_context()) || $category->parent == 0) {
+            $options[0] = get_string('top');
+        }
         $parents = array();
         if ($category->id) {
             // Editing an existing category.
             make_categories_list($options, $parents, 'moodle/category:manage', $category->id);
+            if (empty($options[$category->parent])) {
+                $options[$category->parent] = $DB->get_field('course_categories', 'name', array('id'=>$category->parent));
+            }
             $strsubmit = get_string('savechanges');
         } else {
             // Making a new category
index c3d303d..07b7bc5 100644 (file)
@@ -130,7 +130,7 @@ class core_course_renderer extends plugin_renderer_base {
                 $coursecount ++;
                 $classes[] = ($coursecount%2)?'odd':'even';
                 $content .= html_writer::start_tag('div', array('class'=>join(' ', $classes)));
-                $content .= html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), format_text($course->fullname, FORMAT_HTML), array('class'=>$linkclass));
+                $content .= html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), format_string($course->fullname), array('class'=>$linkclass));
                 $content .= html_writer::start_tag('div', array('class'=>'course_info clearfix'));
 
                 // print enrol info
@@ -152,4 +152,4 @@ class core_course_renderer extends plugin_renderer_base {
         $content .= html_writer::end_tag('div');
         return $content;
     }
-}
\ No newline at end of file
+}
index d877c30..a40c807 100644 (file)
@@ -81,6 +81,15 @@ class enrol_database_plugin extends enrol_plugin {
 
         $ignorehidden     = $this->get_config('ignorehiddencourses');
 
+        if (!is_object($user) or !property_exists($user, 'id')) {
+            throw new coding_exception('Invalid $user parameter in sync_user_enrolments()');
+        }
+
+        if (!property_exists($user, $localuserfield)) {
+            debugging('Invalid $user parameter in sync_user_enrolments(), missing '.$localuserfield);
+            $user = $DB->get_record('user', array('id'=>$user->id));
+        }
+
         // create roles mapping
         $allroles = get_all_roles();
         if (!isset($allroles[$defaultrole])) {
index 04472b0..d16ab6c 100644 (file)
@@ -136,6 +136,15 @@ class enrol_ldap_plugin extends enrol_plugin {
             return;
         }
 
+        if (!is_object($user) or !property_exists($user, 'id')) {
+            throw new coding_exception('Invalid $user parameter in sync_user_enrolments()');
+        }
+
+        if (!property_exists($user, 'idnumbner')) {
+            debugging('Invalid $user parameter in sync_user_enrolments(), missing idnumber');
+            $user = $DB->get_record('user', array('id'=>$user->id));
+        }
+
         // We may need a lot of memory here
         @set_time_limit(0);
         raise_memory_limit(MEMORY_HUGE);
index f004cf8..37d0f29 100644 (file)
@@ -136,29 +136,29 @@ class jsportal {
         $output .= "    main.portal.blocks = new Array(".$blocksoutput.");\n";
         $output .= "    main.portal.strings['courseformat']='".$COURSE->format."';\n";
         $output .= "    main.portal.strings['wwwroot']='".$CFG->wwwroot."';\n";
-        $output .= "    main.portal.strings['marker']='".get_string('markthistopic', '', '_var_')."';\n";
-        $output .= "    main.portal.strings['marked']='".get_string('markedthistopic', '', '_var_')."';\n";
+        $output .= "    main.portal.strings['marker']='".addslashes_js(get_string('markthistopic', '', '_var_'))."';\n";
+        $output .= "    main.portal.strings['marked']='".addslashes_js(get_string('markedthistopic', '', '_var_'))."';\n";
         $output .= "    main.portal.numsections = ".$COURSE->numsections.";\n";
         $output .= "    main.portal.lastsection = ".$DB->get_field_sql("SELECT MAX(section) FROM {course_sections} WHERE course = ?", array($courseid)).";\n"; // needed for orphaned activities in unavailable sections
-        $output .= "    main.portal.strings['hide']='".get_string('hide')."';\n";
-        $output .= "    main.portal.strings['hidesection']='".get_string('hidesection', '', '_var_')."';\n";
-        $output .= "    main.portal.strings['show']='".get_string('show')."';\n";
-        $output .= "    main.portal.strings['delete']='".get_string('delete')."';\n";
-        $output .= "    main.portal.strings['move']='".get_string('move')."';\n";
-        $output .= "    main.portal.strings['movesection']='".get_string('movesection', '', '_var_')."';\n";
-        $output .= "    main.portal.strings['moveleft']='".get_string('moveleft')."';\n";
-        $output .= "    main.portal.strings['moveright']='".get_string('moveright')."';\n";
-        $output .= "    main.portal.strings['update']='".get_string('update')."';\n";
-        $output .= "    main.portal.strings['groupsnone']='".get_string('groupsnone')."';\n";
-        $output .= "    main.portal.strings['groupsseparate']='".get_string('groupsseparate')."';\n";
-        $output .= "    main.portal.strings['groupsvisible']='".get_string('groupsvisible')."';\n";
-        $output .= "    main.portal.strings['clicktochange']='".get_string('clicktochange')."';\n";
-        $output .= "    main.portal.strings['deletecheck']='".get_string('deletecheckfull','','_var_')."';\n";
-        $output .= "    main.portal.strings['resource']='".get_string('resource')."';\n";
-        $output .= "    main.portal.strings['activity']='".get_string('activity')."';\n";
+        $output .= "    main.portal.strings['hide']='".addslashes_js(get_string('hide'))."';\n";
+        $output .= "    main.portal.strings['hidesection']='".addslashes_js(get_string('hidesection', '', '_var_'))."';\n";
+        $output .= "    main.portal.strings['show']='".addslashes_js(get_string('show'))."';\n";
+        $output .= "    main.portal.strings['delete']='".addslashes_js(get_string('delete'))."';\n";
+        $output .= "    main.portal.strings['move']='".addslashes_js(get_string('move'))."';\n";
+        $output .= "    main.portal.strings['movesection']='".addslashes_js(get_string('movesection', '', '_var_'))."';\n";
+        $output .= "    main.portal.strings['moveleft']='".addslashes_js(get_string('moveleft'))."';\n";
+        $output .= "    main.portal.strings['moveright']='".addslashes_js(get_string('moveright'))."';\n";
+        $output .= "    main.portal.strings['update']='".addslashes_js(get_string('update'))."';\n";
+        $output .= "    main.portal.strings['groupsnone']='".addslashes_js(get_string('groupsnone'))."';\n";
+        $output .= "    main.portal.strings['groupsseparate']='".addslashes_js(get_string('groupsseparate'))."';\n";
+        $output .= "    main.portal.strings['groupsvisible']='".addslashes_js(get_string('groupsvisible'))."';\n";
+        $output .= "    main.portal.strings['clicktochange']='".addslashes_js(get_string('clicktochange'))."';\n";
+        $output .= "    main.portal.strings['deletecheck']='".addslashes_js(get_string('deletecheckfull','','_var_'))."';\n";
+        $output .= "    main.portal.strings['resource']='".addslashes_js(get_string('resource'))."';\n";
+        $output .= "    main.portal.strings['activity']='".addslashes_js(get_string('activity'))."';\n";
         $output .= "    main.portal.strings['sesskey']='".sesskey()."';\n";
         foreach (array_keys(get_plugin_list('mod')) as $modtype) {
-            $output .= "    main.portal.strings['modtype_".$modtype."']='".get_string('pluginname', 'mod_'.$modtype)."';\n";
+            $output .= "    main.portal.strings['modtype_".$modtype."']='".addslashes_js(get_string('pluginname', 'mod_'.$modtype))."';\n";
         }
         $output .= "    main.portal.icons['spacerimg']='".$OUTPUT->pix_url('spacer')."';\n";
         $output .= "    main.portal.icons['marker']='".$OUTPUT->pix_url('i/marker')."';\n";
index be0831f..2036658 100644 (file)
@@ -406,21 +406,34 @@ class block_manager {
      * Add something that looks like a block, but which isn't an actual block_instance,
      * to this page.
      *
-     * @param block_contents $bc the content of the block like thing.
+     * @param block_contents $bc the content of the block-like thing.
      * @param string $region a block region that exists on this page.
      */
-    public function add_pretend_block($bc, $region) {
+    public function add_fake_block($bc, $region) {
         $this->page->initialise_theme_and_output();
         if (!$this->is_known_region($region)) {
             $region = $this->get_default_region();
         }
         if (array_key_exists($region, $this->visibleblockcontent)) {
             throw new coding_exception('block_manager has already prepared the blocks in region ' .
-                    $region . 'for output. It is too late to add a pretend block.');
+                    $region . 'for output. It is too late to add a fake block.');
         }
         $this->extracontent[$region][] = $bc;
     }
 
+    /**
+     * When the block_manager class was created, the {@link add_fake_block()}
+     * was called add_pretend_block, which is inconsisted with
+     * {@link show_only_fake_blocks()}. To fix this inconsistency, this method
+     * was renamed to add_fake_block. Please update your code.
+     * @param block_contents $bc the content of the block-like thing.
+     * @param string $region a block region that exists on this page.
+     */
+    public function add_pretend_block($bc, $region) {
+        debugging(DEBUG_DEVELOPER, 'add_pretend_block has been renamed to add_fake_block. Please rename the method call in your code.');
+        $this->add_fake_block($bc, $region);
+    }
+
     /**
      * Checks to see whether all of the blocks within the given region are docked
      *
index 54b69a0..bda8fa3 100644 (file)
@@ -51,9 +51,8 @@ define('LASTACCESS_UPDATE_SECS', 60);
  * Returns $user object of the main admin user
  * primary admin = admin with lowest role_assignment id among admins
  *
- * @global object
- * @static object $myadmin
- * @return object An associative array representing the admin user.
+ * @static stdClass $mainadmin
+ * @return stdClass An associative array representing the admin user.
  */
 function get_admin() {
     static $mainadmin = null;
@@ -66,7 +65,8 @@ function get_admin() {
         //      for now return the first assigned admin
         $mainadmin = reset($admins);
     }
-    return $mainadmin;
+    // we must clone this otherwise code outside can break the static var
+    return clone($mainadmin);
 }
 
 /**
index a1a2b5a..1addd11 100644 (file)
@@ -212,7 +212,13 @@ class MoodleExcelWorksheet {
         $format = $this->MoodleExcelFormat2PearExcelFormat($format);
     /// Convert the date to Excel format
         $timezone = get_user_timezone_offset();
-        $value =  ((usertime($date) + (int)($timezone * HOURSECS * 2)) / 86400) + 25569;
+        if ($timezone == 99) {
+            // system timezone offset in seconds
+            $offset = (int)date('Z');
+        } else {
+            $offset = (int)($timezone * HOURSECS * 2);
+        }
+        $value = ((usertime($date) + $offset) / 86400) + 25569;
     /// Add  the date safely to the PEAR Worksheet
         $this->pear_excel_worksheet->writeNumber($row, $col, $value, $format);
     }
index c87d215..9f36c35 100644 (file)
@@ -176,3 +176,148 @@ class ContainsTagWithContents_test extends UnitTestCase {
 }
 
 
+/**
+ * Unit tests for the {@link ContainsSelectExpectation} class.
+ *
+ * @copyright 2010 The Open University.
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class ContainsSelectExpectation_test extends UnitTestCase {
+    function test_matching_select_passes() {
+        $expectation = new ContainsSelectExpectation('selectname', array('Choice1', 'Choice2'));
+        $this->assertTrue($expectation->test('
+                <select name="selectname">
+                    <option value="0">Choice1</option>
+                    <option value="1">Choice2</option>
+                </select>'));
+    }
+
+    function test_fails_if_no_select() {
+        $expectation = new ContainsSelectExpectation('selectname', array('Choice1', 'Choice2'));
+        $this->assertFalse($expectation->test('<span>should not match</span>'));
+    }
+
+    function test_select_with_missing_choices_fails() {
+        $expectation = new ContainsSelectExpectation('selectname', array('Choice1', 'Choice2'));
+        $this->assertFalse($expectation->test('
+                <select name="selectname">
+                    <option value="0">Choice1</option>
+                </select>'));
+    }
+
+    function test_select_with_extra_choices_fails() {
+        $expectation = new ContainsSelectExpectation('selectname', array('Choice1'));
+        $this->assertFalse($expectation->test('
+                <select name="selectname">
+                    <option value="0">Choice1</option>
+                    <option value="1">Choice2</option>
+                </select>'));
+    }
+
+    function test_select_with_wrong_order_choices_fails() {
+        $expectation = new ContainsSelectExpectation('selectname', array('Choice1'));
+        $this->assertFalse($expectation->test('
+                <select name="selectname">
+                    <option value="1">Choice2</option>
+                    <option value="0">Choice1</option>
+                </select>'));
+    }
+
+    function test_select_check_selected_pass() {
+        $expectation = new ContainsSelectExpectation('selectname',
+                array('key1' => 'Choice1', 'key2' => 'Choice2'), 'key2');
+        $this->assertTrue($expectation->test('
+                <select name="selectname">
+                    <option value="key1">Choice1</option>
+                    <option value="key2" selected="selected">Choice2</option>
+                </select>'));
+    }
+
+    function test_select_check_wrong_one_selected_fail() {
+        $expectation = new ContainsSelectExpectation('selectname',
+                array('key1' => 'Choice1', 'key2' => 'Choice2'), 'key2');
+        $this->assertFalse($expectation->test('
+                <select name="selectname">
+                    <option value="key1" selected="selected">Choice1</option>
+                    <option value="key2">Choice2</option>
+                </select>'));
+    }
+
+    function test_select_check_nothing_selected_fail() {
+        $expectation = new ContainsSelectExpectation('selectname',
+                array('key1' => 'Choice1', 'key2' => 'Choice2'), 'key2');
+        $this->assertFalse($expectation->test('
+                <select name="selectname">
+                    <option value="key1">Choice1</option>
+                    <option value="key2">Choice2</option>
+                </select>'));
+    }
+
+    function test_select_disabled_pass() {
+        $expectation = new ContainsSelectExpectation('selectname',
+                array('key1' => 'Choice1', 'key2' => 'Choice2'), null, false);
+        $this->assertTrue($expectation->test('
+                <select name="selectname" disabled="disabled">
+                    <option value="key1">Choice1</option>
+                    <option value="key2">Choice2</option>
+                </select>'));
+    }
+
+    function test_select_disabled_fail1() {
+        $expectation = new ContainsSelectExpectation('selectname',
+                array('key1' => 'Choice1', 'key2' => 'Choice2'), null, true);
+        $this->assertFalse($expectation->test('
+                <select name="selectname" disabled="disabled">
+                    <option value="key1">Choice1</option>
+                    <option value="key2">Choice2</option>
+                </select>'));
+    }
+
+    function test_select_disabled_fail2() {
+        $expectation = new ContainsSelectExpectation('selectname',
+                array('key1' => 'Choice1', 'key2' => 'Choice2'), null, false);
+        $this->assertFalse($expectation->test('
+                <select name="selectname">
+                    <option value="key1">Choice1</option>
+                    <option value="key2">Choice2</option>
+                </select>'));
+    }
+}
+
+
+/**
+ * Unit tests for the {@link DoesNotContainTagWithAttributes} class.
+ *
+ * @copyright 2010 The Open University
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class DoesNotContainTagWithAttributes_test extends UnitTestCase {
+    function test_simple() {
+        $content = <<<END
+<input id="qIhr6wWLTt3,1_omact_gen_14" name="qIhr6wWLTt3,1_omact_gen_14" onclick="if(this.hasSubmitted) { return false; } this.hasSubmitted=true; preSubmit(this.form); return true;" type="submit" value="Check" />
+END;
+        $expectation = new DoesNotContainTagWithAttributes('input',
+                array('type' => 'submit', 'name' => 'qIhr6wWLTt3,1_omact_gen_14', 'value' => 'Check'));
+        $this->assertFalse($expectation->test($content));
+    }
+
+    function test_zero_attr() {
+        $expectation = new DoesNotContainTagWithAttributes('span', array('class' => 0));
+        $this->assertFalse($expectation->test('<span class="0">message</span>'));
+    }
+
+    function test_zero_different_attr_ok() {
+        $expectation = new DoesNotContainTagWithAttributes('span', array('class' => 'shrub'));
+        $this->assertTrue($expectation->test('<span class="tree">message</span>'));
+    }
+
+    function test_blank_attr() {
+        $expectation = new DoesNotContainTagWithAttributes('span', array('class' => ''));
+        $this->assertFalse($expectation->test('<span class="">message</span>'));
+    }
+
+    function test_blank_attr_does_not_match_zero() {
+        $expectation = new ContainsTagWithAttributes('span', array('class' => ''));
+        $this->assertFalse($expectation->test('<span class="0">message</span>'));
+    }
+}
index 7f9b000..4af49a1 100644 (file)
@@ -123,6 +123,10 @@ class web_test extends UnitTestCase {
         $this->assertEqual('[edit]', html_to_text('<img src="edit.png" alt="edit" />'));
     }
 
+    public function test_html_to_text_image_with_backslash() {
+        $this->assertEqual('[\edit]', html_to_text('<img src="edit.png" alt="\edit" />'));
+    }
+
     public function test_html_to_text_nowrap() {
         $long = "Here is a long string, more than 75 characters long, since by default html_to_text wraps text at 75 chars.";
         $this->assertEqual($long, html_to_text($long, 0));
index 33a1975..7ae7766 100644 (file)
@@ -205,6 +205,18 @@ abstract class XMLStructureExpectation extends SimpleExpectation {
         libxml_use_internal_errors($prevsetting);
         return $parser;
     }
+
+    function testMessage($html) {
+        $parsererrors = $this->load_xml($html);
+        if (is_array($parsererrors)) {
+            foreach ($parsererrors as $key => $message) {
+                $parsererrors[$key] = $message->message;
+            }
+            return 'Could not parse XML [' . $html . '] errors were [' . 
+                    implode('], [', $parsererrors) . ']';
+        }
+        return $this->customMessage($html);
+    }
 }
 /**
  * An Expectation that looks to see whether some HMTL contains a tag with a certain attribute.
@@ -226,6 +238,9 @@ class ContainsTagWithAttribute extends XMLStructureExpectation {
 
     function test($html) {
         $parser = $this->load_xml($html);
+        if (is_array($parser)) {
+            return false;
+        }
         $list = $parser->getElementsByTagName($this->tag);
 
         foreach ($list as $node) {
@@ -236,7 +251,7 @@ class ContainsTagWithAttribute extends XMLStructureExpectation {
         return false;
     }
 
-    function testMessage($html) {
+    function customMessage($html) {
         return 'Content [' . $html . '] does not contain the tag [' .
                 $this->tag . '] with attribute [' . $this->attribute . '="' . $this->value . '"].';
     }
@@ -277,8 +292,11 @@ class ContainsTagWithAttributes extends XMLStructureExpectation {
 
     function test($html) {
         $parser = $this->load_xml($html);
-        $list = $parser->getElementsByTagName($this->tag);
+        if (is_array($parser)) {
+            return false;
+        }
 
+        $list = $parser->getElementsByTagName($this->tag);
         $foundamatch = false;
 
         // Iterating through inputs
@@ -319,7 +337,7 @@ class ContainsTagWithAttributes extends XMLStructureExpectation {
         return $foundamatch;
     }
 
-    function testMessage($html) {
+    function customMessage($html) {
         $output = 'Content [' . $html . '] ';
 
         if (preg_match('/forbiddenmatch:(.*):(.*)/', $this->failurereason, $matches)) {
@@ -337,6 +355,135 @@ class ContainsTagWithAttributes extends XMLStructureExpectation {
     }
 }
 
+/**
+ * An Expectation that looks to see whether some HMTL contains a tag with an array of attributes.
+ * All attributes must be present and their values must match the expected values.
+ * A third parameter can be used to specify attribute=>value pairs which must not be present in a positive match.
+ *
+ * @copyright 2010 The Open University
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class ContainsSelectExpectation extends XMLStructureExpectation {
+    /**
+     * @var string $tag The name of the Tag to search
+     */
+    protected $name;
+    /**
+     * @var array $expectedvalues An associative array of parameters, all of which must be matched
+     */
+    protected $choices;
+    /**
+     * @var array $forbiddenvalues An associative array of parameters, none of which must be matched
+     */
+    protected $selected;
+    /**
+     * @var string $failurereason The reason why the test failed: nomatch or forbiddenmatch
+     */
+    protected $enabled;
+
+    function __construct($name, $choices, $selected = null, $enabled = null, $message = '%s') {
+        parent::__construct($message);
+        $this->name = $name;
+        $this->choices = $choices;
+        $this->selected = $selected;
+        $this->enabled = $enabled;
+    }
+
+    function test($html) {
+        $parser = $this->load_xml($html);
+        if (is_array($parser)) {
+            return false;
+        }
+
+        $list = $parser->getElementsByTagName('select');
+
+        // Iterating through inputs
+        foreach ($list as $node) {
+            if (empty($node->attributes) || !is_a($node->attributes, 'DOMNamedNodeMap')) {
+                continue;
+            }
+
+            if ($node->getAttribute('name') != $this->name) {
+                continue;
+            }
+
+            if ($this->enabled === true && $node->getAttribute('disabled')) {
+                continue;
+            } else if ($this->enabled === false && $node->getAttribute('disabled') != 'disabled') {
+                continue;
+            }
+
+            $options = $node->getElementsByTagName('option');
+            reset($this->choices);
+            foreach ($options as $option) {
+                if ($option->getAttribute('value') != key($this->choices)) {
+                    continue 2;
+                }
+                if ($option->firstChild->wholeText != current($this->choices)) {
+                    continue 2;
+                }
+                if ($option->getAttribute('value') === $this->selected &&
+                        !$option->hasAttribute('selected')) {
+                    continue 2;
+                }
+                next($this->choices);
+            }
+            if (current($this->choices) !== false) {
+                // The HTML did not contain all the choices.
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    function customMessage($html) {
+        if ($this->enabled === true) {
+            $state = 'an enabled';
+        } else if ($this->enabled === false) {
+            $state = 'a disabled';
+        } else {
+            $state = 'a';
+        }
+        $output = 'Content [' . $html . '] does not contain ' . $state .
+                ' <select> with name ' . $this->name . ' and choices ' .
+                implode(', ', $this->choices);
+        if ($this->selected) {
+            $output .= ' with ' . $this->selected . ' selected).';
+        }
+
+        return $output;
+    }
+}
+
+/**
+ * The opposite of {@link ContainsTagWithAttributes}. The test passes only if
+ * the HTML does not contain a tag with the given attributes.
+ *
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class DoesNotContainTagWithAttributes extends ContainsTagWithAttributes {
+    function __construct($tag, $expectedvalues, $message = '%s') {
+        parent::__construct($tag, $expectedvalues, array(), $message);
+    }
+    function test($html) {
+        return !parent::test($html);
+    }
+    function customMessage($html) {
+        $output = 'Content [' . $html . '] ';
+
+        $output .= 'contains the tag [' . $this->tag . '] with attributes [';
+        foreach ($this->expectedvalues as $var => $val) {
+            $output .= "$var=\"$val\" ";
+        }
+        $output = rtrim($output);
+        $output .= '].';
+
+        return $output;
+    }
+}
+
 /**
  * An Expectation that looks to see whether some HMTL contains a tag with a certain text inside it.
  *
index 8031ea1..18861e7 100644 (file)
@@ -3,13 +3,15 @@
  * Used when editing a data template
  */
 function insert_field_tags(selectlist) {
-  if (typeof(currEditor) != 'undefined' && currEditor._editMode == 'wysiwyg') {
-    // HTMLArea-specific
-     currEditor.insertHTML(selectlist.options[selectlist.selectedIndex].value);
-  } else {
-    // For inserting when in HTMLArea code view or for normal textareas
-     insertAtCursor(currTextarea, selectlist.options[selectlist.selectedIndex].value);
-  }
+    var value = selectlist.options[selectlist.selectedIndex].value;
+    var editorname = 'template';
+    if (typeof tinyMCE == 'undefined') {
+        var element = document.getElementsByName(editorname)[0];
+        // For inserting when in normal textareas
+        insertAtCursor(element, value);
+    } else {
+        tinyMCE.execInstanceCommand(editorname, 'mceInsertContent', false, value);
+    }
 }
 
 /**
index ebfebde..90cf012 100755 (executable)
@@ -82,13 +82,6 @@ $strdata = get_string('modulenameplural','data');
 // For the javascript for inserting template tags: initialise the default textarea to
 // 'edit_template' - it is always present in all different possible views.
 
-$editorobj = 'editor_'.md5('template');
-
-$bodytag = 'onload="';
-$bodytag .= 'if (typeof('.$editorobj.') != \'undefined\') { currEditor = '.$editorobj.'; } ';
-$bodytag .= 'currTextarea = document.getElementById(\'tempform\').template;';
-$bodytag .= '" ';
-
 if ($mode == 'singletemplate') {
     $PAGE->navbar->add(get_string($mode,'data'));
 }
index afc804c..01e1c5c 100644 (file)
@@ -461,8 +461,9 @@ function forum_cron() {
             // caching subscribed users of each forum
             if (!isset($subscribedusers[$forumid])) {
                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
-                if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext)) {
+                if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
                     foreach ($subusers as $postuser) {
+                        unset($postuser->description); // not necessary
                         // this user is subscribed to this forum
                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
                         // this user is a user we have to process later
@@ -530,6 +531,7 @@ function forum_cron() {
                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
                     $userfrom = $users[$post->userid];
                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
+                    unset($userfrom->description); // not necessary
                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
                 } else {
                     mtrace('Could not find user '.$post->userid);
@@ -2796,9 +2798,10 @@ function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort
  * @param forum $forum the forum
  * @param integer $groupid group id, or 0 for all.
  * @param object $context the forum context, to save re-fetching it where possible.
+ * @param string $fields requested user fields (with "u." table prefix)
  * @return array list of users.
  */
-function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
+function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
     global $CFG, $DB;
     $params = array($forum->id);
 
@@ -2811,25 +2814,27 @@ function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
         $groupselect = '';
     }
 
-    $fields ="u.id,
-              u.username,
-              u.firstname,
-              u.lastname,
-              u.maildisplay,
-              u.mailformat,
-              u.maildigest,
-              u.imagealt,
-              u.email,
-              u.city,
-              u.country,
-              u.lastaccess,
-              u.lastlogin,
-              u.picture,
-              u.timezone,
-              u.theme,
-              u.lang,
-              u.trackforums,
-              u.mnethostid";
+    if (empty($fields)) {
+        $fields ="u.id,
+                  u.username,
+                  u.firstname,
+                  u.lastname,
+                  u.maildisplay,
+                  u.mailformat,
+                  u.maildigest,
+                  u.imagealt,
+                  u.email,
+                  u.city,
+                  u.country,
+                  u.lastaccess,
+                  u.lastlogin,
+                  u.picture,
+                  u.timezone,
+                  u.theme,
+                  u.lang,
+                  u.trackforums,
+                  u.mnethostid";
+    }
 
     if (forum_is_forcesubscribed($forum)) {
         if (empty($context)) {
index 579ea45..67fc212 100644 (file)
@@ -27,6 +27,13 @@ if (!$entry->approved and confirm_sesskey()) {
     $newentry->approved     = 1;
     $newentry->timemodified = time(); // wee need this date here to speed up recent activity, TODO: use timestamp in approved field instead in 2.0
     $DB->update_record("glossary_entries", $newentry);
+
+    // Update completion state
+    $completion = new completion_info($course);
+    if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries) {
+        $completion->update_state($cm, COMPLETION_COMPLETE, $entry->userid);
+    }
+
     add_to_log($course->id, "glossary", "approve entry", "showentry.php?id=$cm->id&amp;eid=$eid", "$eid", $cm->id);
 }
 
index 585250a..e0186e0 100644 (file)
@@ -43,7 +43,7 @@ class backup_glossary_activity_structure_step extends backup_activity_structure_
             'allowcomments', 'allowprintview', 'usedynalink', 'defaultapproval',
             'globalglossary', 'entbypage', 'editalways', 'rsstype',
             'rssarticles', 'assessed', 'assesstimestart', 'assesstimefinish',
-            'scale', 'timecreated', 'timemodified'));
+            'scale', 'timecreated', 'timemodified', 'completionentries'));
 
         $entries = new backup_nested_element('entries');
 
old mode 100644 (file)
new mode 100755 (executable)
index b7f0aba..e0502e8
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="mod/glossary/db" VERSION="20100428" COMMENT="XMLDB file for Moodle mod/glossary"
+<XMLDB PATH="mod/glossary/db" VERSION="20101115" COMMENT="XMLDB file for Moodle mod/glossary"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
 >
@@ -31,7 +31,8 @@
         <FIELD NAME="assesstimefinish" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="assesstimestart" NEXT="scale"/>
         <FIELD NAME="scale" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" DEFAULT="0" SEQUENCE="false" PREVIOUS="assesstimefinish" NEXT="timecreated"/>
         <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="scale" NEXT="timemodified"/>
-        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="timecreated"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="timecreated" NEXT="completionentries"/>
+        <FIELD NAME="completionentries" TYPE="int" LENGTH="9" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="Non zero if a certain number of entries are required to mark this glossary complete for a user." PREVIOUS="timemodified"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
index 7d155b6..f6507cf 100644 (file)
@@ -309,6 +309,22 @@ function xmldb_glossary_upgrade($oldversion) {
 
         upgrade_mod_savepoint(true, 2010111500, 'glossary');
     }
+
+    if ($oldversion < 2010111501) {
+
+        // Define field completionentries to be added to glossary
+        $table = new xmldb_table('glossary');
+        $field = new xmldb_field('completionentries', XMLDB_TYPE_INTEGER, '9', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'timemodified');
+
+        // Conditionally launch add field completionentries
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // glossary savepoint reached
+        upgrade_mod_savepoint(true, 2010111501, 'glossary');
+    }
+
     return true;
 }
 
index dbaccf4..fe1b709 100644 (file)
@@ -96,6 +96,12 @@ if ($confirm and confirm_sesskey()) { // the operation was confirmed.
         $DB->delete_records("glossary_alias", array("entryid"=>$entry->id));
         $DB->delete_records("glossary_entries", array("id"=>$entry->id));
 
+        // Update completion state
+        $completion = new completion_info($course);
+        if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries) {
+            $completion->update_state($cm, COMPLETION_INCOMPLETE, $entry->userid);
+        }
+
         //delete glossary entry ratings
         require_once($CFG->dirroot.'/rating/lib.php');
         $delopt = new stdclass();
index c59d6c7..d69ae54 100644 (file)
@@ -119,6 +119,13 @@ if ($mform->is_cancelled()){
     if (empty($entry->id)) {
         //new entry
         $entry->id = $DB->insert_record('glossary_entries', $entry);
+
+        // Update completion state
+        $completion = new completion_info($course);
+        if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries && $entry->approved) {
+            $completion->update_state($cm, COMPLETION_COMPLETE);
+        }
+
         add_to_log($course->id, "glossary", "add entry",
                    "view.php?id=$cm->id&amp;mode=entry&amp;hook=$entry->id", $entry->id, $cm->id);
 
index 06d15d3..85090f4 100644 (file)
@@ -63,7 +63,7 @@ function glossary_filter($courseid, $text) {
                                               'glossaryid IN ('.$glossarylist.') AND usedynalink != 0', null, '',
                                               'id,glossaryid,name AS concept, 1 AS casesensitive, 1 AS category, 1 AS fullmatch');
 
-        $aliases = $DB->get_records_sql('SELECT ga.id, ge.glossaryid, ga.alias as concept, ge.concept as originalconcept,
+        $aliases = $DB->get_records_sql('SELECT ga.id, ge.id AS entryid, ge.glossaryid, ga.alias AS concept, ge.concept AS originalconcept,
                                                 casesensitive, 0 AS category, fullmatch
                                            FROM {glossary_alias} ga,
                                                 {glossary_entries} ge
@@ -123,33 +123,36 @@ function glossary_filter($courseid, $text) {
         foreach ($concepts as $concept) {
 
             $glossaryname = str_replace(':', '-', $glossaries[$concept->glossaryid]);
+
             if ($concept->category) {       // Link to a category
                 $title = strip_tags($glossaryname.': '.$strcategory.' '.$concept->concept);
                 $href_tag_begin = '<a class="glossary autolink glossaryid'.$concept->glossaryid.'" title="'.$title.'" '.
                                   'href="'.$CFG->wwwroot.'/mod/glossary/view.php?g='.$concept->glossaryid.
                                   '&amp;mode=cat&amp;hook='.$concept->id.'">';
-            } else {
-                if (!empty($concept->originalconcept)) {  // We are dealing with an alias (so show original)
-                    //$encodedconcept = urlencode($concept->originalconcept);
+
+            } else { // Link to entry or alias
+                if (!empty($concept->originalconcept)) {  // We are dealing with an alias (so show and point to original)
                     $title = str_replace('"', "'", strip_tags($glossaryname.': '.$concept->originalconcept));
-                } else {
-                    //$encodedconcept = urlencode($concept->concept);
+                    $concept->id = $concept->entryid;
+                } else { // This is an entry
                     $title = str_replace('"', "'", strip_tags($glossaryname.': '.$concept->concept));
                 }
-                //hardcoding dictionary format in the URL rather than defaulting to the current glossary format which may not work in a popup.
-                //for example "entry list" means the popup would only contain a link that opens another popup.
+                // hardcoding dictionary format in the URL rather than defaulting
+                // to the current glossary format which may not work in a popup.
+                // for example "entry list" means the popup would only contain
+                // a link that opens another popup.
                 $link = new moodle_url('/mod/glossary/showentry.php', array('courseid'=>$courseid, 'eid'=>$concept->id, 'displayformat'=>'dictionary'));
                 $attributes = array(
-                    'href'=>$link,
-                    'title'=>$title,
-                    'class'=>'glossary autolink glossaryid'.$concept->glossaryid);
+                    'href' => $link,
+                    'title'=> $title,
+                    'class'=> 'glossary autolink glossaryid'.$concept->glossaryid);
 
-                //this flag is optionally set by resource_pluginfile()
-                //if processing an embedded file use target to prevent getting nested Moodles
+                // this flag is optionally set by resource_pluginfile()
+                // if processing an embedded file use target to prevent getting nested Moodles
                 if (isset($CFG->embeddedsoforcelinktarget) && $CFG->embeddedsoforcelinktarget) {
                     $attributes['target'] = '_top';
                 }
-                
+
                 $href_tag_begin = html_writer::start_tag('a', $attributes);
             }
             $conceptlist[] = new filterobject($concept->concept, $href_tag_begin, '</a>',
index f060839..278ecc1 100644 (file)
@@ -79,6 +79,8 @@ $string['commentdeleted'] = 'The comment has been deleted.';
 $string['comments'] = 'Comments';
 $string['commentson'] = 'Comments on';
 $string['commentupdated'] = 'The comment has been updated.';
+$string['completionentries'] = 'Student must create entries:';
+$string['completionentriesgroup'] = 'Require entries';
 $string['concept'] = 'Concept';
 $string['concepts'] = 'Concepts';
 $string['configenablerssfeeds'] = 'This switch will enable the possibility of RSS feeds for all glossaries.  You will still need to turn feeds on manually in the settings for each glossary.';
index a0e1371..0f58ef0 100644 (file)
@@ -24,6 +24,7 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 require_once($CFG->dirroot . '/rating/lib.php');
+require_once($CFG->libdir . '/completionlib.php');
 
 define("GLOSSARY_SHOW_ALL_CATEGORIES", 0);
 define("GLOSSARY_SHOW_NOT_CATEGORISED", -1);
@@ -2537,6 +2538,7 @@ function glossary_supports($feature) {
         case FEATURE_GROUPMEMBERSONLY:        return true;
         case FEATURE_MOD_INTRO:               return true;
         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
+        case FEATURE_COMPLETION_HAS_RULES:    return true;
         case FEATURE_GRADE_HAS_GRADE:         return true;
         case FEATURE_GRADE_OUTCOMES:          return true;
         case FEATURE_RATE:                    return true;
@@ -2546,6 +2548,42 @@ function glossary_supports($feature) {
     }
 }
 
+/**
+ * Obtains the automatic completion state for this glossary based on any conditions
+ * in glossary settings.
+ *
+ * @global object
+ * @global object
+ * @param object $course Course
+ * @param object $cm Course-module
+ * @param int $userid User ID
+ * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
+ * @return bool True if completed, false if not. (If no conditions, then return
+ *   value depends on comparison type)
+ */
+function glossary_get_completion_state($course,$cm,$userid,$type) {
+    global $CFG, $DB;
+
+    // Get glossary details
+    if (!($glossary=$DB->get_record('glossary',array('id'=>$cm->instance)))) {
+        throw new Exception("Can't find glossary {$cm->instance}");
+    }
+
+    $result=$type; // Default return value
+
+    if ($glossary->completionentries) {
+        $value = $glossary->completionentries <=
+                 $DB->count_records('glossary_entries',array('glossaryid'=>$glossary->id, 'userid'=>$userid, 'approved'=>1));
+        if ($type == COMPLETION_AND) {
+            $result = $result && $value;
+        } else {
+            $result = $result || $value;
+        }
+    }
+
+    return $result;
+}
+
 function glossary_extend_navigation($navigation, $course, $module, $cm) {
     global $CFG;
     $navigation->add(get_string('standardview', 'glossary'), new moodle_url('/mod/glossary/view.php', array('id'=>$cm->id, 'mode'=>'letter')));
index fe71488..f5c21bd 100644 (file)
@@ -156,6 +156,45 @@ class mod_glossary_mod_form extends moodleform_mod {
 
     function data_preprocessing(&$default_values){
         parent::data_preprocessing($default_values);
+
+        // Set up the completion checkboxes which aren't part of standard data.
+        // We also make the default value (if you turn on the checkbox) for those
+        // numbers to be 1, this will not apply unless checkbox is ticked.
+        $default_values['completionentriesenabled']=
+            !empty($default_values['completionentries']) ? 1 : 0;
+        if (empty($default_values['completionentries'])) {
+            $default_values['completionentries']=1;
+        }
+    }
+
+    function add_completion_rules() {
+        $mform =& $this->_form;
+
+        $group=array();
+        $group[] =& $mform->createElement('checkbox', 'completionentriesenabled', '', get_string('completionentries','glossary'));
+        $group[] =& $mform->createElement('text', 'completionentries', '', array('size'=>3));
+        $mform->setType('completionentries', PARAM_INT);
+        $mform->addGroup($group, 'completionentriesgroup', get_string('completionentriesgroup','glossary'), array(' '), false);
+        $mform->disabledIf('completionentries','completionentriesenabled','notchecked');
+
+        return array('completionentriesgroup');
+    }
+
+    function completion_rule_enabled($data) {
+        return (!empty($data['completionentriesenabled']) && $data['completionentries']!=0);
+    }
+
+    function get_data() {
+        $data = parent::get_data();
+        if (!$data) {
+            return false;
+        }
+        // Turn off completion settings if the checkboxes aren't ticked
+        $autocompletion = !empty($data->completion) && $data->completion==COMPLETION_TRACKING_AUTOMATIC;
+        if (empty($data->completionentriesenabled) || !$autocompletion) {
+            $data->completionentries = 0;
+        }
+        return $data;
     }
 
 }
index 440ac75..5f7050f 100644 (file)
@@ -5,7 +5,7 @@
 ///  This fragment is called by moodle_needs_upgrading() and /admin/index.php
 /////////////////////////////////////////////////////////////////////////////////
 
-$module->version  = 2010111500;
+$module->version  = 2010111501;
 $module->requires = 2010080300;  // Requires this Moodle version
 $module->cron     = 0;           // Period for cron to check this module (secs)
 
index b9e3634..d207aae 100644 (file)
@@ -67,7 +67,16 @@ if (!$canmanage) {
 
 // record answer (if necessary) and show response (if none say if answer is correct or not)
 $page = $lesson->load_page(required_param('pageid', PARAM_INT));
-$result = $page->record_attempt($context);
+// Check the page has answers [MDL-25632]
+if (count($page->answers) > 0) {
+    $result = $page->record_attempt($context);
+} else {
+    // The page has no answers so we will just progress to the next page in the
+    // sequence (as set by newpageid).
+    $result = new stdClass;
+    $result->newpageid       = optional_param('newpageid', $page->nextpageid, PARAM_INT);
+    $result->nodefaultresponse  = true;
+}
 
 if (isset($USER->modattempts[$lesson->id])) {
     // make sure if the student is reviewing, that he/she sees the same pages/page path that he/she saw the first time
@@ -156,7 +165,7 @@ $PAGE->set_url('/mod/lesson/view.php', array('id' => $cm->id, 'pageid' => $page-
 $PAGE->set_subpage($page->id);
 
 /// Print the header, heading and tabs
-lesson_add_pretend_blocks($PAGE, $cm, $lesson, $timer);
+lesson_add_fake_blocks($PAGE, $cm, $lesson, $timer);
 echo $lessonoutput->header($lesson, $cm, 'view', true, $page->id);
 
 if ($lesson->displayleft) {
index fff58f4..8de5538 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+/**
+ *
+ * @global stdClass $CFG
+ * @global moodle_database $DB
+ * @global core_renderer $OUTPUT
+ * @param int $oldversion
+ * @return bool 
+ */
 function xmldb_lesson_upgrade($oldversion) {
     global $CFG, $DB, $OUTPUT;
 
@@ -236,7 +244,50 @@ function xmldb_lesson_upgrade($oldversion) {
 
         upgrade_mod_savepoint(true, 2010081200, 'lesson');
     }
-
+    
+    
+    if ($oldversion < 2010121400) {
+        // Fix matching question pages.
+        // In Moodle 1.9 matching question pages stored the correct and incorrect
+        // jumps on the third and forth answers, in Moodle 2.0 they are stored
+        // in the first and second answers.
+        // This upgrade block detects matching questions where this is the case
+        // and fixed it by making firstjump = thirdjump && secondjump = forthjump.
+        $pages = $DB->get_recordset('lesson_pages', array('qtype'=>'5'));
+        foreach ($pages as $page) {
+            $answers = $DB->get_records('lesson_answers', array('pageid'=>$page->id), 'id', 'id, jumpto', 0, 4);
+            if (count($answers) < 4) {
+                // If there are less then four answers the problem wont exist.
+                // All Moodle 1.9 matching questions had a least 4 answers.
+                continue;
+            }
+            $first  = array_shift($answers);
+            $second = array_shift($answers);
+            $third  = array_shift($answers);
+            $forth  = array_shift($answers);
+            if ($first->jumpto !== '0' || $second->jumpto !== '0') {
+                // If either are set to something other than the next page then
+                // there is no problem.
+                continue;
+            }
+            if ($third->jumpto !== '0') {
+                $first->jumpto = $third->jumpto;
+                $DB->update_record('lesson_answers', $first);
+                $third->jumpto = '0';
+                $DB->update_record('lesson_answers', $third);
+            }
+            if ($forth->jumpto !== '0') {
+                $second->jumpto = $forth->jumpto;
+                $DB->update_record('lesson_answers', $second);
+                $forth->jumpto = '0';
+                $DB->update_record('lesson_answers', $forth);
+            }
+        }
+        // Close the record set
+        $pages->close();
+        
+        upgrade_mod_savepoint(true, 2010121400, 'lesson');
+    }
 
     return true;
 }
index 35b8982..0f7881c 100644 (file)
@@ -411,23 +411,23 @@ function lesson_displayleftif($lesson) {
  * @param $page
  * @return unknown_type
  */
-function lesson_add_pretend_blocks($page, $cm, $lesson, $timer = null) {
+function lesson_add_fake_blocks($page, $cm, $lesson, $timer = null) {
     $bc = lesson_menu_block_contents($cm->id, $lesson);
     if (!empty($bc)) {
         $regions = $page->blocks->get_regions();
         $firstregion = reset($regions);
-        $page->blocks->add_pretend_block($bc, $firstregion);
+        $page->blocks->add_fake_block($bc, $firstregion);
     }
 
     $bc = lesson_mediafile_block_contents($cm->id, $lesson);
     if (!empty($bc)) {
-        $page->blocks->add_pretend_block($bc, $page->blocks->get_default_region());
+        $page->blocks->add_fake_block($bc, $page->blocks->get_default_region());
     }
 
     if (!empty($timer)) {
         $bc = lesson_clock_block_contents($cm->id, $lesson, $timer, $page);
         if (!empty($bc)) {
-            $page->blocks->add_pretend_block($bc, $page->blocks->get_default_region());
+            $page->blocks->add_fake_block($bc, $page->blocks->get_default_region());
         }
     }
 }
@@ -1881,7 +1881,9 @@ abstract class lesson_page extends lesson_base {
             $this->answers = array();
             $answers = $DB->get_records('lesson_answers', array('pageid'=>$this->properties->id, 'lessonid'=>$this->lesson->id), 'id');
             if (!$answers) {
-                debugging(get_string('cannotfindanswer', 'lesson'));
+                // It is possible that a lesson upgraded from Moodle 1.9 still
+                // contains questions without any answers [MDL-25632].
+                // debugging(get_string('cannotfindanswer', 'lesson'));
                 return array();
             }
             foreach ($answers as $answer) {
@@ -1913,6 +1915,7 @@ abstract class lesson_page extends lesson_base {
      * Records an attempt at this page
      *
      * @final
+     * @global moodle_database $DB
      * @param stdClass $context
      * @return stdClass Returns the result of the attempt
      */
@@ -1958,7 +1961,7 @@ abstract class lesson_page extends lesson_base {
                 if (!$result->correctanswer && ($result->newpageid == 0)) {
                     // wrong answer and student is stuck on this page - check how many attempts
                     // the student has had at this page/question
-                    $nattempts = $DB->count_records("lesson_attempts", array("pageid"=>$this->properties->id, "userid"=>$USER->id),"retry", $nretakes);
+                    $nattempts = $DB->count_records("lesson_attempts", array("pageid"=>$this->properties->id, "userid"=>$USER->id, "retry" => $nretakes));
                     // retreive the number of attempts left counter for displaying at bottom of feedback page
                     if ($nattempts >= $this->lesson->maxattempts) {
                         if ($this->lesson->maxattempts > 1) { // don't bother with message if only one attempt
@@ -2133,13 +2136,17 @@ abstract class lesson_page extends lesson_base {
                 $this->answers[$i]->pageid = $this->id;
                 $this->answers[$i]->timecreated = $this->timecreated;
             }
-            if (!empty($properties->answer_editor[$i])) {
+
+            if (!empty($properties->answer_editor[$i]) && is_array($properties->answer_editor[$i])) {
                 $this->answers[$i]->answer = $properties->answer_editor[$i]['text'];
                 $this->answers[$i]->answerformat = $properties->answer_editor[$i]['format'];
-                if (isset($properties->response_editor[$i])) {
-                    $this->answers[$i]->response = $properties->response_editor[$i]['text'];
-                    $this->answers[$i]->responseformat = $properties->response_editor[$i]['format'];
-                }
+            }
+            if (!empty($properties->response_editor[$i]) && is_array($properties->response_editor[$i])) {
+                $this->answers[$i]->response = $properties->response_editor[$i]['text'];
+                $this->answers[$i]->responseformat = $properties->response_editor[$i]['format'];
+            }
+
+            if (!empty($this->answers[$i]->answer)) {
                 if (isset($properties->jumpto[$i])) {
                     $this->answers[$i]->jumpto = $properties->jumpto[$i];
                 }
@@ -2152,8 +2159,9 @@ abstract class lesson_page extends lesson_base {
                     $DB->update_record("lesson_answers", $this->answers[$i]->properties());
                 }
 
-            } else {
-                break;
+            } else if (isset($this->answers[$i]->id)) {
+                $DB->delete_records('lesson_answers', array('id'=>$this->answers[$i]->id));
+                unset($this->answers[$i]);
             }
         }
         return true;
@@ -2221,13 +2229,17 @@ abstract class lesson_page extends lesson_base {
 
         for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
             $answer = clone($newanswer);
-            if (!empty($properties->answer_editor[$i])) {
+
+            if (!empty($properties->answer_editor[$i]) && is_array($properties->answer_editor[$i])) {
                 $answer->answer = $properties->answer_editor[$i]['text'];
                 $answer->answerformat = $properties->answer_editor[$i]['format'];
-                if (isset($properties->response_editor[$i])) {
-                    $answer->response = $properties->response_editor[$i]['text'];
-                    $answer->responseformat = $properties->response_editor[$i]['format'];
-                }
+            }
+            if (!empty($properties->response_editor[$i]) && is_array($properties->response_editor[$i])) {
+                $answer->response = $properties->response_editor[$i]['text'];
+                $answer->responseformat = $properties->response_editor[$i]['format'];
+            }
+
+            if (!empty($answer->answer)) {
                 if (isset($properties->jumpto[$i])) {
                     $answer->jumpto = $properties->jumpto[$i];
                 }
@@ -2470,6 +2482,8 @@ abstract class lesson_page extends lesson_base {
             foreach ($answers as $answer) {
                 $jumps[] = $this->get_jump_name($answer->jumpto);
             }
+        } else {
+            $jumps[] = $this->get_jump_name($this->properties->nextpageid);
         }
         return $jumps;
     }
index 7961bce..5b6cabd 100644 (file)
@@ -64,6 +64,10 @@ class lesson_page_type_branchtable extends lesson_page {
                 }
                 $jumps[] = $this->get_jump_name($answer->jumpto);
             }
+        } else {
+            // We get here is the lesson was created on a Moodle 1.9 site and
+            // the lesson contains question pages without any answers.
+            $jumps[] = $this->get_jump_name($this->properties->nextpageid);
         }
         return $jumps;
     }
index 9903a6d..43335c0 100644 (file)
@@ -105,13 +105,16 @@ class lesson_page_type_matching extends lesson_page {
         $this->lesson->maxanswers = $this->lesson->maxanswers + 2;
         for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
             $answer = clone($newanswer);
-            if (!empty($properties->answer_editor[$i])) {
+            if (!empty($properties->answer_editor[$i]) && is_array($properties->answer_editor[$i])) {
                 $answer->answer = $properties->answer_editor[$i]['text'];
                 $answer->answerformat = $properties->answer_editor[$i]['format'];
-                if (isset($properties->response_editor[$i])) {
-                    $answer->response = $properties->response_editor[$i]['text'];
-                    $answer->responseformat = $properties->response_editor[$i]['format'];
-                }
+            }
+            if (!empty($properties->response_editor[$i]) && is_array($properties->response_editor[$i])) {
+                $answer->response = $properties->response_editor[$i]['text'];
+                $answer->responseformat = $properties->response_editor[$i]['format'];
+            }
+
+            if (!empty($answer->answer)) {
                 if (isset($properties->jumpto[$i])) {
                     $answer->jumpto = $properties->jumpto[$i];
                 }
@@ -276,6 +279,14 @@ class lesson_page_type_matching extends lesson_page {
         }
         return $table;
     }
+    /**
+     * Updates the page and its answers
+     *
+     * @global moodle_database $DB
+     * @global moodle_page $PAGE
+     * @param stdClass $properties
+     * @return bool
+     */
     public function update($properties) {
         global $DB, $PAGE;
         $answers  = $this->get_answers();
@@ -293,13 +304,17 @@ class lesson_page_type_matching extends lesson_page {
                 $this->answers[$i]->pageid = $this->id;
                 $this->answers[$i]->timecreated = $this->timecreated;
             }
-            if (!empty($properties->answer_editor[$i])) {
+
+            if (!empty($properties->answer_editor[$i]) && is_array($properties->answer_editor[$i])) {
                 $this->answers[$i]->answer = $properties->answer_editor[$i]['text'];
                 $this->answers[$i]->answerformat = $properties->answer_editor[$i]['format'];
-                if (isset($properties->response_editor[$i])) {
-                    $this->answers[$i]->response = $properties->response_editor[$i]['text'];
-                    $this->answers[$i]->responseformat = $properties->response_editor[$i]['format'];
-                }
+            }
+            if (!empty($properties->response_editor[$i]) && is_array($properties->response_editor[$i])) {
+                $this->answers[$i]->response = $properties->response_editor[$i]['text'];
+                $this->answers[$i]->responseformat = $properties->response_editor[$i]['format'];
+            }
+
+            if (!empty($this->answers[$i]->answer)) {
                 if (isset($properties->jumpto[$i])) {
                     $this->answers[$i]->jumpto = $properties->jumpto[$i];
                 }
@@ -319,8 +334,9 @@ class lesson_page_type_matching extends lesson_page {
                     $DB->update_record("lesson_answers", $this->answers[$i]->properties());
                 }
 
-            } else {
-                break;
+            } else if (isset($this->answers[$i]->id)) {
+                $DB->delete_records('lesson_answers', array('id'=>$this->answers[$i]->id));
+                unset($this->answers[$i]);
             }
         }
         return true;
@@ -383,7 +399,11 @@ class lesson_page_type_matching extends lesson_page {
                 $data = "<select disabled=\"disabled\"><option selected=\"selected\">".strip_tags(format_string($answer->answer))."</option></select>";
                 if ($useranswer != NULL) {
                     $userresponse = explode(",", $useranswer->useranswer);
-                    $data .= "<select disabled=\"disabled\"><option selected=\"selected\">".strip_tags(format_string($answers[$userresponse[$i]]->response))."</option></select>";
+                    $data .= "<select disabled=\"disabled\"><option selected=\"selected\">";
+                    if (array_key_exists($i, $userresponse)) {
+                        $data .= strip_tags(format_string($answers[$userresponse[$i]]->response));
+                    }
+                    $data .= "</option></select>";
                 } else {
                     $data .= "<select disabled=\"disabled\"><option selected=\"selected\">".strip_tags(format_string($answer->response))."</option></select>";
                 }
@@ -413,13 +433,14 @@ class lesson_page_type_matching extends lesson_page {
     }
     public function get_jumps() {
         global $DB;
-        // The jumps for matching question type is stored
-        // in the 3rd and 4rth answer record.
+        // The jumps for matching question type are stored in the 1st and 2nd answer record.
         $jumps = array();
         if ($answers = $DB->get_records("lesson_answers", array("lessonid" => $this->lesson->id, "pageid" => $this->properties->id), 'id', '*', 0, 2)) {
             foreach ($answers as $answer) {
                 $jumps[] = $this->get_jump_name($answer->jumpto);
             }
+        } else {
+            $jumps[] = $this->get_jump_name($this->properties->nextpageid);
         }
         return $jumps;
     }
index 9829ad0..a9d912f 100644 (file)
@@ -57,7 +57,6 @@ class lesson_page_type_multichoice extends lesson_page {
     public function get_jumps() {
         global $DB;
         $jumps = array();
-        $params = array ("lessonid" => $this->lesson->id, "pageid" => $this->properties->id);
         if ($answers = $this->get_answers()) {
             foreach ($answers as $answer) {
                 if ($answer->answer === '') {
@@ -66,6 +65,10 @@ class lesson_page_type_multichoice extends lesson_page {
                 }
                 $jumps[] = $this->get_jump_name($answer->jumpto);
             }
+        } else {
+            // We get here is the lesson was created on a Moodle 1.9 site and
+            // the lesson contains question pages without any answers.
+            $jumps[] = $this->get_jump_name($this->properties->nextpageid);
         }
         return $jumps;
     }
index 23d7322..8fdcb1f 100644 (file)
@@ -437,7 +437,12 @@ if ($action === 'delete') {
         $page = $lessonpages[$pageid];
         $answerpage = new stdClass;
         $data ='';
+        
         $answerdata = new stdClass;
+        // Set some defaults for the answer data.
+        $answerdata->score = NULL;
+        $answerdata->response = NULL;
+        $answerdata->responseformat = FORMAT_PLAIN;
 
         $answerpage->title = format_string($page->title);
 
@@ -452,9 +457,7 @@ if ($action === 'delete') {
         if (empty($userid)) {
             // there is no userid, so set these vars and display stats.
             $answerpage->grayout = 0;
-            $useranswer = NULL;
-            $answerdata->score = NULL;
-            $answerdata->response = NULL;
+            $useranswer = NULL;    
         } elseif ($useranswers = $DB->get_records("lesson_attempts",array("lessonid"=>$lesson->id, "userid"=>$userid, "retry"=>$try,"pageid"=>$page->id), "timeseen")) {
             // get the user's answer for this page
             // need to find the right one
@@ -470,9 +473,6 @@ if ($action === 'delete') {
             // user did not answer this page, gray it out and set some nulls
             $answerpage->grayout = 1;
             $useranswer = NULL;
-            $answerdata->score = NULL;
-            $answerdata->response = NULL;
-
         }
         $i = 0;
         $n = 0;
index 6528763..ceba7c2 100644 (file)
@@ -27,7 +27,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$module->version  = 2010081200;  // The current module version (Date: YYYYMMDDXX)
+$module->version  = 2010121400;  // The current module version (Date: YYYYMMDDXX)
 $module->requires = 2010080300;  // Requires this Moodle version
 $module->cron     = 0;           // Period for cron to check this module (secs)
 
index 9889dc9..b0d93c8 100644 (file)
@@ -359,11 +359,17 @@ if ($pageid != LESSON_EOL) {
     } else {
         $data = new stdClass;
         $data->id = $PAGE->cm->id;
+        $data->pageid = $page->id;
+        $data->newpageid = LESSON_NEXTPAGE;
         if ($nextpage = $lesson->get_next_page($page->nextpageid)) {
             $data->newpageid = $nextpage->id;
         }
 
-        $mform = new lesson_page_without_answers();
+        $customdata = array(
+            'title'     => $page->title,
+            'contents'  => $page->get_contents()
+        );
+        $mform = new lesson_page_without_answers($CFG->wwwroot.'/mod/lesson/continue.php', $customdata);
         $mform->set_data($data);
         ob_start();
         $mform->display();
@@ -371,7 +377,7 @@ if ($pageid != LESSON_EOL) {
         ob_end_clean();
     }
 
-    lesson_add_pretend_blocks($PAGE, $cm, $lesson, $timer);
+    lesson_add_fake_blocks($PAGE, $cm, $lesson, $timer);
     echo $lessonoutput->header($lesson, $cm, $currenttab, $extraeditbuttons, $lessonpageid);
     if ($attemptflag) {
         echo $OUTPUT->heading(get_string('attempt', 'lesson', $retries));
@@ -553,7 +559,7 @@ if ($pageid != LESSON_EOL) {
     $url = new moodle_url('/grade/index.php', array('id'=>$course->id));
     $lessoncontent .= html_writer::link($url, get_string('viewgrades', 'lesson'), array('class'=>'centerpadded lessonbutton standardbutton'));
 
-    lesson_add_pretend_blocks($PAGE, $cm, $lesson, $timer);
+    lesson_add_fake_blocks($PAGE, $cm, $lesson, $timer);
     echo $lessonoutput->header($lesson, $cm, $currenttab, $extraeditbuttons, $lessonpageid);
     echo $lessoncontent;
     echo $lessonoutput->footer();
index df8b7e1..f7c108a 100644 (file)
@@ -41,15 +41,30 @@ require_once($CFG->libdir.'/formslib.php');
 class lesson_page_without_answers extends moodleform {
 
     public function definition() {
+        global $OUTPUT;
 
         $mform = $this->_form;
 
+        $title = $this->_customdata['title'];
+        $contents = $this->_customdata['contents'];
+
+        if (!empty($title)) {
+            $mform->addElement('header', 'pageheader', $title);
+        }
+
+        if (!empty($contents)) {
+            $mform->addElement('html', $OUTPUT->box($contents, 'contents'));
+        }
+
         $mform->addElement('hidden', 'id');
         $mform->setType('id', PARAM_INT);
 
         $mform->addElement('hidden', 'pageid');
         $mform->setType('pageid', PARAM_INT);
 
+        $mform->addElement('hidden', 'newpageid');
+        $mform->setType('newpageid', PARAM_INT);
+
         $this->add_action_buttons(null, get_string("continue", "lesson"));
 
     }
index a8c9595..252a03c 100644 (file)
@@ -97,7 +97,7 @@
     // Arrange for the navigation to be displayed.
     $navbc = $attemptobj->get_navigation_panel('quiz_attempt_nav_panel', $page);
     $firstregion = reset($PAGE->blocks->get_regions());
-    $PAGE->blocks->add_pretend_block($navbc, $firstregion);
+    $PAGE->blocks->add_fake_block($navbc, $firstregion);
 
     // Print the page header
     $title = get_string('attempt', 'quiz', $attemptobj->get_attempt_number());
index 7b727b9..e3714d0 100644 (file)
@@ -93,7 +93,7 @@
     // Arrange for the navigation to be displayed.
     $navbc = $attemptobj->get_navigation_panel('quiz_review_nav_panel', $page, $showall);
     $firstregion = reset($PAGE->blocks->get_regions());
-    $PAGE->blocks->add_pretend_block($navbc, $firstregion);
+    $PAGE->blocks->add_fake_block($navbc, $firstregion);
 
 /// Print the page header
     $headtags = $attemptobj->get_html_head_contributions($page);
index 68a4512..b8f6b83 100644 (file)
@@ -160,6 +160,7 @@ $string['searchresult'] = 'Search results:';
 $string['searchwikis'] = 'Search wikis';
 $string['special'] = 'Special';
 $string['tagsdeleted'] = 'Wiki tags have been deleted';
+$string['tagtitle'] = 'See the "{$a}" tag';
 $string['teacherrating'] = 'Teacher rating';
 $string['timesrating']='This page has been rated  {$a->c} times with an average of: {$a->s}';
 $string['updatedpages'] = "Updated pages";
index 22a2006..6f07795 100644 (file)
@@ -1194,7 +1194,15 @@ function wiki_print_page_content($page, $context, $subwikiid) {
 
     if (!empty($CFG->usetags)) {
         $tags = tag_get_tags_array('wiki_pages', $page->id);
-        echo '<p class="wiki-tags"><span>Tags: </span>' . join($tags, ", ") . '</p>';
+        echo $OUTPUT->container_start('wiki-tags');
+        echo '<span class="wiki-tags-title">'.get_string('tags').': </span>';
+        $links = array();
+        foreach ($tags as $tagid=>$tag) {
+            $url = new moodle_url('/tag/index.php', array('tag'=>$tag));
+            $links[] = html_writer::link($url, $tag, array('title'=>get_string('tagtitle', 'wiki', $tag)));
+        }
+        echo join($links, ", ");
+        echo $OUTPUT->container_end();
     }
 
     wiki_increment_pageviews($page);
index 0a5262e..2645d79 100644 (file)
@@ -1463,24 +1463,6 @@ class page_wiki_map extends page_wiki {
         $this->view = $option;
     }
 
-    function print_menu_map() {
-        $options = array('contributions', 'links', 'orphaned', 'pageindex', 'pagelist', 'updatedpages');
-        $items = array();
-        foreach ($options as $opt) {
-            $items[] = get_string($opt, 'wiki');
-        }
-        $table = new html_table();
-        $table->head = array(get_string('mapmenu', 'wiki'));
-        $table->attributes['class'] = 'wiki_editor generalbox';
-        $table->data = array();
-        $table->rowclasses = array();
-        foreach ($items as $key => $item) {
-            $link = new moodle_url('/mod/wiki/map.php', array('pageid' => $this->page->id, 'option' => $key + 1));
-            $table->data[] = array(html_writer::link($link, $item));
-        }
-        echo html_writer::table($table);
-    }
-
     function set_url() {
         global $PAGE, $CFG;
         $PAGE->set_url($CFG->wwwroot . '/mod/wiki/map.php', array('pageid' => $this->page->id));