Merge branch 'MDL-32005-master-10' of git://git.luns.net.uk/moodle
authorAparup Banerjee <aparup@moodle.com>
Wed, 16 May 2012 02:39:48 +0000 (10:39 +0800)
committerAparup Banerjee <aparup@moodle.com>
Wed, 16 May 2012 02:39:48 +0000 (10:39 +0800)
- fixed up comments (was phpdoc blocks)

99 files changed:
admin/roles/usersroles.php
admin/tool/innodb/index.php
admin/user/user_bulk_download.php
calendar/lib.php
config-dist.php
course/editcategory.php
course/format/renderer.php
course/format/topics/format.js
course/format/weeks/format.js
course/lib.php
course/rest.php
course/yui/toolboxes/toolboxes.js
grade/export/grade_export_form.php
grade/export/lib.php
grade/export/ods/export.php
grade/export/ods/grade_export_ods.php
grade/export/ods/index.php
grade/export/txt/export.php
grade/export/txt/grade_export_txt.php
grade/export/txt/index.php
grade/export/xls/export.php
grade/export/xls/grade_export_xls.php
grade/export/xls/index.php
grade/export/xml/export.php
grade/export/xml/grade_export_xml.php
grade/export/xml/index.php
grade/lib.php
grade/report/grader/lib.php
lang/en/admin.php
lang/en/grades.php
lang/en/moodle.php
lib/dml/mysqli_native_moodle_database.php
lib/editor/tinymce/lang/en/editor_tinymce.php
lib/editor/tinymce/lib.php
lib/editor/tinymce/settings.php
lib/editor/tinymce/version.php
lib/excel/test.php
lib/externallib.php
lib/filelib.php
lib/form/yui/dateselector/dateselector.js
lib/moodlelib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/phpunit/classes/advanced_testcase.php [new file with mode: 0644]
lib/phpunit/classes/arraydataset.php [new file with mode: 0644]
lib/phpunit/classes/basic_testcase.php [new file with mode: 0644]
lib/phpunit/classes/block_generator.php [new file with mode: 0644]
lib/phpunit/classes/data_generator.php [new file with mode: 0644]
lib/phpunit/classes/database_driver_testcase.php [new file with mode: 0644]
lib/phpunit/classes/hint_resultprinter.php [new file with mode: 0644]
lib/phpunit/classes/module_generator.php [new file with mode: 0644]
lib/phpunit/classes/unittestcase.php [new file with mode: 0644]
lib/phpunit/classes/util.php [new file with mode: 0644]
lib/phpunit/generatorlib.php
lib/phpunit/lib.php
lib/phpunit/tests/advanced_test.php [moved from lib/tests/phpunit_test.php with 63% similarity]
lib/phpunit/tests/basic_test.php [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset.csv [moved from lib/tests/fixtures/sample_dataset.csv with 100% similarity]
lib/phpunit/tests/fixtures/sample_dataset.xml [moved from lib/tests/fixtures/sample_dataset.xml with 100% similarity]
lib/phpunit/tests/generator_test.php [new file with mode: 0644]
lib/setuplib.php
lib/tests/pagelib_test.php
lib/tests/repositorylib_test.php
mod/assign/styles.css
mod/assignment/tests/generator_test.php
mod/chat/gui_basic/index.php
mod/chat/lib.php
mod/data/lib.php
mod/data/view.php
mod/feedback/edit_form.php
mod/feedback/export.php
mod/feedback/lib.php
mod/forum/lib.php
mod/lesson/lib.php
mod/lesson/locallib.php
mod/lti/lib.php
mod/quiz/lib.php
mod/survey/lib.php
mod/url/lib.php
mod/wiki/lib.php
mod/wiki/locallib.php
mod/workshop/form/rubric/lib.php
mod/workshop/form/rubric/tests/lib_test.php
mod/workshop/lib.php
phpunit.xml.dist
pix/t/editstring.png [new file with mode: 0644]
question/editlib.php
question/engine/simpletest/helpers.php [new file with mode: 0644]
question/engine/tests/helpers.php
question/engine/upgrade/tests/helper.php
question/type/match/tests/walkthrough_test.php
question/type/multianswer/tests/upgradelibnewqe_test.php
theme/base/style/core.css
theme/base/style/course.css
theme/formal_white/style/core.css
theme/formal_white/style/formal_white.css
webservice/rest/locallib.php
webservice/soap/locallib.php
webservice/xmlrpc/locallib.php

index 0c4b157..968f025 100644 (file)
@@ -54,6 +54,17 @@ if (!$canview) {
     print_error('nopermissions', 'error', '', get_string('checkpermissions', 'role'));
 }
 
+if ($userid != $USER->id) {
+    // If its not the current user we need to extend the navigation for that user to ensure
+    // their navigation is loaded and this page found upon it.
+    $PAGE->navigation->extend_for_user($user);
+}
+if ($course->id != $SITE->id || $userid != $USER->id) {
+    // If we're within a course OR if we're viewing another user then we need to include the
+    // settings base on the navigation to ensure that the navbar will contain the users name.
+    $PAGE->navbar->includesettingsbase = true;
+}
+
 /// Now get the role assignments for this user.
 $sql = "SELECT
         ra.id, ra.userid, ra.contextid, ra.roleid, ra.component, ra.itemid,
@@ -120,14 +131,9 @@ $title = get_string('xroleassignments', 'role', $fullname);
 $PAGE->set_title($title);
 if ($courseid != SITEID) {
     $PAGE->set_heading($fullname);
-    if (has_capability('moodle/course:viewparticipants', $coursecontext)) {
-        $PAGE->navbar->add(get_string('participants'),new moodle_url('/user/index.php', array('id'=>$courseid)));
-    }
 } else {
     $PAGE->set_heading($course->fullname);
 }
-$PAGE->navbar->add($fullname, new moodle_url("$CFG->wwwroot/user/view.php", array('id'=>$userid,'course'=>$courseid)));
-$PAGE->navbar->add($straction);
 echo $OUTPUT->header();
 echo $OUTPUT->heading($title, 3);
 echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthnormal');
index d629486..3e57e90 100644 (file)
@@ -42,29 +42,40 @@ if ($DB->get_dbfamily() != 'mysql') {
     notice('This function is for MySQL databases only!', new moodle_url('/admin/'));
 }
 
+$prefix = str_replace('_', '\\_', $DB->get_prefix()).'%';
+$sql = "SHOW TABLE STATUS WHERE Name LIKE ? AND Engine <> 'InnoDB'";
+$rs = $DB->get_recordset_sql($sql, array($prefix));
+if (!$rs->valid()) {
+    $rs->close();
+    echo $OUTPUT->box('<p>All tables are already using InnoDB database engine.</p>');
+    echo $OUTPUT->continue_button('/admin/');
+    echo $OUTPUT->footer();
+    die;
+}
+
 if (data_submitted() and $confirm and confirm_sesskey()) {
 
     echo $OUTPUT->notification('Please be patient and wait for this to complete...', 'notifysuccess');
 
     set_time_limit(0);
 
-    if ($tables = $DB->get_tables()) {
+    foreach ($rs as $table) {
         $DB->set_debug(true);
-        foreach ($tables as $table) {
-            $fulltable = $DB->get_prefix().$table;
-            try {
-                $DB->change_database_structure("ALTER TABLE $fulltable ENGINE=INNODB");
-            } catch (moodle_exception $e) {
-                echo $OUTPUT->notification(s($e->getMessage()).'<br />'.s($e->debuginfo));
-            }
+        $fulltable = $table->name;
+        try {
+            $DB->change_database_structure("ALTER TABLE $fulltable ENGINE=INNODB");
+        } catch (moodle_exception $e) {
+            echo $OUTPUT->notification(s($e->getMessage()).'<br />'.s($e->debuginfo));
         }
         $DB->set_debug(false);
     }
+    $rs->close();
     echo $OUTPUT->notification('... done.', 'notifysuccess');
     echo $OUTPUT->continue_button(new moodle_url('/admin/'));
     echo $OUTPUT->footer();
 
 } else {
+    $rs->close();
     $optionsyes = array('confirm'=>'1', 'sesskey'=>sesskey());
     $formcontinue = new single_button(new moodle_url('/admin/tool/innodb/index.php', $optionsyes), get_string('yes'));
     $formcancel = new single_button(new moodle_url('/admin/'), get_string('no'), 'get');
index 6b5881c..27fc9b9 100644 (file)
@@ -152,7 +152,7 @@ function user_download_csv($fields) {
     $filename = clean_filename(get_string('users').'.csv');
 
     header("Content-Type: application/download\n");
-    header("Content-Disposition: attachment; filename=$filename");
+    header("Content-Disposition: attachment; filename=\"$filename\"");
     header("Expires: 0");
     header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
     header("Pragma: public");
index 7d55605..16c5f28 100644 (file)
@@ -1049,28 +1049,6 @@ function calendar_get_link_href($linkbase, $d, $m, $y) {
     return $linkbase;
 }
 
-/**
- * This function has been deprecated as of Moodle 2.0... DO NOT USE!!!!!
- *
- * @deprecated Moodle 2.0 - MDL-24284 please do not use this function any more.
- * @todo MDL-31134 - will be removed in Moodle 2.3
- * @see calendar_get_link_href()
- *
- * @param string $text
- * @param string|moodle_url $linkbase
- * @param int|null $d The number of the day.
- * @param int|null $m The number of the month.
- * @param int|null $y The number of the year.
- * @return string HTML link
- */
-function calendar_get_link_tag($text, $linkbase, $d, $m, $y) {
-    $url = calendar_get_link_href(new moodle_url($linkbase), $d, $m, $y);
-    if (empty($url)) {
-        return $text;
-    }
-    return html_writer::link($url, $text);
-}
-
 /**
  * Build and return a previous month HTML link, with an arrow.
  *
@@ -1491,15 +1469,16 @@ function calendar_get_default_courses() {
     }
 
     $courses = array();
-    if (!empty($CFG->calendar_adminseesall) && has_capability('moodle/calendar:manageentries', get_context_instance(CONTEXT_SYSTEM))) {
+    if (!empty($CFG->calendar_adminseesall) && has_capability('moodle/calendar:manageentries', context_system::instance())) {
         list ($select, $join) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
-        $sql = "SELECT DISTINCT c.* $select
+        $sql = "SELECT c.* $select
                   FROM {course} c
-                  JOIN {event} e ON e.courseid = c.id
-                  $join";
+                  $join
+                  WHERE EXISTS (SELECT 1 FROM {event} e WHERE e.courseid = c.id)
+                  ";
         $courses = $DB->get_records_sql($sql, null, 0, 20);
         foreach ($courses as $course) {
-            context_instance_preload($course);
+            context_helper::preload_from_record($course);
         }
         return $courses;
     }
index 4b3534f..986d2ba 100644 (file)
@@ -38,7 +38,7 @@ $CFG = new stdClass();
 // will be stored.  This database must already have been created         //
 // and a username/password created to access it.                         //
 
-$CFG->dbtype    = 'pgsql';      // 'pgsql', 'mysqli', 'mssql' or 'oci'
+$CFG->dbtype    = 'pgsql';      // 'pgsql', 'mysqli', 'mssql', 'sqlsrv' or 'oci'
 $CFG->dblibrary = 'native';     // 'native' only at the moment
 $CFG->dbhost    = 'localhost';  // eg 'localhost' or 'db.isp.com' or IP
 $CFG->dbname    = 'moodle';     // database name, eg moodle
index 97f791a..0a9dc89 100644 (file)
@@ -128,18 +128,25 @@ if ($mform->is_cancelled()) {
     redirect('category.php?id='.$newcategory->id.'&categoryedit=on');
 }
 
-// Unfortunately the navigation never generates correctly for this page because technically
-// this page doesn't actually exist on the navigation you get here through the course
-// management page.
-try {
-    // First up we'll try to make the course management page active seeing as that is
-    // where the user thinks they are.
-    // The big prolem here is that the course management page is a common page
-    // for both editing users and common users.
-    $PAGE->settingsnav->get('root')->get('courses')->get('coursemgmt')->make_active();
-} catch (Exception $ex) {
+// Unfortunately the navigation never generates correctly for this page because technically this page doesn't actually
+// exist on the navigation; you get here through the course management page.
+// First up we'll try to make the course management page active seeing as that is where the user thinks they are.
+// The big prolem here is that the course management page is a common page for both editing users and common users and
+// is only added to the admin tree if the user has permission to edit at the system level.
+$node = $PAGE->settingsnav->get('root');
+if ($node) {
+    $node = $node->get('courses');
+    if ($node) {
+        $node = $node->get('coursemgmt');
+    }
+}
+if ($node) {
+    // The course management page exists so make that active.
+    $node->make_active();
+} else {
     // Failing that we'll override the URL, not as accurate and chances are things
     // won't be 100% correct all the time but should work most times.
+    // A common reason to arrive here is having the management capability within only a particular category (not at system level).
     navigation_node::override_active_url(new moodle_url('/course/index.php', array('categoryedit' => 'on')));
 }
 
index 61670a5..2464afd 100644 (file)
@@ -314,7 +314,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
      * @param stdClass $course The course entry from DB
      * @param array $sections The course_sections entries from the DB
      * @param int $sectionno The section number in the coruse which is being dsiplayed
-     * @return string HTML to output.
+     * @return array associative array with previous and next section link
      */
     protected function get_nav_links($course, $sections, $sectionno) {
         // FIXME: This is really evil and should by using the navigation API.
@@ -325,8 +325,9 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $back = $sectionno - 1;
         while ($back > 0 and empty($links['previous'])) {
             if ($canviewhidden || $sections[$back]->visible) {
-                $links['previous'] = html_writer::link(course_get_url($course, $back),
-                    $this->output->larrow().$this->output->spacer().get_section_name($course, $sections[$back]));
+                $previouslink = html_writer::tag('span', $this->output->larrow(), array('class' => 'larrow'));
+                $previouslink .= get_section_name($course, $sections[$back]);
+                $links['previous'] = html_writer::link(course_get_url($course, $back), $previouslink);
             }
             $back--;
         }
@@ -334,19 +335,14 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $forward = $sectionno + 1;
         while ($forward <= $course->numsections and empty($links['next'])) {
             if ($canviewhidden || $sections[$forward]->visible) {
-                $links['next'] = html_writer::link(course_get_url($course, $forward),
-                    get_section_name($course, $sections[$forward]).$this->output->spacer().$this->output->rarrow());
+                $nextlink = get_section_name($course, $sections[$forward]);
+                $nextlink .= html_writer::tag('span', $this->output->rarrow(), array('class' => 'rarrow'));
+                $links['next'] = html_writer::link(course_get_url($course, $forward), $nextlink);
             }
             $forward++;
         }
 
-        $o = '';
-        $o.= html_writer::start_tag('div', array('class' => 'section-navigation yui3-g'));
-        $o.= html_writer::tag('div', $links['previous'], array('class' => 'yui3-u'));
-        $o.= html_writer::tag('div', $links['next'], array('class' => 'right yui3-u'));
-        $o.= html_writer::end_tag('div');
-
-        return $o;
+        return $links;
     }
 
     /**
@@ -441,16 +437,19 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             echo $this->end_section_list();
         }
 
-        // Section next/previous links.
+        // Title with section navigation links.
         $sectionnavlinks = $this->get_nav_links($course, $sections, $displaysection);
-        echo $sectionnavlinks;
-
-
-        // Title with completion help icon.
+        $sectiontitle = '';
+        $sectiontitle .= html_writer::start_tag('div', array('class' => 'section-navigation headingblock header'));
+        $sectiontitle .= html_writer::tag('span', $sectionnavlinks['previous'], array('class' => 'mdl-left'));
+        $sectiontitle .= html_writer::tag('span', $sectionnavlinks['next'], array('class' => 'mdl-right'));
+        $sectiontitle .= html_writer::tag('div', get_section_name($course, $sections[$displaysection]), array('class' => 'mdl-align'));
+        $sectiontitle .= html_writer::end_tag('div');
+        echo $sectiontitle;
+
+        // Show completion help icon.
         $completioninfo = new completion_info($course);
         echo $completioninfo->display_help_icon();
-        $title = get_section_name($course, $sections[$displaysection]);
-        echo $this->output->heading($title, 2, 'headingblock header outline');
 
         // Copy activity clipboard..
         echo $this->course_activity_clipboard($course, $displaysection);
@@ -466,8 +465,17 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             print_section_add_menus($course, $displaysection, $modnames);
         }
         echo $this->section_footer();
-        echo $sectionnavlinks;
         echo $this->end_section_list();
+
+        // Display section bottom navigation.
+        $courselink = html_writer::link(course_get_url($course), get_string('returntomaincoursepage'));
+        $sectionbottomnav = '';
+        $sectionbottomnav .= html_writer::start_tag('div', array('class' => 'section-navigation mdl-bottom'));
+        $sectionbottomnav .= html_writer::tag('span', $sectionnavlinks['previous'], array('class' => 'mdl-left'));
+        $sectionbottomnav .= html_writer::tag('span', $sectionnavlinks['next'], array('class' => 'mdl-right'));
+        $sectionbottomnav .= html_writer::tag('div', $courselink, array('class' => 'mdl-align'));
+        $sectionbottomnav .= html_writer::end_tag('div');
+        echo $sectionbottomnav;
     }
 
     /**
index 26f2801..85aec6d 100644 (file)
@@ -26,15 +26,12 @@ M.course.format.swap_sections = function(Y, node1, node2) {
     var CSS = {
         COURSECONTENT : 'course-content',
         LEFT : 'left',
-        RIGHT : 'right',
-        SECTIONADDMENUS : 'section_add_menus',
+        SECTIONADDMENUS : 'section_add_menus'
     };
 
     var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.course.format.get_section_selector(Y));
     // Swap left block
     sectionlist.item(node1).one('.'+CSS.LEFT).swap(sectionlist.item(node2).one('.'+CSS.LEFT));
-    // Swap right block
-    sectionlist.item(node1).one('.'+CSS.RIGHT).swap(sectionlist.item(node2).one('.'+CSS.RIGHT));
     // Swap menus
     sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
 }
index d1b5430..78c2063 100644 (file)
@@ -26,7 +26,6 @@ M.course.format.swap_sections = function(Y, node1, node2) {
     var CSS = {
         COURSECONTENT : 'course-content',
         LEFT : 'left',
-        RIGHT : 'right',
         SECTIONADDMENUS : 'section_add_menus',
         WEEKDATES: 'weekdates'
     };
@@ -34,8 +33,6 @@ M.course.format.swap_sections = function(Y, node1, node2) {
     var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.course.format.get_section_selector(Y));
     // Swap left block
     sectionlist.item(node1).one('.'+CSS.LEFT).swap(sectionlist.item(node2).one('.'+CSS.LEFT));
-    // Swap right block
-    sectionlist.item(node1).one('.'+CSS.RIGHT).swap(sectionlist.item(node2).one('.'+CSS.RIGHT));
     // Swap menus
     sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
     // Swap week dates
index 262582f..c412e0b 100644 (file)
@@ -566,7 +566,7 @@ function print_log_csv($course, $user, $date, $order='l.time DESC', $modname,
     $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
     $filename .= '.txt';
     header("Content-Type: application/download\n");
-    header("Content-Disposition: attachment; filename=$filename");
+    header("Content-Disposition: attachment; filename=\"$filename\"");
     header("Expires: 0");
     header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
     header("Pragma: public");
@@ -3026,11 +3026,11 @@ function move_section_to($course, $section, $destination) {
     // If we move the highlighted section itself, then just highlight the destination.
     // Adjust the higlighted section location if we move something over it either direction.
     if ($section == $course->marker) {
-        course_set_marker($course, $destination);
+        course_set_marker($course->id, $destination);
     } elseif ($moveup && $section > $course->marker && $course->marker >= $destination) {
-        course_set_marker($course, $course->marker+1);
+        course_set_marker($course->id, $course->marker+1);
     } elseif (!$moveup && $section < $course->marker && $course->marker <= $destination) {
-        course_set_marker($course, $course->marker-1);
+        course_set_marker($course->id, $course->marker-1);
     }
 
     $transaction->allow_commit();
@@ -3155,7 +3155,7 @@ function moveto_module($mod, $section, $beforemod=NULL) {
  * @return string XHTML for the editing buttons
  */
 function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $moveselect = true, $indent=-1, $section=-1) {
-    global $CFG, $OUTPUT;
+    global $CFG, $OUTPUT, $COURSE;
 
     static $str;
 
@@ -3191,6 +3191,7 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
         $str->forcedgroupsnone     = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsnone"));
         $str->forcedgroupsseparate = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsseparate"));
         $str->forcedgroupsvisible  = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsvisible"));
+        $str->edittitle = get_string('edittitle', 'moodle');
     }
 
     $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
@@ -3200,6 +3201,16 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
     }
     $actions = array();
 
+    // AJAX edit title
+    if ($mod->modname !== 'label' && $hasmanageactivities && course_ajax_enabled($COURSE)) {
+        $actions[] = new action_link(
+            new moodle_url($baseurl, array('update' => $mod->id)),
+            new pix_icon('t/editstring', $str->edittitle, 'moodle', array('class' => 'iconsmall visibleifjs')),
+            null,
+            array('class' => 'editing_title', 'title' => $str->edittitle)
+        );
+    }
+
     // leftright
     if ($hasmanageactivities) {
         if (right_to_left()) {   // Exchange arrows on RTL
@@ -4396,6 +4407,43 @@ function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
     }
 }
 
+/**
+ * Determine whether course ajax should be enabled for the specified course
+ *
+ * @param stdClass $course The course to test against
+ * @return boolean Whether course ajax is enabled or note
+ */
+function course_ajax_enabled($course) {
+    global $CFG, $PAGE, $SITE;
+
+    // Ajax must be enabled globally
+    if (!$CFG->enableajax) {
+        return false;
+    }
+
+    // The user must be editing for AJAX to be included
+    if (!$PAGE->user_is_editing()) {
+        return false;
+    }
+
+    // Check that the theme suports
+    if (!$PAGE->theme->enablecourseajax) {
+        return false;
+    }
+
+    // Check that the course format supports ajax functionality
+    // The site 'format' doesn't have information on course format support
+    if ($SITE->id !== $course->id) {
+        $courseformatajaxsupport = course_format_ajax_support($course->format);
+        if (!$courseformatajaxsupport->capable) {
+            return false;
+        }
+    }
+
+    // All conditions have been met so course ajax should be enabled
+    return true;
+}
+
 /**
  * Include the relevant javascript and language strings for the resource
  * toolbox YUI module
@@ -4403,23 +4451,18 @@ function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
  * @param integer $id The ID of the course being applied to
  * @param array $modules An array containing the names of the modules in
  *                       use on the page
- * @param object $config An object containing configuration parameters for ajax modules including:
+ * @param stdClass $config An object containing configuration parameters for ajax modules including:
  *          * resourceurl   The URL to post changes to for resource changes
  *          * sectionurl    The URL to post changes to for section changes
  *          * pageparams    Additional parameters to pass through in the post
  * @return void
  */
 function include_course_ajax($course, $modules = array(), $config = null) {
-    global $PAGE, $CFG, $USER;
+    global $PAGE, $SITE;
 
     // Ensure that ajax should be included
-    $courseformatajaxsupport = course_format_ajax_support($course->format);
-    if (!$PAGE->theme->enablecourseajax
-        || !$CFG->enableajax
-        || empty($USER->editing)
-        || !$PAGE->user_is_editing()
-        || ($course->id != SITEID && !$courseformatajaxsupport->capable)) {
-        return;
+    if (!course_ajax_enabled($course)) {
+        return false;
     }
 
     if (!$config) {
@@ -4461,7 +4504,7 @@ function include_course_ajax($course, $modules = array(), $config = null) {
     );
 
     // Include course dragdrop
-    if ($course->id != SITEID) {
+    if ($course->id != $SITE->id) {
         $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop',
             array(array(
                 'courseid' => $course->id,
@@ -4491,6 +4534,8 @@ function include_course_ajax($course, $modules = array(), $config = null) {
             'moveleft',
             'deletechecktype',
             'deletechecktypename',
+            'edittitle',
+            'edittitleinstructions',
             'show',
             'hide',
             'groupsnone',
@@ -4504,7 +4549,7 @@ function include_course_ajax($course, $modules = array(), $config = null) {
         ), 'moodle');
 
     // Include format-specific strings
-    if ($course->id != SITEID) {
+    if ($course->id != $SITE->id) {
         $PAGE->requires->strings_for_js(array(
                 'showfromothers',
                 'hidefromothers',
@@ -4515,6 +4560,11 @@ function include_course_ajax($course, $modules = array(), $config = null) {
     foreach ($modules as $module => $modname) {
         $PAGE->requires->string_for_js('pluginname', $module);
     }
+
+    // Prevent caching of this page to stop confusion when changing page after making AJAX changes
+    $PAGE->set_cacheable(false);
+
+    return true;
 }
 
 /**
index 8ec14e6..5152563 100644 (file)
@@ -43,6 +43,7 @@ $summary    = optional_param('summary', '', PARAM_RAW);
 $sequence   = optional_param('sequence', '', PARAM_SEQUENCE);
 $visible    = optional_param('visible', 0, PARAM_INT);
 $pageaction = optional_param('action', '', PARAM_ALPHA); // Used to simulate a DELETE command
+$title      = optional_param('title', '', PARAM_TEXT);
 
 $PAGE->set_url('/course/rest.php', array('courseId'=>$courseid,'class'=>$class));
 
@@ -56,8 +57,8 @@ if (in_array($class, array('resource'))) {
     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
 } else {
     require_login($course);
-    $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
 }
+$coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
 require_sesskey();
 
 echo $OUTPUT->header(); // send headers
@@ -128,6 +129,39 @@ switch($requestmethod) {
 
                         moveto_module($cm, $section, $beforemod);
                         break;
+                    case 'gettitle':
+                        require_capability('moodle/course:manageactivities', $modcontext);
+                        $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
+                        $module = new stdClass();
+                        $module->id = $cm->instance;
+
+                        // Don't pass edit strings through multilang filters - we need the entire string
+                        echo json_encode(array('instancename' => $cm->name));
+                        break;
+                    case 'updatetitle':
+                        require_capability('moodle/course:manageactivities', $modcontext);
+                        $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
+                        $module = new stdClass();
+                        $module->id = $cm->instance;
+
+                        // Escape strings as they would be by mform
+                        if (!empty($CFG->formatstringstriptags)) {
+                            $module->name = clean_param($title, PARAM_TEXT);
+                        } else {
+                            $module->name = clean_param($title, PARAM_CLEANHTML);
+                        }
+
+                        if (!empty($module->name)) {
+                            $DB->update_record($cm->modname, $module);
+                        } else {
+                            $module->name = $cm->name;
+                        }
+
+                        // We need to return strings after they've been through filters for multilang
+                        $stringoptions = new stdClass;
+                        $stringoptions->context = $coursecontext;
+                        echo json_encode(array('instancename' => format_string($module->name, true,  $stringoptions)));
+                        break;
                 }
                 rebuild_course_cache($course->id);
                 break;
index 562307a..0c21648 100644 (file)
@@ -9,6 +9,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
         DELETE : 'a.editing_delete',
         DIMCLASS : 'dimmed',
         DIMMEDTEXT : 'dimmed_text',
+        EDITTITLE : 'a.editing_title',
         EDITTITLECLASS : 'edittitle',
         GENERICICONCLASS : 'iconsmall',
         GROUPSNONE : 'a.editing_groupsnone',
@@ -257,6 +258,9 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             Y.all(baseselector).each(this._setup_for_resource, this);
         },
         _setup_for_resource : function(toolboxtarget) {
+            // Edit Title
+            this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.EDITTITLE, this.edit_resource_title);
+
             // Move left and right
             this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.MOVELEFT, this.move_left);
             this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.MOVERIGHT, this.move_right);
@@ -482,6 +486,114 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             anchor.appendChild(newicon);
             anchor.on('click', this.move_left, this);
             moveright.insert(anchor, 'before');
+        },
+        /**
+         * Edit the title for the resource
+         */
+        edit_resource_title : function(e) {
+            // Get the element we're working on
+            var element = e.target.ancestor(CSS.ACTIVITYLI);
+            var instancename  = element.one(CSS.INSTANCENAME);
+            var currenttitle = instancename.get('firstChild');
+            var oldtitle = currenttitle.get('data');
+            var titletext = oldtitle;
+            var editbutton = element.one('a.' + CSS.EDITTITLECLASS + ' img');
+
+            // Disable the current href to prevent redirections when editing
+            var anchor = instancename.ancestor('a');
+            anchor.setAttribute('oldhref', anchor.getAttribute('href'));
+            anchor.removeAttribute('href');
+
+            var data = {
+                'class'   : 'resource',
+                'field'   : 'gettitle',
+                'id'      : this.get_element_id(element)
+            };
+
+            // Try to retrieve the existing string from the server
+            var response = this.send_request(data, editbutton);
+            if (response.instancename) {
+                titletext = response.instancename;
+            }
+
+            // Create the editor and submit button
+            var editor = Y.Node.create('<input />')
+                .setAttrs({
+                    'name'  : 'title',
+                    'value' : titletext,
+                    'autocomplete' : 'off'
+                })
+                .addClass('titleeditor');
+            var editform = Y.Node.create('<form />')
+                .setStyle('padding', '0')
+                .setStyle('display', 'inline')
+                .setAttribute('action', '#');
+
+            var editinstructions = Y.Node.create('<span />')
+                .addClass('editinstructions')
+                .set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
+
+            // Clear the existing content and put the editor in
+            currenttitle.set('data', '');
+            editform.appendChild(editor);
+            instancename.appendChild(editform);
+            element.appendChild(editinstructions);
+            e.preventDefault();
+
+            // Focus and select the editor text
+            editor.focus().select();
+
+            // Handle cancellation of the editor
+            editor.on('blur', function(e) {
+                // Detach the blur event before removing as some actions trigger multiple blurs in
+                // some browser
+                editor.detach('blur');
+                editform.remove();
+                editinstructions.remove();
+
+                // Set the title and anchor back to their previous settings
+                currenttitle.set('data', oldtitle);
+                anchor.setAttribute('href', anchor.getAttribute('oldhref'));
+                anchor.removeAttribute('oldhref');
+            });
+
+            // Handle form submission
+            editform.on('submit', function(e) {
+                // We don't actually want to submit anything
+                e.preventDefault();
+
+                // Detach the handlers to prevent multiple submissions
+                editform.detach('submit');
+                editor.detach('blur');
+
+                // We only accept strings which have valid content
+                var newtitle = Y.Lang.trim(editor.get('value'));
+                if (newtitle != null && newtitle != "" && newtitle != titletext) {
+                    var data = {
+                        'class'   : 'resource',
+                        'field'   : 'updatetitle',
+                        'title'   : newtitle,
+                        'id'      : this.get_element_id(element)
+                    };
+                    var response = this.send_request(data, editbutton);
+                    if (response.instancename) {
+                        currenttitle.set('data', response.instancename);
+                    }
+                } else {
+                    // Invalid content. Set the title back to it's original contents
+                    currenttitle.set('data', oldtitle);
+                }
+
+                editform.remove();
+                editinstructions.remove();
+
+                // We need a timeout here otherwise hitting return to save in some browsers triggers
+                // the anchor
+                setTimeout(function(e) {
+                    anchor.setAttribute('href', anchor.getAttribute('oldhref'));
+                    anchor.removeAttribute('oldhref');
+                }, 500);
+            }, this);
         }
     }, {
         NAME : 'course-resource-toolbox',
index a5c02c6..daccfa8 100644 (file)
@@ -37,6 +37,10 @@ class grade_export_form extends moodleform {
         $mform->addElement('advcheckbox', 'export_feedback', get_string('exportfeedback', 'grades'));
         $mform->setDefault('export_feedback', 0);
 
+        $mform->addElement('advcheckbox', 'export_onlyactive', get_string('exportonlyactive', 'grades'));
+        $mform->setDefault('export_onlyactive', 0);
+        $mform->addHelpButton('export_onlyactive', 'exportonlyactive', 'grades');
+
         $options = array('10'=>10, '20'=>20, '100'=>100, '1000'=>1000, '100000'=>100000);
         $mform->addElement('select', 'previewrows', get_string('previewrows', 'grades'), $options);
 
index b71a7b3..1530d2c 100644 (file)
@@ -39,6 +39,7 @@ abstract class grade_export {
     public $updatedgradesonly; // only export updated grades
     public $displaytype; // display type (e.g. real, percentages, letter) for exports
     public $decimalpoints; // number of decimal points for exports
+    public $onlyactive; // only include users with an active enrolment
     /**
      * Constructor should set up all the private variables ready to be pulled
      * @access public
@@ -49,7 +50,7 @@ abstract class grade_export {
      * @param boolean $export_letters
      * @note Exporting as letters will lead to data loss if that exported set it re-imported.
      */
-    public function grade_export($course, $groupid=0, $itemlist='', $export_feedback=false, $updatedgradesonly = false, $displaytype = GRADE_DISPLAY_TYPE_REAL, $decimalpoints = 2) {
+    public function grade_export($course, $groupid=0, $itemlist='', $export_feedback=false, $updatedgradesonly = false, $displaytype = GRADE_DISPLAY_TYPE_REAL, $decimalpoints = 2, $onlyactive = false) {
         $this->course = $course;
         $this->groupid = $groupid;
         $this->grade_items = grade_item::fetch_all(array('courseid'=>$this->course->id));
@@ -83,6 +84,7 @@ abstract class grade_export {
 
         $this->displaytype = $displaytype;
         $this->decimalpoints = $decimalpoints;
+        $this->onlyactive = $onlyactive;
     }
 
     /**
@@ -125,6 +127,10 @@ abstract class grade_export {
             $this->export_feedback = $formdata->export_feedback;
         }
 
+        if (isset($formdata->export_onlyactive)) {
+            $this->onlyactive = $formdata->export_onlyactive;
+        }
+
         if (isset($formdata->previewrows)) {
             $this->previewrows = $formdata->previewrows;
         }
@@ -222,6 +228,7 @@ abstract class grade_export {
 
         $i = 0;
         $gui = new graded_users_iterator($this->course, $this->columns, $this->groupid);
+        $gui->require_active_enrolment($this->onlyactive);
         $gui->init();
         while ($userdata = $gui->next_user()) {
             // number of preview rows
@@ -290,7 +297,8 @@ abstract class grade_export {
                         'export_feedback'   =>$this->export_feedback,
                         'updatedgradesonly' =>$this->updatedgradesonly,
                         'displaytype'       =>$this->displaytype,
-                        'decimalpoints'     =>$this->decimalpoints);
+                        'decimalpoints'     =>$this->decimalpoints,
+                        'export_onlyactive' =>$this->onlyactive);
 
         return $params;
     }
index f81f1d6..04a00e8 100644 (file)
@@ -26,6 +26,7 @@ $export_feedback   = optional_param('export_feedback', 0, PARAM_BOOL);
 $updatedgradesonly = optional_param('updatedgradesonly', false, PARAM_BOOL);
 $displaytype       = optional_param('displaytype', $CFG->grade_export_displaytype, PARAM_INT);
 $decimalpoints     = optional_param('decimalpoints', $CFG->grade_export_decimalpoints, PARAM_INT);
+$onlyactive        = optional_param('export_onlyactive', 0, PARAM_BOOL);
 
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     print_error('nocourseid');
@@ -44,7 +45,7 @@ if (groups_get_course_groupmode($COURSE) == SEPARATEGROUPS and !has_capability('
 }
 
 // print all the exported data here
-$export = new grade_export_ods($course, $groupid, $itemids, $export_feedback, $updatedgradesonly, $displaytype, $decimalpoints);
+$export = new grade_export_ods($course, $groupid, $itemids, $export_feedback, $updatedgradesonly, $displaytype, $decimalpoints, $onlyactive);
 $export->print_grades();
 
 
index e9ff095..e366131 100644 (file)
@@ -64,6 +64,7 @@ class grade_export_ods extends grade_export {
         $i = 0;
         $geub = new grade_export_update_buffer();
         $gui = new graded_users_iterator($this->course, $this->columns, $this->groupid);
+        $gui->require_active_enrolment($this->onlyactive);
         $gui->init();
         while ($userdata = $gui->next_user()) {
             $i++;
index f0d4b3c..78cd116 100644 (file)
@@ -51,7 +51,7 @@ if ($groupmode == SEPARATEGROUPS and !$currentgroup and !has_capability('moodle/
 
 // process post information
 if ($data = $mform->get_data()) {
-    $export = new grade_export_ods($course, $currentgroup, '', false, false, $data->display, $data->decimals);
+    $export = new grade_export_ods($course, $currentgroup, '', false, false, $data->display, $data->decimals, $data->export_onlyactive);
 
     // print the grades on screen for feedbacks
     $export->process_form($data);
index ab7a03b..e5e572c 100644 (file)
@@ -27,6 +27,7 @@ $separator         = optional_param('separator', 'comma', PARAM_ALPHA);
 $updatedgradesonly = optional_param('updatedgradesonly', false, PARAM_BOOL);
 $displaytype       = optional_param('displaytype', $CFG->grade_export_displaytype, PARAM_INT);
 $decimalpoints     = optional_param('decimalpoints', $CFG->grade_export_decimalpoints, PARAM_INT);
+$onlyactive        = optional_param('export_onlyactive', 0, PARAM_BOOL);
 
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     print_error('nocourseid');
@@ -45,7 +46,7 @@ if (groups_get_course_groupmode($COURSE) == SEPARATEGROUPS and !has_capability('
 }
 
 // print all the exported data here
-$export = new grade_export_txt($course, $groupid, $itemids, $export_feedback, $updatedgradesonly, $displaytype, $decimalpoints, $separator);
+$export = new grade_export_txt($course, $groupid, $itemids, $export_feedback, $updatedgradesonly, $displaytype, $decimalpoints, $separator, $onlyactive);
 $export->print_grades();
 
 
index 77f804b..0be9589 100644 (file)
@@ -23,13 +23,8 @@ class grade_export_txt extends grade_export {
 
     public $separator; // default separator
 
-    public function grade_export_txt($course, $groupid=0, $itemlist='', $export_feedback=false, $updatedgradesonly = false, $displaytype = GRADE_DISPLAY_TYPE_REAL, $decimalpoints = 2, $separator='comma') {
-        $this->grade_export($course, $groupid, $itemlist, $export_feedback, $updatedgradesonly, $displaytype, $decimalpoints);
-        $this->separator = $separator;
-    }
-
-    public function __construct($course, $groupid=0, $itemlist='', $export_feedback=false, $updatedgradesonly = false, $displaytype = GRADE_DISPLAY_TYPE_REAL, $decimalpoints = 2, $separator='comma') {
-        parent::__construct($course, $groupid, $itemlist, $export_feedback, $updatedgradesonly, $displaytype, $decimalpoints);
+    public function __construct($course, $groupid=0, $itemlist='', $export_feedback=false, $updatedgradesonly = false, $displaytype = GRADE_DISPLAY_TYPE_REAL, $decimalpoints = 2, $separator = 'comma', $onlyactive = false) {
+        parent::__construct($course, $groupid, $itemlist, $export_feedback, $updatedgradesonly, $displaytype, $decimalpoints, $onlyactive);
         $this->separator = $separator;
     }
 
@@ -91,6 +86,7 @@ class grade_export_txt extends grade_export {
 /// Print all the lines of data.
         $geub = new grade_export_update_buffer();
         $gui = new graded_users_iterator($this->course, $this->columns, $this->groupid);
+        $gui->require_active_enrolment($this->onlyactive);
         $gui->init();
         while ($userdata = $gui->next_user()) {
 
index 47de3e7..7f5f842 100644 (file)
@@ -51,7 +51,7 @@ if ($groupmode == SEPARATEGROUPS and !$currentgroup and !has_capability('moodle/
 
 // process post information
 if ($data = $mform->get_data()) {
-    $export = new grade_export_txt($course, $currentgroup, '', false, false, $data->display, $data->decimals, $data->separator);
+    $export = new grade_export_txt($course, $currentgroup, '', false, false, $data->display, $data->decimals, $data->separator, $data->export_onlyactive);
 
     // print the grades on screen for feedback
 
index 4df1fec..8526f3c 100644 (file)
@@ -26,6 +26,7 @@ $export_feedback   = optional_param('export_feedback', 0, PARAM_BOOL);
 $updatedgradesonly = optional_param('updatedgradesonly', false, PARAM_BOOL);
 $displaytype       = optional_param('displaytype', $CFG->grade_export_displaytype, PARAM_INT);
 $decimalpoints     = optional_param('decimalpoints', $CFG->grade_export_decimalpoints, PARAM_INT);
+$onlyactive        = optional_param('export_onlyactive', 0, PARAM_BOOL);
 
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     print_error('nocourseid');
@@ -44,7 +45,7 @@ if (groups_get_course_groupmode($COURSE) == SEPARATEGROUPS and !has_capability('
 }
 
 // print all the exported data here
-$export = new grade_export_xls($course, $groupid, $itemids, $export_feedback, $updatedgradesonly, $displaytype, $decimalpoints);
+$export = new grade_export_xls($course, $groupid, $itemids, $export_feedback, $updatedgradesonly, $displaytype, $decimalpoints, $onlyactive);
 $export->print_grades();
 
 
index f1efc6e..a89c910 100644 (file)
@@ -63,6 +63,7 @@ class grade_export_xls extends grade_export {
         $i = 0;
         $geub = new grade_export_update_buffer();
         $gui = new graded_users_iterator($this->course, $this->columns, $this->groupid);
+        $gui->require_active_enrolment($this->onlyactive);
         $gui->init();
         while ($userdata = $gui->next_user()) {
             $i++;
index fc509bc..68d7f67 100644 (file)
@@ -51,7 +51,7 @@ if ($groupmode == SEPARATEGROUPS and !$currentgroup and !has_capability('moodle/
 
 // process post information
 if ($data = $mform->get_data()) {
-    $export = new grade_export_xls($course, $currentgroup, '', false, false, $data->display, $data->decimals);
+    $export = new grade_export_xls($course, $currentgroup, '', false, false, $data->display, $data->decimals, $data->export_onlyactive);
 
     // print the grades on screen for feedbacks
     $export->process_form($data);
index 7807993..1424fe8 100644 (file)
@@ -26,6 +26,7 @@ $export_feedback   = optional_param('export_feedback', 0, PARAM_BOOL);
 $updatedgradesonly = optional_param('updatedgradesonly', false, PARAM_BOOL);
 $displaytype       = optional_param('displaytype', $CFG->grade_export_displaytype, PARAM_INT);
 $decimalpoints     = optional_param('decimalpoints', $CFG->grade_export_decimalpoints, PARAM_INT);
+$onlyactive        = optional_param('export_onlyactive', 0, PARAM_BOOL);
 
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     print_error('nocourseid');
@@ -44,7 +45,7 @@ if (groups_get_course_groupmode($COURSE) == SEPARATEGROUPS and !has_capability('
 }
 
 // print all the exported data here
-$export = new grade_export_xml($course, $groupid, $itemids, $export_feedback, $updatedgradesonly, $displaytype, $decimalpoints);
+$export = new grade_export_xml($course, $groupid, $itemids, $export_feedback, $updatedgradesonly, $displaytype, $decimalpoints, $onlyactive);
 $export->print_grades();
 
 
index b349b89..396a044 100644 (file)
@@ -54,6 +54,7 @@ class grade_export_xml extends grade_export {
 
         $geub = new grade_export_update_buffer();
         $gui = new graded_users_iterator($this->course, $this->columns, $this->groupid);
+        $gui->require_active_enrolment($this->onlyactive);
         $gui->init();
         while ($userdata = $gui->next_user()) {
             $user = $userdata->user;
index bc4d925..3903846 100644 (file)
@@ -52,7 +52,7 @@ if ($groupmode == SEPARATEGROUPS and !$currentgroup and !has_capability('moodle/
 
 // process post information
 if ($data = $mform->get_data()) {
-    $export = new grade_export_xml($course, $currentgroup, '', false, $data->updatedgradesonly, $data->display, $data->decimals);
+    $export = new grade_export_xml($course, $currentgroup, '', false, $data->updatedgradesonly, $data->display, $data->decimals, $data->export_onlyactive);
 
     // print the grades on screen for feedbacks
     $export->process_form($data);
index 8c969f2..185fed5 100644 (file)
@@ -43,6 +43,11 @@ class graded_users_iterator {
     public $sortfield2;
     public $sortorder2;
 
+    /**
+     * Should users whose enrolment has been suspended be ignored?
+     */
+    protected $onlyactive = false;
+
     /**
      * Constructor
      *
@@ -89,9 +94,7 @@ class graded_users_iterator {
 
         list($gradebookroles_sql, $params) =
             $DB->get_in_or_equal(explode(',', $CFG->gradebookroles), SQL_PARAMS_NAMED, 'grbr');
-
-        //limit to users with an active enrolment
-        list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext);
+        list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext, '', 0, $this->onlyactive);
 
         $params = array_merge($params, $enrolledparams);
 
@@ -215,6 +218,9 @@ class graded_users_iterator {
 
         if (!empty($this->grade_items)) {
             foreach ($this->grade_items as $grade_item) {
+                if (!isset($feedbacks[$grade_item->id])) {
+                    $feedbacks[$grade_item->id] = new stdClass();
+                }
                 if (array_key_exists($grade_item->id, $grade_records)) {
                     $feedbacks[$grade_item->id]->feedback       = $grade_records[$grade_item->id]->feedback;
                     $feedbacks[$grade_item->id]->feedbackformat = $grade_records[$grade_item->id]->feedbackformat;
@@ -253,6 +259,18 @@ class graded_users_iterator {
         $this->gradestack = array();
     }
 
+    /**
+     * Should all enrolled users be exported or just those with an active enrolment?
+     *
+     * @param bool $onlyactive True to limit the export to users with an active enrolment
+     */
+    public function require_active_enrolment($onlyactive = true) {
+        if (!empty($this->users_rs)) {
+            debugging('Calling require_active_enrolment() has no effect unless you call init() again', DEBUG_DEVELOPER);
+        }
+        $this->onlyactive  = $onlyactive;
+    }
+
 
     /**
      * _push
index 9d03a57..83d3d6c 100644 (file)
@@ -391,8 +391,7 @@ class grade_report_grader extends grade_report {
         list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->context);
 
         //fields we need from the user table
-        $userfields = user_picture::fields('u');
-        $userfields .= get_extra_user_fields_sql($this->context);
+        $userfields = user_picture::fields('u', get_extra_user_fields($this->context));
 
         $sortjoin = $sort = $params = null;
 
index c622bcc..ec60ddb 100644 (file)
@@ -127,7 +127,7 @@ $string['configallowobjectembed'] = 'As a default security measure, normal users
 $string['configallowoverride'] = 'You can allow people with the roles on the left side to override some of the column roles';
 $string['configallowoverride2'] = 'Select which role(s) can be overridden by each role in the left column.<br />Note that these settings only apply to users who have either the capability moodle/role:override or the capability moodle/role:safeoverride allowed.';
 $string['configallowswitch'] = 'Select which roles a user may switch to, based on which roles they already have. In addition to an entry in this table, a user must also have the moodle/role:switchroles capability to be able to switch.<br />Note that it is only possible to switch to roles that have the moodle/course:view capability, and that do not have the moodle/site:doanything capability, so some columns in this table are disabled.';
-$string['configallowthemechangeonurl'] = 'If enabled, the theme can be changed by adding theme={themename} to any Moodle URL.';
+$string['configallowthemechangeonurl'] = 'If enabled, the theme can be changed by adding either:<br />?theme=themename to any Moodle URL (eg: mymoodlesite.com/?theme=afterburner ) or <br />&theme=themename to any internal Moodle URL (eg: mymoodlesite.com/course/view.php?id=2&theme=afterburner ).';
 $string['configallowuserblockhiding'] = 'Do you want to allow users to hide/show side blocks throughout this site?  This feature uses Javascript and cookies to remember the state of each collapsible block, and only affects the user\'s own view.';
 $string['configallowuserswitchrolestheycantassign'] = 'By default, moodle/role:assign is required for users to switch roles. Enabling this setting removes this requirement, and results in the roles available in the "Switch role to" dropdown menu being determined by settings in the "Allow role assignments" table only.
 It is recommended that the settings in the "Allow role assignments" table do not allow users to switch to a role with more capabilities than their existing role.';
index f79ae7f..5443b07 100644 (file)
@@ -190,6 +190,8 @@ $string['exportalloutcomes'] = 'Export all outcomes';
 $string['exportfeedback'] = 'Include feedback in export';
 $string['exportplugins'] = 'Export plugins';
 $string['exportsettings'] = 'Export settings';
+$string['exportonlyactive'] = 'Require active enrolment';
+$string['exportonlyactive_help'] = 'Only include students in the export whose enrolment has not been suspended';
 $string['exportto'] = 'Export to';
 $string['extracreditwarning'] = 'Note: Setting all items for a category to extra credit will effectively remove them from the grade calculation. Since there will be no point total';
 $string['feedback'] = 'Feedback';
index 1087a2b..0109f55 100644 (file)
@@ -508,6 +508,8 @@ $string['editorsettings'] = 'Editor settings';
 $string['editorshortcutkeys'] = 'Editor shortcut keys';
 $string['editsettings'] = 'Edit settings';
 $string['editsummary'] = 'Edit summary';
+$string['edittitle'] = 'Edit title';
+$string['edittitleinstructions'] = 'Escape to cancel, Enter when finished';
 $string['editthisactivity'] = 'Edit this activity';
 $string['editthiscategory'] = 'Edit this category';
 $string['edituser'] = 'Edit user accounts';
@@ -1397,6 +1399,7 @@ $string['restoreusersprecheck'] = 'Checking user data';
 $string['restoreusersprecheckerror'] = 'Some problems were detected when checking user data';
 $string['restricted'] = 'Restricted';
 $string['returningtosite'] = 'Returning to this web site?';
+$string['returntomaincoursepage'] = 'Return to main course page';
 $string['returntooriginaluser'] = 'Return to {$a}';
 $string['revert'] = 'Revert';
 $string['role'] = 'Role';
index c834eb4..535eca6 100644 (file)
@@ -377,19 +377,16 @@ class mysqli_native_moodle_database extends moodle_database {
             return $this->tables;
         }
         $this->tables = array();
-        $sql = "SHOW TABLES";
+        $prefix = str_replace('_', '\\_', $this->prefix);
+        $sql = "SHOW TABLES LIKE '$prefix%'";
         $this->query_start($sql, null, SQL_QUERY_AUX);
         $result = $this->mysqli->query($sql);
         $this->query_end($result);
+        $len = strlen($this->prefix);
         if ($result) {
             while ($arr = $result->fetch_assoc()) {
                 $tablename = reset($arr);
-                if ($this->prefix !== '') {
-                    if (strpos($tablename, $this->prefix) !== 0) {
-                        continue;
-                    }
-                    $tablename = substr($tablename, strlen($this->prefix));
-                }
+                $tablename = substr($tablename, $len);
                 $this->tables[$tablename] = $tablename;
             }
             $result->close();
index 484ed6b..587ed66 100644 (file)
@@ -31,6 +31,7 @@ $string['common:browsemedia'] = 'Find or upload a sound, video or applet...';
 $string['dragmath:dragmath_desc'] = 'Insert equation';
 $string['dragmath:dragmath_javaneeded'] = 'To use this page you need a Java-enabled browser. Download the latest Java plug-in from {$a}.';
 $string['dragmath:dragmath_title'] = 'DragMath Equation Editor';
+$string['fontselectlist'] = 'Available fonts list';
 $string['media_dlg:filename'] = 'Filename';
 $string['moodleemoticon:desc'] = 'Insert emoticon';
 $string['moodlenolink:desc'] = 'Prevent automatic linking';
index 3b1a691..885af15 100644 (file)
@@ -94,6 +94,14 @@ class tinymce_texteditor extends texteditor {
 
         $context = empty($options['context']) ? get_context_instance(CONTEXT_SYSTEM) : $options['context'];
 
+        $config = get_config('editor_tinymce');
+
+        $spelllanguagelist = empty($config->spelllanguagelist) ? '' : $config->spelllanguagelist;
+        $spellbutton = ($spelllanguagelist === '') ? '' : ',spellchecker';
+
+        $fontselectlist = empty($config->fontselectlist) ? '' : $config->fontselectlist;
+        $fontbutton = ($fontselectlist === '') ? '' : 'fontselect,';
+
         $xmedia = 'moodlemedia,'; // HQ thinks it should be always on, so it is no matter if it will actually work or not
         /*
         if (!empty($options['legacy'])) {
@@ -135,20 +143,20 @@ class tinymce_texteditor extends texteditor {
                     'theme_advanced_font_sizes' => "1,2,3,4,5,6,7",
                     'theme_advanced_layout_manager' => "SimpleLayout",
                     'theme_advanced_toolbar_align' => "left",
-                    'theme_advanced_buttons1' => "fontselect,fontsizeselect,formatselect",
+                    'theme_advanced_buttons1' => "{$fontbutton}fontsizeselect,formatselect",
                     'theme_advanced_buttons1_add' => "|,undo,redo,|,search,replace,|,fullscreen",
                     'theme_advanced_buttons2' => "bold,italic,underline,strikethrough,sub,sup,|,justifyleft,justifycenter,justifyright",
                     'theme_advanced_buttons2_add' => "|,cleanup,removeformat,pastetext,pasteword,|,forecolor,backcolor,|,ltr,rtl",
                     'theme_advanced_buttons3' => "bullist,numlist,outdent,indent,|,link,unlink,moodlenolink,|,image,{$xemoticon}{$xmedia}{$xdragmath}nonbreaking,charmap",
-                    'theme_advanced_buttons3_add' => "table,|,code,spellchecker",
-                    'theme_advanced_fonts' => "Trebuchet=Trebuchet MS,Verdana,Arial,Helvetica,sans-serif;Arial=arial,helvetica,sans-serif;Courier New=courier new,courier,monospace;Georgia=georgia,times new roman,times,serif;Tahoma=tahoma,arial,helvetica,sans-serif;Times New Roman=times new roman,times,serif;Verdana=verdana,arial,helvetica,sans-serif;Impact=impact;Wingdings=wingdings",
+                    'theme_advanced_buttons3_add' => "table,|,code{$spellbutton}",
+                    'theme_advanced_fonts' => $fontselectlist,
                     'theme_advanced_resize_horizontal' => true,
                     'theme_advanced_resizing' => true,
                     'theme_advanced_resizing_min_height' => 30,
                     'theme_advanced_toolbar_location' => "top",
                     'theme_advanced_statusbar_location' => "bottom",
                     'spellchecker_rpc_url' => $CFG->wwwroot."/lib/editor/tinymce/tiny_mce/$this->version/plugins/spellchecker/rpc.php",
-                    'spellchecker_languages' => get_config('editor_tinymce', 'spelllanguagelist')
+                    'spellchecker_languages' => $spelllanguagelist
                   );
 
         if ($xemoticon) {
index db02700..d26d84b 100644 (file)
@@ -33,8 +33,11 @@ if ($ADMIN->fulltree) {
         'PSpellShell'=>'PSpellShell');
     $settings->add(new admin_setting_configselect('editor_tinymce/spellengine',
             get_string('spellengine', 'admin'), '', 'GoogleSpell', $options));
-    $settings->add(new admin_setting_configtext('editor_tinymce/spelllanguagelist',
+    $settings->add(new admin_setting_configtextarea('editor_tinymce/spelllanguagelist',
             get_string('spelllanguagelist', 'admin'), '',
             '+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,' .
             'Portuguese=pt,Spanish=es,Swedish=sv', PARAM_RAW));
+    $settings->add(new admin_setting_configtextarea('editor_tinymce/fontselectlist',
+        get_string('fontselectlist', 'editor_tinymce'), '',
+        'Trebuchet=Trebuchet MS,Verdana,Arial,Helvetica,sans-serif;Arial=arial,helvetica,sans-serif;Courier New=courier new,courier,monospace;Georgia=georgia,times new roman,times,serif;Tahoma=tahoma,arial,helvetica,sans-serif;Times New Roman=times new roman,times,serif;Verdana=verdana,arial,helvetica,sans-serif;Impact=impact;Wingdings=wingdings', PARAM_RAW));
 }
index 318d641..416fd95 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012030300;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2012050800;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2011112900;        // Requires this Moodle version
 $plugin->component = 'editor_tinymce';  // Full name of the plugin (used for diagnostics)
-$plugin->release   = '3.4.9';
+$plugin->release   = '3.5.0';
index 888bbc3..68300f6 100644 (file)
@@ -6,7 +6,7 @@
 
   function HeaderingExcel($filename) {
       header("Content-type: application/vnd.ms-excel");
-      header("Content-Disposition: attachment; filename=$filename" );
+      header("Content-Disposition: attachment; filename=\"$filename\"" );
       header("Expires: 0");
       header("Cache-Control: must-revalidate, post-check=0,pre-check=0");
       header("Pragma: public");
@@ -87,4 +87,4 @@
   //$worksheet2->insert_bitmap(0, 0, "some.bmp",10,10);
 
   $workbook->close();
-?>
\ No newline at end of file
+?>
index 3eb1f1f..9ef3b12 100644 (file)
@@ -581,4 +581,22 @@ function external_delete_descriptions($component) {
             "functionname IN (SELECT name FROM {external_functions} WHERE component = ?)", $params);
     $DB->delete_records('external_services', array('component'=>$component));
     $DB->delete_records('external_functions', array('component'=>$component));
-}
\ No newline at end of file
+}
+
+/**
+ * Creates a warnings external_multiple_structure
+ *
+ * @return external_multiple_structure
+ * @since  Moodle 2.3
+ */
+function external_warnings() {
+    return new external_multiple_structure(
+        new external_single_structure(array(
+            'item' => new external_value(PARAM_TEXT, 'item', VALUE_OPTIONAL),
+            'itemid' => new external_value(PARAM_INT, 'item id', VALUE_OPTIONAL),
+            'warningcode' => new external_value(PARAM_ALPHANUM, 'the warning code can be used by
+                the client app to implement specific behaviour'),
+            'message' => new external_value(PARAM_TEXT, 'untranslated english message to explain the warning')
+                ), 'warning'), 'list of warnings', VALUE_OPTIONAL
+    );
+}
index aba7175..f10d8ca 100644 (file)
@@ -1801,7 +1801,7 @@ function send_temp_file($path, $filename, $pathisstring=false) {
         $filename = urlencode($filename);
     }
 
-    header('Content-Disposition: attachment; filename='.$filename);
+    header('Content-Disposition: attachment; filename="'.$filename.'"');
     if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
         header('Cache-Control: max-age=10');
         header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
index 34b76f7..371e82f 100644 (file)
@@ -136,6 +136,7 @@ YUI.add('moodle-form-dateselector', function(Y) {
             this.yearselect.set('selectedIndex', newindex);
             this.monthselect.set('selectedIndex', date[1] - this.monthselect.firstOptionValue());
             this.dayselect.set('selectedIndex', date[2] - this.dayselect.firstOptionValue());
+            M.form.dateselector.release_current();
         },
         connect_handlers : function() {
             M.form.dateselector.calendar.selectEvent.subscribe(this.set_selects_from_date, this, true);
index d781292..8f18a11 100644 (file)
@@ -10096,20 +10096,22 @@ function get_performance_info() {
              } else {
                  $othercount += 1;
              }
-             $details .= "<div class='yui-module'><p>$module</p>";
-             foreach ($backtraces as $backtrace) {
-                 $details .= "<div class='backtrace'>$backtrace</div>";
+             if (!empty($CFG->yuimoduledebug)) {
+                 // hidden feature for developers working on YUI module infrastructure
+                 $details .= "<div class='yui-module'><p>$module</p>";
+                 foreach ($backtraces as $backtrace) {
+                     $details .= "<div class='backtrace'>$backtrace</div>";
+                 }
+                 $details .= '</div>';
              }
-             $details .= '</div>';
          }
          $info['html'] .= "<span class='includedyuimodules'>Included YUI modules: $yuicount</span> ";
          $info['txt'] .= "includedyuimodules: $yuicount ";
          $info['html'] .= "<span class='includedjsmodules'>Other JavaScript modules: $othercount</span> ";
          $info['txt'] .= "includedjsmodules: $othercount ";
-         // Slightly odd to output the details in a display: none div. The point
-         // Is that it takes a lot of space, and if you care you can reveal it
-         // using firebug.
-         $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
+         if ($details) {
+             $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
+         }
      }
 
     if (!empty($PERF->logwrites)) {
index 707ea92..5f44996 100644 (file)
@@ -1577,21 +1577,29 @@ class global_navigation extends navigation_node {
                 // First up fetch all of the courses in categories where we know that we are going to
                 // need the majority of courses.
                 list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
-                list($courseids, $courseparams) = $DB->get_in_or_equal(array_keys($this->addedcourses) + array($SITE->id), SQL_PARAMS_NAMED, 'lcourse', false);
                 list($categoryids, $categoryparams) = $DB->get_in_or_equal($fullfetch, SQL_PARAMS_NAMED, 'lcategory');
                 $sql = "SELECT c.id, c.sortorder, c.visible, c.fullname, c.shortname, c.category $ccselect
                             FROM {course} c
                                 $ccjoin
-                            WHERE c.category {$categoryids} AND
-                                c.id {$courseids}
+                            WHERE c.category {$categoryids}
                         ORDER BY c.sortorder ASC";
-                $coursesrs = $DB->get_recordset_sql($sql, $courseparams + $categoryparams);
+                $coursesrs = $DB->get_recordset_sql($sql, $categoryparams);
                 foreach ($coursesrs as $course) {
+                    if ($course->id == $SITE->id) {
+                        // This should not be necessary, frontpage is not in any category.
+                        continue;
+                    }
+                    if (array_key_exists($course->id, $this->addedcourses)) {
+                        // It is probably better to not include the already loaded courses
+                        // directly in SQL because inequalities may confuse query optimisers
+                        // and may interfere with query caching.
+                        continue;
+                    }
                     if (!$this->can_add_more_courses_to_category($course->category)) {
                         continue;
                     }
                     context_instance_preload($course);
-                    if ($course->id != $SITE->id && !$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
+                    if (!$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
                         continue;
                     }
                     $coursenodes[$course->id] = $this->add_course($course);
@@ -1604,21 +1612,30 @@ class global_navigation extends navigation_node {
                 // proportion of the courses.
                 foreach ($partfetch as $categoryid) {
                     list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
-                    list($courseids, $courseparams) = $DB->get_in_or_equal(array_keys($this->addedcourses) + array($SITE->id), SQL_PARAMS_NAMED, 'lcourse', false);
                     $sql = "SELECT c.id, c.sortorder, c.visible, c.fullname, c.shortname, c.category $ccselect
                                 FROM {course} c
                                     $ccjoin
-                                WHERE c.category = :categoryid AND
-                                    c.id {$courseids}
+                                WHERE c.category = :categoryid
                             ORDER BY c.sortorder ASC";
-                    $courseparams['categoryid'] = $categoryid;
+                    $courseparams = array('categoryid' => $categoryid);
                     $coursesrs = $DB->get_recordset_sql($sql, $courseparams, 0, $limit * 5);
                     foreach ($coursesrs as $course) {
+                        if ($course->id == $SITE->id) {
+                            // This should not be necessary, frontpage is not in any category.
+                            continue;
+                        }
+                        if (array_key_exists($course->id, $this->addedcourses)) {
+                            // It is probably better to not include the already loaded courses
+                            // directly in SQL because inequalities may confuse query optimisers
+                            // and may interfere with query caching.
+                            // This also helps to respect expected $limit on repeated executions.
+                            continue;
+                        }
                         if (!$this->can_add_more_courses_to_category($course->category)) {
                             break;
                         }
                         context_instance_preload($course);
-                        if ($course->id != $SITE->id && !$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
+                        if (!$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
                             continue;
                         }
                         $coursenodes[$course->id] = $this->add_course($course);
@@ -1637,8 +1654,12 @@ class global_navigation extends navigation_node {
                     ORDER BY c.sortorder ASC";
             $coursesrs = $DB->get_recordset_sql($sql, $courseparams);
             foreach ($coursesrs as $course) {
+                if ($course->id == $SITE->id) {
+                    // frotpage is not wanted here
+                    continue;
+                }
                 context_instance_preload($course);
-                if ($course->id != $SITE->id && !$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
+                if (!$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
                     continue;
                 }
                 $coursenodes[$course->id] = $this->add_course($course);
index 8bd3f38..95d3bb8 100644 (file)
@@ -520,7 +520,7 @@ class core_renderer extends renderer_base {
             return '';
         }
 
-        $loginapge = ((string)$this->page->url === get_login_url());
+        $loginpage = ((string)$this->page->url === get_login_url());
         $course = $this->page->course;
 
         if (session_is_loggedinas()) {
@@ -547,7 +547,7 @@ class core_renderer extends renderer_base {
             }
             if (isguestuser()) {
                 $loggedinas = $realuserinfo.get_string('loggedinasguest');
-                if (!$loginapge) {
+                if (!$loginpage) {
                     $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
                 }
             } else if (is_role_switched($course->id)) { // Has switched roles
@@ -563,7 +563,7 @@ class core_renderer extends renderer_base {
             }
         } else {
             $loggedinas = get_string('loggedinnot', 'moodle');
-            if (!$loginapge) {
+            if (!$loginpage) {
                 $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
             }
         }
diff --git a/lib/phpunit/classes/advanced_testcase.php b/lib/phpunit/classes/advanced_testcase.php
new file mode 100644 (file)
index 0000000..2afb8c3
--- /dev/null
@@ -0,0 +1,339 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Advanced test case.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * Advanced PHPUnit test case customised for Moodle.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class advanced_testcase extends PHPUnit_Framework_TestCase {
+    /** @var bool automatically reset everything? null means log changes */
+    private $resetAfterTest;
+
+    /** @var moodle_transaction */
+    private $testdbtransaction;
+
+    /**
+     * Constructs a test case with the given name.
+     *
+     * Note: use setUp() or setUpBeforeClass() in your test cases.
+     *
+     * @param string $name
+     * @param array  $data
+     * @param string $dataName
+     */
+    final public function __construct($name = null, array $data = array(), $dataName = '') {
+        parent::__construct($name, $data, $dataName);
+
+        $this->setBackupGlobals(false);
+        $this->setBackupStaticAttributes(false);
+        $this->setRunTestInSeparateProcess(false);
+    }
+
+    /**
+     * Runs the bare test sequence.
+     * @return void
+     */
+    final public function runBare() {
+        global $DB;
+
+        if (phpunit_util::$lastdbwrites != $DB->perf_get_writes()) {
+            // this happens when previous test does not reset, we can not use transactions
+            $this->testdbtransaction = null;
+
+        } else if ($DB->get_dbfamily() === 'postgres' or $DB->get_dbfamily() === 'mssql') {
+            // database must allow rollback of DDL, so no mysql here
+            $this->testdbtransaction = $DB->start_delegated_transaction();
+        }
+
+        try {
+            parent::runBare();
+            // set DB reference in case somebody mocked it in test
+            $DB = phpunit_util::get_global_backup('DB');
+        } catch (Exception $e) {
+            // cleanup after failed expectation
+            phpunit_util::reset_all_data();
+            throw $e;
+        }
+
+        if (!$this->testdbtransaction or $this->testdbtransaction->is_disposed()) {
+            $this->testdbtransaction = null;
+        }
+
+        if ($this->resetAfterTest === true) {
+            if ($this->testdbtransaction) {
+                $DB->force_transaction_rollback();
+                phpunit_util::reset_all_database_sequences();
+                phpunit_util::$lastdbwrites = $DB->perf_get_writes(); // no db reset necessary
+            }
+            phpunit_util::reset_all_data();
+
+        } else if ($this->resetAfterTest === false) {
+            if ($this->testdbtransaction) {
+                $this->testdbtransaction->allow_commit();
+            }
+            // keep all data untouched for other tests
+
+        } else {
+            // reset but log what changed
+            if ($this->testdbtransaction) {
+                try {
+                    $this->testdbtransaction->allow_commit();
+                } catch (dml_transaction_exception $e) {
+                    phpunit_util::reset_all_data();
+                    throw new coding_exception('Invalid transaction state detected in test '.$this->getName());
+                }
+            }
+            phpunit_util::reset_all_data(true);
+        }
+
+        // make sure test did not forget to close transaction
+        if ($DB->is_transaction_started()) {
+            phpunit_util::reset_all_data();
+            if ($this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED
+                or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED
+                or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) {
+                throw new coding_exception('Test '.$this->getName().' did not close database transaction');
+            }
+        }
+    }
+
+    /**
+     * Creates a new FlatXmlDataSet with the given $xmlFile. (absolute path.)
+     *
+     * @param string $xmlFile
+     * @return PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet
+     */
+    protected function createFlatXMLDataSet($xmlFile) {
+        return new PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet($xmlFile);
+    }
+
+    /**
+     * Creates a new XMLDataSet with the given $xmlFile. (absolute path.)
+     *
+     * @param string $xmlFile
+     * @return PHPUnit_Extensions_Database_DataSet_XmlDataSet
+     */
+    protected function createXMLDataSet($xmlFile) {
+        return new PHPUnit_Extensions_Database_DataSet_XmlDataSet($xmlFile);
+    }
+
+    /**
+     * Creates a new CsvDataSet from the given array of csv files. (absolute paths.)
+     *
+     * @param array $files array tablename=>cvsfile
+     * @param string $delimiter
+     * @param string $enclosure
+     * @param string $escape
+     * @return PHPUnit_Extensions_Database_DataSet_CsvDataSet
+     */
+    protected function createCsvDataSet($files, $delimiter = ',', $enclosure = '"', $escape = '"') {
+        $dataSet = new PHPUnit_Extensions_Database_DataSet_CsvDataSet($delimiter, $enclosure, $escape);
+        foreach($files as $table=>$file) {
+            $dataSet->addTable($table, $file);
+        }
+        return $dataSet;
+    }
+
+    /**
+     * Creates new ArrayDataSet from given array
+     *
+     * @param array $data array of tables, first row in each table is columns
+     * @return phpunit_ArrayDataSet
+     */
+    protected function createArrayDataSet(array $data) {
+        return new phpunit_ArrayDataSet($data);
+    }
+
+    /**
+     * Load date into moodle database tables from standard PHPUnit data set.
+     *
+     * Note: it is usually better to use data generators
+     *
+     * @param PHPUnit_Extensions_Database_DataSet_IDataSet $dataset
+     * @return void
+     */
+    protected function loadDataSet(PHPUnit_Extensions_Database_DataSet_IDataSet $dataset) {
+        global $DB;
+
+        $structure = phpunit_util::get_tablestructure();
+
+        foreach($dataset->getTableNames() as $tablename) {
+            $table = $dataset->getTable($tablename);
+            $metadata = $dataset->getTableMetaData($tablename);
+            $columns = $metadata->getColumns();
+
+            $doimport = false;
+            if (isset($structure[$tablename]['id']) and $structure[$tablename]['id']->auto_increment) {
+                $doimport = in_array('id', $columns);
+            }
+
+            for($r=0; $r<$table->getRowCount(); $r++) {
+                $record = $table->getRow($r);
+                if ($doimport) {
+                    $DB->import_record($tablename, $record);
+                } else {
+                    $DB->insert_record($tablename, $record);
+                }
+            }
+            if ($doimport) {
+                $DB->get_manager()->reset_sequence(new xmldb_table($tablename));
+            }
+        }
+    }
+
+    /**
+     * Call this method from test if you want to make sure that
+     * the resetting of database is done the slow way without transaction
+     * rollback.
+     *
+     * This is useful especially when testing stuff that is not compatible with transactions.
+     *
+     * @return void
+     */
+    public function preventResetByRollback() {
+        if ($this->testdbtransaction and !$this->testdbtransaction->is_disposed()) {
+            $this->testdbtransaction->allow_commit();
+            $this->testdbtransaction = null;
+        }
+    }
+
+    /**
+     * Reset everything after current test.
+     * @param bool $reset true means reset state back, false means keep all data for the next test,
+     *      null means reset state and show warnings if anything changed
+     * @return void
+     */
+    public function resetAfterTest($reset = true) {
+        $this->resetAfterTest = $reset;
+    }
+
+    /**
+     * Cleanup after all tests are executed.
+     *
+     * Note: do not forget to call this if overridden...
+     *
+     * @static
+     * @return void
+     */
+    public static function tearDownAfterClass() {
+        phpunit_util::reset_all_data();
+    }
+
+    /**
+     * Reset all database tables, restore global state and clear caches and optionally purge dataroot dir.
+     * @static
+     * @return void
+     */
+    public static function resetAllData() {
+        phpunit_util::reset_all_data();
+    }
+
+    /**
+     * Set current $USER, reset access cache.
+     * @static
+     * @param null|int|stdClass $user user record, null or 0 means non-logged-in, positive integer means userid
+     * @return void
+     */
+    public static function setUser($user = null) {
+        global $CFG, $DB;
+
+        if (is_object($user)) {
+            $user = clone($user);
+        } else if (!$user) {
+            $user = new stdClass();
+            $user->id = 0;
+            $user->mnethostid = $CFG->mnet_localhost_id;
+        } else {
+            $user = $DB->get_record('user', array('id'=>$user));
+        }
+        unset($user->description);
+        unset($user->access);
+        unset($user->preference);
+
+        session_set_user($user);
+    }
+
+    /**
+     * Set current $USER to admin account, reset access cache.
+     * @static
+     * @return void
+     */
+    public static function setAdminUser() {
+        self::setUser(2);
+    }
+
+    /**
+     * Set current $USER to guest account, reset access cache.
+     * @static
+     * @return void
+     */
+    public static function setGuestUser() {
+        self::setUser(1);
+    }
+
+    /**
+     * Get data generator
+     * @static
+     * @return phpunit_data_generator
+     */
+    public static function getDataGenerator() {
+        return phpunit_util::get_data_generator();
+    }
+
+    /**
+     * Recursively visit all the files in the source tree. Calls the callback
+     * function with the pathname of each file found.
+     *
+     * @param string $path the folder to start searching from.
+     * @param string $callback the method of this class to call with the name of each file found.
+     * @param string $fileregexp a regexp used to filter the search (optional).
+     * @param bool $exclude If true, pathnames that match the regexp will be ignored. If false,
+     *     only files that match the regexp will be included. (default false).
+     * @param array $ignorefolders will not go into any of these folders (optional).
+     * @return void
+     */
+    public function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) {
+        $files = scandir($path);
+
+        foreach ($files as $file) {
+            $filepath = $path .'/'. $file;
+            if (strpos($file, '.') === 0) {
+                /// Don't check hidden files.
+                continue;
+            } else if (is_dir($filepath)) {
+                if (!in_array($filepath, $ignorefolders)) {
+                    $this->recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders);
+                }
+            } else if ($exclude xor preg_match($fileregexp, $filepath)) {
+                $this->$callback($filepath);
+            }
+        }
+    }
+}
diff --git a/lib/phpunit/classes/arraydataset.php b/lib/phpunit/classes/arraydataset.php
new file mode 100644 (file)
index 0000000..d4148f9
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Array based data iterator.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * Based on array iterator code from PHPUnit documentation by Sebastian Bergmann
+ * with new constructor parameter for different array types.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class phpunit_ArrayDataSet extends PHPUnit_Extensions_Database_DataSet_AbstractDataSet {
+    /**
+     * @var array
+     */
+    protected $tables = array();
+
+    /**
+     * @param array $data
+     */
+    public function __construct(array $data) {
+        foreach ($data AS $tableName => $rows) {
+            $firstrow = reset($rows);
+
+            if (array_key_exists(0, $firstrow)) {
+                // columns in first row
+                $columnsInFirstRow = true;
+                $columns = $firstrow;
+                $key = key($rows);
+                unset($rows[$key]);
+            } else {
+                // column name is in each row as key
+                $columnsInFirstRow = false;
+                $columns = array_keys($firstrow);
+            }
+
+            $metaData = new PHPUnit_Extensions_Database_DataSet_DefaultTableMetaData($tableName, $columns);
+            $table = new PHPUnit_Extensions_Database_DataSet_DefaultTable($metaData);
+
+            foreach ($rows AS $row) {
+                if ($columnsInFirstRow) {
+                    $row = array_combine($columns, $row);
+                }
+                $table->addRow($row);
+            }
+            $this->tables[$tableName] = $table;
+        }
+    }
+
+    protected function createIterator($reverse = FALSE) {
+        return new PHPUnit_Extensions_Database_DataSet_DefaultTableIterator($this->tables, $reverse);
+    }
+
+    public function getTable($tableName) {
+        if (!isset($this->tables[$tableName])) {
+            throw new InvalidArgumentException("$tableName is not a table in the current database.");
+        }
+
+        return $this->tables[$tableName];
+    }
+}
diff --git a/lib/phpunit/classes/basic_testcase.php b/lib/phpunit/classes/basic_testcase.php
new file mode 100644 (file)
index 0000000..6ca3900
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Basic test case.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * The simplest PHPUnit test case customised for Moodle
+ *
+ * It is intended for isolated tests that do not modify database or any globals.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class basic_testcase extends PHPUnit_Framework_TestCase {
+
+    /**
+     * Constructs a test case with the given name.
+     *
+     * Note: use setUp() or setUpBeforeClass() in your test cases.
+     *
+     * @param string $name
+     * @param array  $data
+     * @param string $dataName
+     */
+    final public function __construct($name = null, array $data = array(), $dataName = '') {
+        parent::__construct($name, $data, $dataName);
+
+        $this->setBackupGlobals(false);
+        $this->setBackupStaticAttributes(false);
+        $this->setRunTestInSeparateProcess(false);
+    }
+
+    /**
+     * Runs the bare test sequence and log any changes in global state or database.
+     * @return void
+     */
+    final public function runBare() {
+        global $DB;
+
+        try {
+            parent::runBare();
+        } catch (Exception $e) {
+            // cleanup after failed expectation
+            phpunit_util::reset_all_data();
+            throw $e;
+        }
+
+        if ($DB->is_transaction_started()) {
+            phpunit_util::reset_all_data();
+            throw new coding_exception('basic_testcase '.$this->getName().' is not supposed to use database transactions!');
+        }
+
+        phpunit_util::reset_all_data(true);
+    }
+}
diff --git a/lib/phpunit/classes/block_generator.php b/lib/phpunit/classes/block_generator.php
new file mode 100644 (file)
index 0000000..a1cb635
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Block generator base class.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * Block generator base class.
+ *
+ * Extend in blocks/xxxx/tests/generator/lib.php as class block_xxxx_generator.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class phpunit_block_generator {
+    /** @var phpunit_data_generator@var  */
+    protected $datagenerator;
+
+    /** @var number of created instances */
+    protected $instancecount = 0;
+
+    public function __construct(phpunit_data_generator $datagenerator) {
+        $this->datagenerator = $datagenerator;
+    }
+
+    /**
+     * To be called from data reset code only,
+     * do not use in tests.
+     * @return void
+     */
+    public function reset() {
+        $this->instancecount = 0;
+    }
+
+    /**
+     * Returns block name
+     * @return string name of block that this class describes
+     * @throws coding_exception if class invalid
+     */
+    public function get_blockname() {
+        $matches = null;
+        if (!preg_match('/^block_([a-z0-9_]+)_generator$/', get_class($this), $matches)) {
+            throw new coding_exception('Invalid block generator class name: '.get_class($this));
+        }
+
+        if (empty($matches[1])) {
+            throw new coding_exception('Invalid block generator class name: '.get_class($this));
+        }
+        return $matches[1];
+    }
+
+    /**
+     * Fill in record defaults
+     * @param stdClass $record
+     * @return stdClass
+     */
+    protected function prepare_record(stdClass $record) {
+        $record->blockname = $this->get_blockname();
+        if (!isset($record->parentcontextid)) {
+            $record->parentcontextid = context_system::instance()->id;
+        }
+        if (!isset($record->showinsubcontexts)) {
+            $record->showinsubcontexts = 1;
+        }
+        if (!isset($record->pagetypepattern)) {
+            $record->pagetypepattern = '';
+        }
+        if (!isset($record->subpagepattern)) {
+            $record->subpagepattern = null;
+        }
+        if (!isset($record->defaultregion)) {
+            $record->defaultregion = '';
+        }
+        if (!isset($record->defaultweight)) {
+            $record->defaultweight = '';
+        }
+        if (!isset($record->configdata)) {
+            $record->configdata = null;
+        }
+        return $record;
+    }
+
+    /**
+     * Create a test block
+     * @param array|stdClass $record
+     * @param array $options
+     * @return stdClass activity record
+     */
+    abstract public function create_instance($record = null, array $options = null);
+}
diff --git a/lib/phpunit/classes/data_generator.php b/lib/phpunit/classes/data_generator.php
new file mode 100644 (file)
index 0000000..48a459f
--- /dev/null
@@ -0,0 +1,544 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Data generator.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * Data generator class for unit tests and other tools
+ * that need to create fake test sites.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class phpunit_data_generator {
+    protected $usercounter = 0;
+    protected $categorycount = 0;
+    protected $coursecount = 0;
+    protected $scalecount = 0;
+    protected $groupcount = 0;
+    protected $groupingcount = 0;
+
+    /** @var array list of plugin generators */
+    protected $generators = array();
+
+    /** @var array lis of common last names */
+    public $lastnames = array(
+        'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Davis', 'García', 'Rodríguez', 'Wilson',
+        'Müller', 'Schmidt', 'Schneider', 'Fischer', 'Meyer', 'Weber', 'Schulz', 'Wagner', 'Becker', 'Hoffmann',
+        'Novák', 'Svoboda', 'Novotný', 'Dvořák', 'Černý', 'Procházková', 'Kučerová', 'Veselá', 'Horáková', 'Němcová',
+        'Смирнов', 'Иванов', 'Кузнецов', 'Соколов', 'Попов', 'Лебедева', 'Козлова', 'Новикова', 'Морозова', 'Петрова',
+        '王', '李', '张', '刘', '陈', '楊', '黃', '趙', '吳', '周',
+        '佐藤', '鈴木', '高橋', '田中', '渡辺', '伊藤', '山本', '中村', '小林', '斎藤',
+    );
+
+    /** @var array lis of common first names */
+    public $firstnames = array(
+        'Jacob', 'Ethan', 'Michael', 'Jayden', 'William', 'Isabella', 'Sophia', 'Emma', 'Olivia', 'Ava',
+        'Lukas', 'Leon', 'Luca', 'Timm', 'Paul', 'Leonie', 'Leah', 'Lena', 'Hanna', 'Laura',
+        'Jakub', 'Jan', 'Tomáš', 'Lukáš', 'Matěj', 'Tereza', 'Eliška', 'Anna', 'Adéla', 'Karolína',
+        'Даниил', 'Максим', 'Артем', 'Иван', 'Александр', 'София', 'Анастасия', 'Дарья', 'Мария', 'Полина',
+        '伟', '伟', '芳', '伟', '秀英', '秀英', '娜', '秀英', '伟', '敏',
+        '翔', '大翔', '拓海', '翔太', '颯太', '陽菜', 'さくら', '美咲', '葵', '美羽',
+    );
+
+    public $loremipsum = <<<EOD
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla non arcu lacinia neque faucibus fringilla. Vivamus porttitor turpis ac leo. Integer in sapien. Nullam eget nisl. Aliquam erat volutpat. Cras elementum. Mauris suscipit, ligula sit amet pharetra semper, nibh ante cursus purus, vel sagittis velit mauris vel metus. Integer malesuada. Nullam lectus justo, vulputate eget mollis sed, tempor sed magna. Mauris elementum mauris vitae tortor. Aliquam erat volutpat.
+Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Pellentesque ipsum. Cras pede libero, dapibus nec, pretium sit amet, tempor quis. Aliquam ante. Proin in tellus sit amet nibh dignissim sagittis. Vivamus porttitor turpis ac leo. Duis bibendum, lectus ut viverra rhoncus, dolor nunc faucibus libero, eget facilisis enim ipsum id lacus. In sem justo, commodo ut, suscipit at, pharetra vitae, orci. Aliquam erat volutpat. Nulla est.
+Vivamus luctus egestas leo. Aenean fermentum risus id tortor. Mauris dictum facilisis augue. Aliquam erat volutpat. Aliquam ornare wisi eu metus. Aliquam id dolor. Duis condimentum augue id magna semper rutrum. Donec iaculis gravida nulla. Pellentesque ipsum. Etiam dictum tincidunt diam. Quisque tincidunt scelerisque libero. Etiam egestas wisi a erat.
+Integer lacinia. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris tincidunt sem sed arcu. Nullam feugiat, turpis at pulvinar vulputate, erat libero tristique tellus, nec bibendum odio risus sit amet ante. Aliquam id dolor. Maecenas sollicitudin. Et harum quidem rerum facilis est et expedita distinctio. Mauris suscipit, ligula sit amet pharetra semper, nibh ante cursus purus, vel sagittis velit mauris vel metus. Nullam dapibus fermentum ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Pellentesque sapien. Duis risus. Mauris elementum mauris vitae tortor. Suspendisse nisl. Integer rutrum, orci vestibulum ullamcorper ultricies, lacus quam ultricies odio, vitae placerat pede sem sit amet enim.
+In laoreet, magna id viverra tincidunt, sem odio bibendum justo, vel imperdiet sapien wisi sed libero. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Nullam justo enim, consectetuer nec, ullamcorper ac, vestibulum in, elit. Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Maecenas lorem. Etiam posuere lacus quis dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Nam sed tellus id magna elementum tincidunt. Suspendisse nisl. Vivamus luctus egestas leo. Nulla non arcu lacinia neque faucibus fringilla. Etiam dui sem, fermentum vitae, sagittis id, malesuada in, quam. Etiam dictum tincidunt diam. Etiam commodo dui eget wisi. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Duis ante orci, molestie vitae vehicula venenatis, tincidunt ac pede. Pellentesque sapien.
+EOD;
+
+    /**
+     * To be called from data reset code only,
+     * do not use in tests.
+     * @return void
+     */
+    public function reset() {
+        $this->usercounter = 0;
+        $this->categorycount = 0;
+        $this->coursecount = 0;
+        $this->scalecount = 0;
+
+        foreach($this->generators as $generator) {
+            $generator->reset();
+        }
+    }
+
+    /**
+     * Return generator for given plugin
+     * @param string $component
+     * @return mixed plugin data generator
+     */
+    public function get_plugin_generator($component) {
+        list($type, $plugin) = normalize_component($component);
+
+        if ($type !== 'mod' and $type !== 'block') {
+            throw new coding_exception("Plugin type $type does not support generators yet");
+        }
+
+        $dir = get_plugin_directory($type, $plugin);
+
+        if (!isset($this->generators[$type.'_'.$plugin])) {
+            $lib = "$dir/tests/generator/lib.php";
+            if (!include_once($lib)) {
+                throw new coding_exception("Plugin $component does not support data generator, missing tests/generator/lib");
+            }
+            $classname = $type.'_'.$plugin.'_generator';
+            $this->generators[$type.'_'.$plugin] = new $classname($this);
+        }
+
+        return $this->generators[$type.'_'.$plugin];
+    }
+
+    /**
+     * Create a test user
+     * @param array|stdClass $record
+     * @param array $options
+     * @return stdClass user record
+     */
+    public function create_user($record=null, array $options=null) {
+        global $DB, $CFG;
+
+        $this->usercounter++;
+        $i = $this->usercounter;
+
+        $record = (array)$record;
+
+        if (!isset($record['auth'])) {
+            $record['auth'] = 'manual';
+        }
+
+        if (!isset($record['firstname']) and !isset($record['lastname'])) {
+            $country = rand(0, 5);
+            $firstname = rand(0, 4);
+            $lastname = rand(0, 4);
+            $female = rand(0, 1);
+            $record['firstname'] = $this->firstnames[($country*10) + $firstname + ($female*5)];
+            $record['lastname'] = $this->lastnames[($country*10) + $lastname + ($female*5)];
+
+        } else if (!isset($record['firstname'])) {
+            $record['firstname'] = 'Firstname'.$i;
+
+        } else if (!isset($record['lastname'])) {
+            $record['lastname'] = 'Lastname'.$i;
+        }
+
+        if (!isset($record['idnumber'])) {
+            $record['idnumber'] = '';
+        }
+
+        if (!isset($record['mnethostid'])) {
+            $record['mnethostid'] = $CFG->mnet_localhost_id;
+        }
+
+        if (!isset($record['username'])) {
+            $record['username'] = textlib::strtolower($record['firstname']).textlib::strtolower($record['lastname']);
+            while ($DB->record_exists('user', array('username'=>$record['username'], 'mnethostid'=>$record['mnethostid']))) {
+                $record['username'] = $record['username'].'_'.$i;
+            }
+        }
+
+        if (!isset($record['password'])) {
+            $record['password'] = 'lala';
+        }
+
+        if (!isset($record['email'])) {
+            $record['email'] = $record['username'].'@example.com';
+        }
+
+        if (!isset($record['confirmed'])) {
+            $record['confirmed'] = 1;
+        }
+
+        if (!isset($record['lang'])) {
+            $record['lang'] = 'en';
+        }
+
+        if (!isset($record['maildisplay'])) {
+            $record['maildisplay'] = 1;
+        }
+
+        if (!isset($record['deleted'])) {
+            $record['deleted'] = 0;
+        }
+
+        $record['timecreated'] = time();
+        $record['timemodified'] = $record['timecreated'];
+        $record['lastip'] = '0.0.0.0';
+
+        $record['password'] = hash_internal_user_password($record['password']);
+
+        if ($record['deleted']) {
+            $delname = $record['email'].'.'.time();
+            while ($DB->record_exists('user', array('username'=>$delname))) {
+                $delname++;
+            }
+            $record['idnumber'] = '';
+            $record['email']    = md5($record['username']);
+            $record['username'] = $delname;
+            $record['picture']  = 0;
+        }
+
+        $userid = $DB->insert_record('user', $record);
+
+        if (!$record['deleted']) {
+            context_user::instance($userid);
+        }
+
+        return $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
+    }
+
+    /**
+     * Create a test course category
+     * @param array|stdClass $record
+     * @param array $options
+     * @return stdClass course category record
+     */
+    function create_category($record=null, array $options=null) {
+        global $DB, $CFG;
+        require_once("$CFG->dirroot/course/lib.php");
+
+        $this->categorycount++;
+        $i = $this->categorycount;
+
+        $record = (array)$record;
+
+        if (!isset($record['name'])) {
+            $record['name'] = 'Course category '.$i;
+        }
+
+        if (!isset($record['idnumber'])) {
+            $record['idnumber'] = '';
+        }
+
+        if (!isset($record['description'])) {
+            $record['description'] = "Test course category $i\n$this->loremipsum";
+        }
+
+        if (!isset($record['descriptionformat'])) {
+            $record['description'] = FORMAT_MOODLE;
+        }
+
+        if (!isset($record['parent'])) {
+            $record['descriptionformat'] = 0;
+        }
+
+        if (empty($record['parent'])) {
+            $parent = new stdClass();
+            $parent->path = '';
+            $parent->depth = 0;
+        } else {
+            $parent = $DB->get_record('course_categories', array('id'=>$record['parent']), '*', MUST_EXIST);
+        }
+        $record['depth'] = $parent->depth+1;
+
+        $record['sortorder'] = 0;
+        $record['timemodified'] = time();
+        $record['timecreated'] = $record['timemodified'];
+
+        $catid = $DB->insert_record('course_categories', $record);
+        $path = $parent->path . '/' . $catid;
+        $DB->set_field('course_categories', 'path', $path, array('id'=>$catid));
+        context_coursecat::instance($catid);
+
+        fix_course_sortorder();
+
+        return $DB->get_record('course_categories', array('id'=>$catid), '*', MUST_EXIST);
+    }
+
+    /**
+     * Create a test course
+     * @param array|stdClass $record
+     * @param array $options with keys:
+     *      'createsections'=>bool precreate all sections
+     * @return stdClass course record
+     */
+    function create_course($record=null, array $options=null) {
+        global $DB, $CFG;
+        require_once("$CFG->dirroot/course/lib.php");
+
+        $this->coursecount++;
+        $i = $this->coursecount;
+
+        $record = (array)$record;
+
+        if (!isset($record['fullname'])) {
+            $record['fullname'] = 'Test course '.$i;
+        }
+
+        if (!isset($record['shortname'])) {
+            $record['shortname'] = 'tc_'.$i;
+        }
+
+        if (!isset($record['idnumber'])) {
+            $record['idnumber'] = '';
+        }
+
+        if (!isset($record['format'])) {
+            $record['format'] = 'topics';
+        }
+
+        if (!isset($record['newsitems'])) {
+            $record['newsitems'] = 0;
+        }
+
+        if (!isset($record['numsections'])) {
+            $record['numsections'] = 5;
+        }
+
+        if (!isset($record['description'])) {
+            $record['description'] = "Test course $i\n$this->loremipsum";
+        }
+
+        if (!isset($record['descriptionformat'])) {
+            $record['description'] = FORMAT_MOODLE;
+        }
+
+        if (!isset($record['category'])) {
+            $record['category'] = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
+        }
+
+        $course = create_course((object)$record);
+        context_course::instance($course->id);
+
+        if (!empty($options['createsections'])) {
+            for($i=1; $i<$record['numsections']; $i++) {
+                self::create_course_section(array('course'=>$course->id, 'section'=>$i));
+            }
+        }
+
+        return $course;
+    }
+
+    /**
+     * Create course section if does not exist yet
+     * @param mixed $record
+     * @param array|null $options
+     * @return stdClass
+     * @throws coding_exception
+     */
+    public function create_course_section($record = null, array $options = null) {
+        global $DB;
+
+        $record = (array)$record;
+
+        if (empty($record['course'])) {
+            throw new coding_exception('course must be present in phpunit_util::create_course_section() $record');
+        }
+
+        if (!isset($record['section'])) {
+            throw new coding_exception('section must be present in phpunit_util::create_course_section() $record');
+        }
+
+        if (!isset($record['name'])) {
+            $record['name'] = '';
+        }
+
+        if (!isset($record['summary'])) {
+            $record['summary'] = '';
+        }
+
+        if (!isset($record['summaryformat'])) {
+            $record['summaryformat'] = FORMAT_MOODLE;
+        }
+
+        if ($section = $DB->get_record('course_sections', array('course'=>$record['course'], 'section'=>$record['section']))) {
+            return $section;
+        }
+
+        $section = new stdClass();
+        $section->course        = $record['course'];
+        $section->section       = $record['section'];
+        $section->name          = $record['name'];
+        $section->summary       = $record['summary'];
+        $section->summaryformat = $record['summaryformat'];
+        $id = $DB->insert_record('course_sections', $section);
+
+        return $DB->get_record('course_sections', array('id'=>$id));
+    }
+
+    /**
+     * Create a test block
+     * @param string $blockname
+     * @param array|stdClass $record
+     * @param array $options
+     * @return stdClass block instance record
+     */
+    public function create_block($blockname, $record=null, array $options=null) {
+        $generator = $this->get_plugin_generator('block_'.$blockname);
+        return $generator->create_instance($record, $options);
+    }
+
+    /**
+     * Create a test module
+     * @param string $modulename
+     * @param array|stdClass $record
+     * @param array $options
+     * @return stdClass activity record
+     */
+    public function create_module($modulename, $record=null, array $options=null) {
+        $generator = $this->get_plugin_generator('mod_'.$modulename);
+        return $generator->create_instance($record, $options);
+    }
+
+    /**
+     * Create a test group for the specified course
+     *
+     * $record should be either an array or a stdClass containing infomation about the group to create.
+     * At the very least it needs to contain courseid.
+     * Default values are added for name, description, and descriptionformat if they are not present.
+     *
+     * This function calls {@see groups_create_group()} to create the group within the database.
+     *
+     * @param array|stdClass $record
+     * @return stdClass group record
+     */
+    public function create_group($record) {
+        global $DB, $CFG;
+
+        require_once($CFG->dirroot . '/group/lib.php');
+
+        $this->groupcount++;
+        $i = $this->groupcount;
+
+        $record = (array)$record;
+
+        if (empty($record['courseid'])) {
+            throw new coding_exception('courseid must be present in phpunit_util::create_group() $record');
+        }
+
+        if (!isset($record['name'])) {
+            $record['name'] = 'group-' . $i;
+        }
+
+        if (!isset($record['description'])) {
+            $record['description'] = "Test Group $i\n{$this->loremipsum}";
+        }
+
+        if (!isset($record['descriptionformat'])) {
+            $record['descriptionformat'] = FORMAT_MOODLE;
+        }
+
+        $id = groups_create_group((object)$record);
+
+        return $DB->get_record('groups', array('id'=>$id));
+    }
+
+    /**
+     * Create a test grouping for the specified course
+     *
+     * $record should be either an array or a stdClass containing infomation about the grouping to create.
+     * At the very least it needs to contain courseid.
+     * Default values are added for name, description, and descriptionformat if they are not present.
+     *
+     * This function calls {@see groups_create_grouping()} to create the grouping within the database.
+     *
+     * @param array|stdClass $record
+     * @return stdClass grouping record
+     */
+    public function create_grouping($record) {
+        global $DB, $CFG;
+
+        require_once($CFG->dirroot . '/group/lib.php');
+
+        $this->groupingcount++;
+        $i = $this->groupingcount;
+
+        $record = (array)$record;
+
+        if (empty($record['courseid'])) {
+            throw new coding_exception('courseid must be present in phpunit_util::create_grouping() $record');
+        }
+
+        if (!isset($record['name'])) {
+            $record['name'] = 'grouping-' . $i;
+        }
+
+        if (!isset($record['description'])) {
+            $record['description'] = "Test Grouping $i\n{$this->loremipsum}";
+        }
+
+        if (!isset($record['descriptionformat'])) {
+            $record['descriptionformat'] = FORMAT_MOODLE;
+        }
+
+        $id = groups_create_grouping((object)$record);
+
+        return $DB->get_record('groupings', array('id'=>$id));
+    }
+
+    /**
+     * Create a test scale
+     * @param array|stdClass $record
+     * @param array $options
+     * @return stdClass block instance record
+     */
+    public function create_scale($record=null, array $options=null) {
+        global $DB;
+
+        $this->scalecount++;
+        $i = $this->scalecount;
+
+        $record = (array)$record;
+
+        if (!isset($record['name'])) {
+            $record['name'] = 'Test scale '.$i;
+        }
+
+        if (!isset($record['scale'])) {
+            $record['scale'] = 'A,B,C,D,F';
+        }
+
+        if (!isset($record['courseid'])) {
+            $record['courseid'] = 0;
+        }
+
+        if (!isset($record['userid'])) {
+            $record['userid'] = 0;
+        }
+
+        if (!isset($record['description'])) {
+            $record['description'] = 'Test scale description '.$i;
+        }
+
+        if (!isset($record['descriptionformat'])) {
+            $record['descriptionformat'] = FORMAT_MOODLE;
+        }
+
+        $record['timemodified'] = time();
+
+        if (isset($record['id'])) {
+            $DB->import_record('scale', $record);
+            $DB->get_manager()->reset_sequence('scale');
+            $id = $record['id'];
+        } else {
+            $id = $DB->insert_record('scale', $record);
+        }
+
+        return $DB->get_record('scale', array('id'=>$id), '*', MUST_EXIST);
+    }
+}
diff --git a/lib/phpunit/classes/database_driver_testcase.php b/lib/phpunit/classes/database_driver_testcase.php
new file mode 100644 (file)
index 0000000..dc07596
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Database driver test case.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * Special test case for testing of DML drivers and DDL layer.
+ *
+ * Note: Use only 'test_table*' names when creating new tables.
+ *
+ * For DML/DDL developers: you can add following settings to config.php if you want to test different driver than the main one,
+ *                         the reason is to allow testing of incomplete drivers that do not allow full PHPUnit environment
+ *                         initialisation (the database can be empty).
+ * $CFG->phpunit_extra_drivers = array(
+ *      1=>array('dbtype'=>'mysqli', 'dbhost'=>'localhost', 'dbname'=>'moodle', 'dbuser'=>'root', 'dbpass'=>'', 'prefix'=>'phpu2_'),
+ *      2=>array('dbtype'=>'pgsql', 'dbhost'=>'localhost', 'dbname'=>'moodle', 'dbuser'=>'postgres', 'dbpass'=>'', 'prefix'=>'phpu2_'),
+ *      3=>array('dbtype'=>'sqlsrv', 'dbhost'=>'127.0.0.1', 'dbname'=>'moodle', 'dbuser'=>'sa', 'dbpass'=>'', 'prefix'=>'phpu2_'),
+ *      4=>array('dbtype'=>'oci', 'dbhost'=>'127.0.0.1', 'dbname'=>'XE', 'dbuser'=>'sa', 'dbpass'=>'', 'prefix'=>'t_'),
+ * );
+ * define('PHPUNIT_TEST_DRIVER')=1; //number is index in the previous array
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class database_driver_testcase extends PHPUnit_Framework_TestCase {
+    /** @var moodle_database connection to extra database */
+    private static $extradb = null;
+
+    /** @var moodle_database used in these tests*/
+    protected $tdb;
+
+    /**
+     * Constructs a test case with the given name.
+     *
+     * @param string $name
+     * @param array  $data
+     * @param string $dataName
+     */
+    final public function __construct($name = null, array $data = array(), $dataName = '') {
+        parent::__construct($name, $data, $dataName);
+
+        $this->setBackupGlobals(false);
+        $this->setBackupStaticAttributes(false);
+        $this->setRunTestInSeparateProcess(false);
+    }
+
+    public static function setUpBeforeClass() {
+        global $CFG;
+        parent::setUpBeforeClass();
+
+        if (!defined('PHPUNIT_TEST_DRIVER')) {
+            // use normal $DB
+            return;
+        }
+
+        if (!isset($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER])) {
+            throw new exception('Can not find driver configuration options with index: '.PHPUNIT_TEST_DRIVER);
+        }
+
+        $dblibrary = empty($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dblibrary']) ? 'native' : $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dblibrary'];
+        $dbtype = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbtype'];
+        $dbhost = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbhost'];
+        $dbname = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbname'];
+        $dbuser = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbuser'];
+        $dbpass = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbpass'];
+        $prefix = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['prefix'];
+        $dboptions = empty($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dboptions']) ? array() : $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dboptions'];
+
+        $classname = "{$dbtype}_{$dblibrary}_moodle_database";
+        require_once("$CFG->libdir/dml/$classname.php");
+        $d = new $classname();
+        if (!$d->driver_installed()) {
+            throw new exception('Database driver for '.$classname.' is not installed');
+        }
+
+        $d->connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
+
+        self::$extradb = $d;
+    }
+
+    protected function setUp() {
+        global $DB;
+        parent::setUp();
+
+        if (self::$extradb) {
+            $this->tdb = self::$extradb;
+        } else {
+            $this->tdb = $DB;
+        }
+    }
+
+    protected function tearDown() {
+        // delete all test tables
+        $dbman = $this->tdb->get_manager();
+        $tables = $this->tdb->get_tables(false);
+        foreach($tables as $tablename) {
+            if (strpos($tablename, 'test_table') === 0) {
+                $table = new xmldb_table($tablename);
+                $dbman->drop_table($table);
+            }
+        }
+        parent::tearDown();
+    }
+
+    public static function tearDownAfterClass() {
+        if (self::$extradb) {
+            self::$extradb->dispose();
+            self::$extradb = null;
+        }
+        phpunit_util::reset_all_data();
+        parent::tearDownAfterClass();
+    }
+}
diff --git a/lib/phpunit/classes/hint_resultprinter.php b/lib/phpunit/classes/hint_resultprinter.php
new file mode 100644 (file)
index 0000000..02a09f8
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Helper test listener.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * Helper test listener that prints command necessary
+ * for execution of failed test.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class Hint_ResultPrinter extends PHPUnit_TextUI_ResultPrinter {
+    protected function printDefectTrace(PHPUnit_Framework_TestFailure $defect) {
+        global $CFG;
+
+        parent::printDefectTrace($defect);
+
+        $failedTest = $defect->failedTest();
+        $testName = get_class($failedTest);
+
+        $exception = $defect->thrownException();
+        $trace = $exception->getTrace();
+        $file = null;
+        $dirroot = realpath($CFG->dirroot).DIRECTORY_SEPARATOR;
+        $classpath = realpath("$CFG->dirroot/lib/phpunit/classes").DIRECTORY_SEPARATOR;
+        foreach ($trace as $item) {
+            if (strpos($item['file'], $dirroot) === 0 and strpos($item['file'], $classpath) !== 0) {
+                $file = $item['file'];
+                break;
+            }
+        }
+        if ($file === null) {
+            return;
+        }
+
+        $cwd = getcwd();
+        if (strpos($file, $cwd) === 0) {
+            $file = substr($file, strlen($cwd)+1);
+        }
+
+        $this->write("\nTo re-run:\n phpunit $testName $file\n");
+    }
+}
diff --git a/lib/phpunit/classes/module_generator.php b/lib/phpunit/classes/module_generator.php
new file mode 100644 (file)
index 0000000..e684657
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Module generator base class.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * Module generator base class.
+ *
+ * Extend in mod/xxxx/tests/generator/lib.php as class mod_xxxx_generator.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class phpunit_module_generator {
+    /** @var phpunit_data_generator@var  */
+    protected $datagenerator;
+
+    /** @var number of created instances */
+    protected $instancecount = 0;
+
+    public function __construct(phpunit_data_generator $datagenerator) {
+        $this->datagenerator = $datagenerator;
+    }
+
+    /**
+     * To be called from data reset code only,
+     * do not use in tests.
+     * @return void
+     */
+    public function reset() {
+        $this->instancecount = 0;
+    }
+
+    /**
+     * Returns module name
+     * @return string name of module that this class describes
+     * @throws coding_exception if class invalid
+     */
+    public function get_modulename() {
+        $matches = null;
+        if (!preg_match('/^mod_([a-z0-9]+)_generator$/', get_class($this), $matches)) {
+            throw new coding_exception('Invalid module generator class name: '.get_class($this));
+        }
+
+        if (empty($matches[1])) {
+            throw new coding_exception('Invalid module generator class name: '.get_class($this));
+        }
+        return $matches[1];
+    }
+
+    /**
+     * Create course module and link it to course
+     * @param int $courseid
+     * @param array $options: section, visible
+     * @return int $cm instance id
+     */
+    protected function precreate_course_module($courseid, array $options) {
+        global $DB, $CFG;
+        require_once("$CFG->dirroot/course/lib.php");
+
+        $modulename = $this->get_modulename();
+
+        $cm = new stdClass();
+        $cm->course             = $courseid;
+        $cm->module             = $DB->get_field('modules', 'id', array('name'=>$modulename));
+        $cm->instance           = 0;
+        $cm->section            = isset($options['section']) ? $options['section'] : 0;
+        $cm->idnumber           = isset($options['idnumber']) ? $options['idnumber'] : 0;
+        $cm->added              = time();
+
+        $columns = $DB->get_columns('course_modules');
+        foreach ($options as $key=>$value) {
+            if ($key === 'id' or !isset($columns[$key])) {
+                continue;
+            }
+            if (property_exists($cm, $key)) {
+                continue;
+            }
+            $cm->$key = $value;
+        }
+
+        $cm->id = $DB->insert_record('course_modules', $cm);
+        $cm->coursemodule = $cm->id;
+
+        add_mod_to_section($cm);
+
+        return $cm->id;
+    }
+
+    /**
+     * Called after *_add_instance()
+     * @param int $id
+     * @param int $cmid
+     * @return stdClass module instance
+     */
+    protected function post_add_instance($id, $cmid) {
+        global $DB;
+
+        $DB->set_field('course_modules', 'instance', $id, array('id'=>$cmid));
+
+        $instance = $DB->get_record($this->get_modulename(), array('id'=>$id), '*', MUST_EXIST);
+
+        $cm = get_coursemodule_from_id($this->get_modulename(), $cmid, $instance->course, true, MUST_EXIST);
+        context_module::instance($cm->id);
+
+        $instance->cmid = $cm->id;
+
+        return $instance;
+    }
+
+    /**
+     * Create a test module
+     * @param array|stdClass $record
+     * @param array $options
+     * @return stdClass activity record
+     */
+    abstract public function create_instance($record = null, array $options = null);
+}
diff --git a/lib/phpunit/classes/unittestcase.php b/lib/phpunit/classes/unittestcase.php
new file mode 100644 (file)
index 0000000..6058cc6
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Legacy SimpleTest layer.
+ *
+ * @deprecated since 2.3
+ * @package    core
+ * @category   phpunit
+ * @author     Petr Skoda
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * Simplified emulation test case for legacy SimpleTest.
+ *
+ * Note: this is supposed to work for very simple tests only.
+ *
+ * @deprecated since 2.3
+ * @package    core
+ * @category   phpunit
+ * @author     Petr Skoda
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class UnitTestCase extends PHPUnit_Framework_TestCase {
+
+    /**
+     * @deprecated since 2.3
+     * @param bool $expected
+     * @param string $message
+     * @return void
+     */
+    public function expectException($expected, $message = '') {
+        // alternatively use phpdocs: @expectedException ExceptionClassName
+        if (!$expected) {
+            return;
+        }
+        $this->setExpectedException('moodle_exception', $message);
+    }
+
+    /**
+     * @deprecated since 2.3
+     * @param bool $expected
+     * @param string $message
+     * @return void
+     */
+    public function expectError($expected = false, $message = '') {
+        // alternatively use phpdocs: @expectedException PHPUnit_Framework_Error
+        if (!$expected) {
+            return;
+        }
+        $this->setExpectedException('PHPUnit_Framework_Error', $message);
+    }
+
+    /**
+     * @deprecated since 2.3
+     * @static
+     * @param mixed $actual
+     * @param string $messages
+     * @return void
+     */
+    public static function assertTrue($actual, $messages = '') {
+        parent::assertTrue((bool)$actual, $messages);
+    }
+
+    /**
+     * @deprecated since 2.3
+     * @static
+     * @param mixed $actual
+     * @param string $messages
+     * @return void
+     */
+    public static function assertFalse($actual, $messages = '') {
+        parent::assertFalse((bool)$actual, $messages);
+    }
+
+    /**
+     * @deprecated since 2.3
+     * @static
+     * @param mixed $expected
+     * @param mixed $actual
+     * @param string $message
+     * @return void
+     */
+    public static function assertEqual($expected, $actual, $message = '') {
+        parent::assertEquals($expected, $actual, $message);
+    }
+
+    /**
+     * @deprecated since 2.3
+     * @static
+     * @param mixed $expected
+     * @param mixed $actual
+     * @param float|int $margin
+     * @param string $message
+     * @return void
+     */
+    public static function assertWithinMargin($expected, $actual, $margin, $message = '') {
+        parent::assertEquals($expected, $actual, '', $margin, $message);
+    }
+
+    /**
+     * @deprecated since 2.3
+     * @static
+     * @param mixed $expected
+     * @param mixed $actual
+     * @param string $message
+     * @return void
+     */
+    public static function assertNotEqual($expected, $actual, $message = '') {
+        parent::assertNotEquals($expected, $actual, $message);
+    }
+
+    /**
+     * @deprecated since 2.3
+     * @static
+     * @param mixed $expected
+     * @param mixed $actual
+     * @param string $message
+     * @return void
+     */
+    public static function assertIdentical($expected, $actual, $message = '') {
+        parent::assertSame($expected, $actual, $message);
+    }
+
+    /**
+     * @deprecated since 2.3
+     * @static
+     * @param mixed $expected
+     * @param mixed $actual
+     * @param string $message
+     * @return void
+     */
+    public static function assertNotIdentical($expected, $actual, $message = '') {
+        parent::assertNotSame($expected, $actual, $message);
+    }
+
+    /**
+     * @deprecated since 2.3
+     * @static
+     * @param mixed $actual
+     * @param mixed $expected
+     * @param string $message
+     * @return void
+     */
+    public static function assertIsA($actual, $expected, $message = '') {
+        if ($expected === 'array') {
+            parent::assertEquals('array', gettype($actual), $message);
+        } else {
+            parent::assertInstanceOf($expected, $actual, $message);
+        }
+    }
+
+    /**
+     * @deprecated since 2.3
+     * @static
+     * @param mixed $pattern
+     * @param mixed $string
+     * @param string $message
+     * @return void
+     */
+    public static function assertPattern($pattern, $string, $message = '') {
+        parent::assertRegExp($pattern, $string, $message);
+    }
+
+    /**
+     * @deprecated since 2.3
+     * @static
+     * @param mixed $pattern
+     * @param mixed $string
+     * @param string $message
+     * @return void
+     */
+    public static function assertNotPattern($pattern, $string, $message = '') {
+        parent::assertNotRegExp($pattern, $string, $message);
+    }
+}
diff --git a/lib/phpunit/classes/util.php b/lib/phpunit/classes/util.php
new file mode 100644 (file)
index 0000000..a9c54f4
--- /dev/null
@@ -0,0 +1,1135 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Utility class.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * Collection of utility methods.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class phpunit_util {
+    /** @var string current version hash from php files */
+    protected static $versionhash = null;
+
+    /** @var array original content of all database tables*/
+    protected static $tabledata = null;
+
+    /** @var array original structure of all database tables */
+    protected static $tablestructure = null;
+
+    /** @var array original structure of all database tables */
+    protected static $sequencenames = null;
+
+    /** @var array An array of original globals, restored after each test */
+    protected static $globals = array();
+
+    /** @var int last value of db writes counter, used for db resetting */
+    public static $lastdbwrites = null;
+
+    /** @var phpunit_data_generator */
+    protected static $generator = null;
+
+    /** @var resource used for prevention of parallel test execution */
+    protected static $lockhandle = null;
+
+    /**
+     * Prevent parallel test execution - this can not work in Moodle because we modify database and dataroot.
+     *
+     * Note: do not call manually!
+     *
+     * @internal
+     * @static
+     * @return void
+     */
+    public static function acquire_test_lock() {
+        global $CFG;
+        if (!file_exists("$CFG->phpunit_dataroot/phpunit")) {
+            // dataroot not initialised yet
+            return;
+        }
+        if (!file_exists("$CFG->phpunit_dataroot/phpunit/lock")) {
+            file_put_contents("$CFG->phpunit_dataroot/phpunit/lock", 'This file prevents concurrent execution of Moodle PHPUnit tests');
+            phpunit_boostrap_fix_file_permissions("$CFG->phpunit_dataroot/phpunit/lock");
+        }
+        if (self::$lockhandle = fopen("$CFG->phpunit_dataroot/phpunit/lock", 'r')) {
+            $wouldblock = null;
+            $locked = flock(self::$lockhandle, (LOCK_EX | LOCK_NB), $wouldblock);
+            if (!$locked) {
+                if ($wouldblock) {
+                    echo "Waiting for other test execution to complete...\n";
+                }
+                $locked = flock(self::$lockhandle, LOCK_EX);
+            }
+            if (!$locked) {
+                fclose(self::$lockhandle);
+                self::$lockhandle = null;
+            }
+        }
+        register_shutdown_function(array('phpunit_util', 'release_test_lock'));
+    }
+
+    /**
+     * Note: do not call manually!
+     * @internal
+     * @static
+     * @return void
+     */
+    public static function release_test_lock() {
+        if (self::$lockhandle) {
+            flock(self::$lockhandle, LOCK_UN);
+            fclose(self::$lockhandle);
+            self::$lockhandle = null;
+        }
+    }
+
+    /**
+     * Load global $CFG;
+     * @internal
+     * @static
+     * @return void
+     */
+    public static function initialise_cfg() {
+        global $DB;
+        $dbhash = false;
+        try {
+            $dbhash = $DB->get_field('config', 'value', array('name'=>'phpunittest'));
+        } catch (Exception $e) {
+            // not installed yet
+            initialise_cfg();
+            return;
+        }
+        if ($dbhash !== phpunit_util::get_version_hash()) {
+            // do not set CFG - the only way forward is to drop and reinstall
+            return;
+        }
+        // standard CFG init
+        initialise_cfg();
+    }
+
+    /**
+     * Get data generator
+     * @static
+     * @return phpunit_data_generator
+     */
+    public static function get_data_generator() {
+        if (is_null(self::$generator)) {
+            require_once(__DIR__.'/../generatorlib.php');
+            self::$generator = new phpunit_data_generator();
+        }
+        return self::$generator;
+    }
+
+    /**
+     * Returns contents of all tables right after installation.
+     * @static
+     * @return array $table=>$records
+     */
+    protected static function get_tabledata() {
+        global $CFG;
+
+        if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
+            // not initialised yet
+            return array();
+        }
+
+        if (!isset(self::$tabledata)) {
+            $data = file_get_contents("$CFG->dataroot/phpunit/tabledata.ser");
+            self::$tabledata = unserialize($data);
+        }
+
+        if (!is_array(self::$tabledata)) {
+            phpunit_bootstrap_error(1, 'Can not read dataroot/phpunit/tabledata.ser or invalid format, reinitialize test database.');
+        }
+
+        return self::$tabledata;
+    }
+
+    /**
+     * Returns structure of all tables right after installation.
+     * @static
+     * @return array $table=>$records
+     */
+    public static function get_tablestructure() {
+        global $CFG;
+
+        if (!file_exists("$CFG->dataroot/phpunit/tablestructure.ser")) {
+            // not initialised yet
+            return array();
+        }
+
+        if (!isset(self::$tablestructure)) {
+            $data = file_get_contents("$CFG->dataroot/phpunit/tablestructure.ser");
+            self::$tablestructure = unserialize($data);
+        }
+
+        if (!is_array(self::$tablestructure)) {
+            phpunit_bootstrap_error(1, 'Can not read dataroot/phpunit/tablestructure.ser or invalid format, reinitialize test database.');
+        }
+
+        return self::$tablestructure;
+    }
+
+    /**
+     * Returns the names of sequences for each autoincrementing id field in all standard tables.
+     * @static
+     * @return array $table=>$sequencename
+     */
+    public static function get_sequencenames() {
+        global $DB;
+
+        if (isset(self::$sequencenames)) {
+            return self::$sequencenames;
+        }
+
+        if (!$structure = self::get_tablestructure()) {
+            return array();
+        }
+
+        self::$sequencenames = array();
+        foreach ($structure as $table=>$ignored) {
+            $name = $DB->get_manager()->generator->getSequenceFromDB(new xmldb_table($table));
+            if ($name !== false) {
+                self::$sequencenames[$table] = $name;
+            }
+        }
+
+        return self::$sequencenames;
+    }
+
+    /**
+     * Returns list of tables that are unmodified and empty.
+     *
+     * @static
+     * @return array of table names, empty if unknown
+     */
+    protected static function guess_unmodified_empty_tables() {
+        global $DB;
+
+        $dbfamily = $DB->get_dbfamily();
+
+        if ($dbfamily === 'mysql') {
+            $empties = array();
+            $prefix = $DB->get_prefix();
+            $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
+            foreach ($rs as $info) {
+                $table = strtolower($info->name);
+                if (strpos($table, $prefix) !== 0) {
+                    // incorrect table match caused by _
+                    continue;
+                }
+                if (!is_null($info->auto_increment)) {
+                    $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
+                    if ($info->auto_increment == 1) {
+                        $empties[$table] = $table;
+                    }
+                }
+            }
+            $rs->close();
+            return $empties;
+
+        } else if ($dbfamily === 'mssql') {
+            $empties = array();
+            $prefix = $DB->get_prefix();
+            $sql = "SELECT t.name
+                      FROM sys.identity_columns i
+                      JOIN sys.tables t ON t.object_id = i.object_id
+                     WHERE t.name LIKE ?
+                       AND i.name = 'id'
+                       AND i.last_value IS NULL";
+            $rs = $DB->get_recordset_sql($sql, array($prefix.'%'));
+            foreach ($rs as $info) {
+                $table = strtolower($info->name);
+                if (strpos($table, $prefix) !== 0) {
+                    // incorrect table match caused by _
+                    continue;
+                }
+                $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
+                $empties[$table] = $table;
+            }
+            $rs->close();
+            return $empties;
+
+        } else if ($dbfamily === 'oracle') {
+            $sequences = phpunit_util::get_sequencenames();
+            $sequences = array_map('strtoupper', $sequences);
+            $lookup = array_flip($sequences);
+            $empties = array();
+            list($seqs, $params) = $DB->get_in_or_equal($sequences);
+            $sql = "SELECT sequence_name FROM user_sequences WHERE last_number = 1 AND sequence_name $seqs";
+            $rs = $DB->get_recordset_sql($sql, $params);
+            foreach ($rs as $seq) {
+                $table = $lookup[$seq->sequence_name];
+                $empties[$table] = $table;
+            }
+            $rs->close();
+            return $empties;
+
+        } else {
+            return array();
+        }
+    }
+
+    /**
+     * Reset all database sequences to initial values.
+     *
+     * @static
+     * @param array $empties tables that are known to be unmodified and empty
+     * @return void
+     */
+    public static function reset_all_database_sequences(array $empties = null) {
+        global $DB;
+
+        if (!$data = self::get_tabledata()) {
+            // not initialised yet
+            return;
+        }
+        if (!$structure = self::get_tablestructure()) {
+            // not initialised yet
+            return;
+        }
+
+        $dbfamily = $DB->get_dbfamily();
+        if ($dbfamily === 'postgres') {
+            $queries = array();
+            $prefix = $DB->get_prefix();
+            foreach ($data as $table=>$records) {
+                if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
+                    if (empty($records)) {
+                        $nextid = 1;
+                    } else {
+                        $lastrecord = end($records);
+                        $nextid = $lastrecord->id + 1;
+                    }
+                    $queries[] = "ALTER SEQUENCE {$prefix}{$table}_id_seq RESTART WITH $nextid";
+                }
+            }
+            if ($queries) {
+                $DB->change_database_structure(implode(';', $queries));
+            }
+
+        } else if ($dbfamily === 'mysql') {
+            $sequences = array();
+            $prefix = $DB->get_prefix();
+            $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
+            foreach ($rs as $info) {
+                $table = strtolower($info->name);
+                if (strpos($table, $prefix) !== 0) {
+                    // incorrect table match caused by _
+                    continue;
+                }
+                if (!is_null($info->auto_increment)) {
+                    $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
+                    $sequences[$table] = $info->auto_increment;
+                }
+            }
+            $rs->close();
+            $prefix = $DB->get_prefix();
+            foreach ($data as $table=>$records) {
+                if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
+                    if (isset($sequences[$table])) {
+                        if (empty($records)) {
+                            $nextid = 1;
+                        } else {
+                            $lastrecord = end($records);
+                            $nextid = $lastrecord->id + 1;
+                        }
+                        if ($sequences[$table] != $nextid) {
+                            $DB->change_database_structure("ALTER TABLE {$prefix}{$table} AUTO_INCREMENT = $nextid");
+                        }
+
+                    } else {
+                        // some problem exists, fallback to standard code
+                        $DB->get_manager()->reset_sequence($table);
+                    }
+                }
+            }
+
+        } else if ($dbfamily === 'oracle') {
+            $sequences = phpunit_util::get_sequencenames();
+            $sequences = array_map('strtoupper', $sequences);
+            $lookup = array_flip($sequences);
+
+            $current = array();
+            list($seqs, $params) = $DB->get_in_or_equal($sequences);
+            $sql = "SELECT sequence_name, last_number FROM user_sequences WHERE sequence_name $seqs";
+            $rs = $DB->get_recordset_sql($sql, $params);
+            foreach ($rs as $seq) {
+                $table = $lookup[$seq->sequence_name];
+                $current[$table] = $seq->last_number;
+            }
+            $rs->close();
+
+            foreach ($data as $table=>$records) {
+                if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
+                    $lastrecord = end($records);
+                    if ($lastrecord) {
+                        $nextid = $lastrecord->id + 1;
+                    } else {
+                        $nextid = 1;
+                    }
+                    if (!isset($current[$table])) {
+                        $DB->get_manager()->reset_sequence($table);
+                    } else if ($nextid == $current[$table]) {
+                        continue;
+                    }
+                    // reset as fast as possible - alternatively we could use http://stackoverflow.com/questions/51470/how-do-i-reset-a-sequence-in-oracle
+                    $seqname = $sequences[$table];
+                    $cachesize = $DB->get_manager()->generator->sequence_cache_size;
+                    $DB->change_database_structure("DROP SEQUENCE $seqname");
+                    $DB->change_database_structure("CREATE SEQUENCE $seqname START WITH $nextid INCREMENT BY 1 NOMAXVALUE CACHE $cachesize");
+                }
+            }
+
+        } else {
+            // note: does mssql support any kind of faster reset?
+            if (is_null($empties)) {
+                $empties = self::guess_unmodified_empty_tables();
+            }
+            foreach ($data as $table=>$records) {
+                if (isset($empties[$table])) {
+                    continue;
+                }
+                if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
+                    $DB->get_manager()->reset_sequence($table);
+                }
+            }
+        }
+    }
+
+    /**
+     * Reset all database tables to default values.
+     * @static
+     * @return bool true if reset done, false if skipped
+     */
+    public static function reset_database() {
+        global $DB;
+
+        if (!is_null(self::$lastdbwrites) and self::$lastdbwrites == $DB->perf_get_writes()) {
+            return false;
+        }
+
+        $tables = $DB->get_tables(false);
+        if (!$tables or empty($tables['config'])) {
+            // not installed yet
+            return false;
+        }
+
+        if (!$data = self::get_tabledata()) {
+            // not initialised yet
+            return false;
+        }
+        if (!$structure = self::get_tablestructure()) {
+            // not initialised yet
+            return false;
+        }
+
+        $empties = self::guess_unmodified_empty_tables();
+
+        foreach ($data as $table=>$records) {
+            if (empty($records)) {
+                if (isset($empties[$table])) {
+                    // table was not modified and is empty
+                } else {
+                    $DB->delete_records($table, array());
+                }
+                continue;
+            }
+
+            if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
+                $currentrecords = $DB->get_records($table, array(), 'id ASC');
+                $changed = false;
+                foreach ($records as $id=>$record) {
+                    if (!isset($currentrecords[$id])) {
+                        $changed = true;
+                        break;
+                    }
+                    if ((array)$record != (array)$currentrecords[$id]) {
+                        $changed = true;
+                        break;
+                    }
+                    unset($currentrecords[$id]);
+                }
+                if (!$changed) {
+                    if ($currentrecords) {
+                        $lastrecord = end($records);
+                        $DB->delete_records_select($table, "id > ?", array($lastrecord->id));
+                        continue;
+                    } else {
+                        continue;
+                    }
+                }
+            }
+
+            $DB->delete_records($table, array());
+            foreach ($records as $record) {
+                $DB->import_record($table, $record, false, true);
+            }
+        }
+
+        // reset all next record ids - aka sequences
+        self::reset_all_database_sequences($empties);
+
+        // remove extra tables
+        foreach ($tables as $table) {
+            if (!isset($data[$table])) {
+                $DB->get_manager()->drop_table(new xmldb_table($table));
+            }
+        }
+
+        self::$lastdbwrites = $DB->perf_get_writes();
+
+        return true;
+    }
+
+    /**
+     * Purge dataroot directory
+     * @static
+     * @return void
+     */
+    public static function reset_dataroot() {
+        global $CFG;
+
+        $handle = opendir($CFG->dataroot);
+        $skip = array('.', '..', 'phpunittestdir.txt', 'phpunit', '.htaccess');
+        while (false !== ($item = readdir($handle))) {
+            if (in_array($item, $skip)) {
+                continue;
+            }
+            if (is_dir("$CFG->dataroot/$item")) {
+                remove_dir("$CFG->dataroot/$item", false);
+            } else {
+                unlink("$CFG->dataroot/$item");
+            }
+        }
+        closedir($handle);
+        make_temp_directory('');
+        make_cache_directory('');
+        make_cache_directory('htmlpurifier');
+    }
+
+    /**
+     * Reset contents of all database tables to initial values, reset caches, etc.
+     *
+     * Note: this is relatively slow (cca 2 seconds for pg and 7 for mysql) - please use with care!
+     *
+     * @static
+     * @param bool $logchanges log changes in global state and database in error log
+     * @return void
+     */
+    public static function reset_all_data($logchanges = false) {
+        global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION;
+
+        // reset global $DB in case somebody mocked it
+        $DB = self::get_global_backup('DB');
+
+        if ($DB->is_transaction_started()) {
+            // we can not reset inside transaction
+            $DB->force_transaction_rollback();
+        }
+
+        $resetdb = self::reset_database();
+        $warnings = array();
+
+        if ($logchanges) {
+            if ($resetdb) {
+                $warnings[] = 'Warning: unexpected database modification, resetting DB state';
+            }
+
+            $oldcfg = self::get_global_backup('CFG');
+            $oldsite = self::get_global_backup('SITE');
+            foreach($CFG as $k=>$v) {
+                if (!property_exists($oldcfg, $k)) {
+                    $warnings[] = 'Warning: unexpected new $CFG->'.$k.' value';
+                } else if ($oldcfg->$k !== $CFG->$k) {
+                    $warnings[] = 'Warning: unexpected change of $CFG->'.$k.' value';
+                }
+                unset($oldcfg->$k);
+
+            }
+            if ($oldcfg) {
+                foreach($oldcfg as $k=>$v) {
+                    $warnings[] = 'Warning: unexpected removal of $CFG->'.$k;
+                }
+            }
+
+            if ($USER->id != 0) {
+                $warnings[] = 'Warning: unexpected change of $USER';
+            }
+
+            if ($COURSE->id != $oldsite->id) {
+                $warnings[] = 'Warning: unexpected change of $COURSE';
+            }
+        }
+
+        // restore original globals
+        $_SERVER = self::get_global_backup('_SERVER');
+        $CFG = self::get_global_backup('CFG');
+        $SITE = self::get_global_backup('SITE');
+        $COURSE = $SITE;
+
+        // reinitialise following globals
+        $OUTPUT = new bootstrap_renderer();
+        $PAGE = new moodle_page();
+        $FULLME = null;
+        $ME = null;
+        $SCRIPT = null;
+        $SESSION = new stdClass();
+        $_SESSION['SESSION'] =& $SESSION;
+
+        // set fresh new not-logged-in user
+        $user = new stdClass();
+        $user->id = 0;
+        $user->mnethostid = $CFG->mnet_localhost_id;
+        session_set_user($user);
+
+        // reset all static caches
+        accesslib_clear_all_caches(true);
+        get_string_manager()->reset_caches();
+        events_get_handlers('reset');
+        textlib::reset_caches();
+        //TODO: add more resets here and probably refactor them to new core function
+
+        // purge dataroot directory
+        self::reset_dataroot();
+
+        // restore original config once more in case resetting of caches changed CFG
+        $CFG = self::get_global_backup('CFG');
+
+        // inform data generator
+        self::get_data_generator()->reset();
+
+        // fix PHP settings
+        error_reporting($CFG->debug);
+
+        // verify db writes just in case something goes wrong in reset
+        if (self::$lastdbwrites != $DB->perf_get_writes()) {
+            error_log('Unexpected DB writes in phpunit_util::reset_all_data()');
+            self::$lastdbwrites = $DB->perf_get_writes();
+        }
+
+        if ($warnings) {
+            $warnings = implode("\n", $warnings);
+            trigger_error($warnings, E_USER_WARNING);
+        }
+    }
+
+    /**
+     * Called during bootstrap only!
+     * @internal
+     * @static
+     * @return void
+     */
+    public static function bootstrap_init() {
+        global $CFG, $SITE, $DB;
+
+        // backup the globals
+        self::$globals['_SERVER'] = $_SERVER;
+        self::$globals['CFG'] = clone($CFG);
+        self::$globals['SITE'] = clone($SITE);
+        self::$globals['DB'] = $DB;
+
+        // refresh data in all tables, clear caches, etc.
+        phpunit_util::reset_all_data();
+    }
+
+    /**
+     * Returns original state of global variable.
+     * @static
+     * @param string $name
+     * @return mixed
+     */
+    public static function get_global_backup($name) {
+        if ($name === 'DB') {
+            // no cloning of database object,
+            // we just need the original reference, not original state
+            return self::$globals['DB'];
+        }
+        if (isset(self::$globals[$name])) {
+            if (is_object(self::$globals[$name])) {
+                $return = clone(self::$globals[$name]);
+                return $return;
+            } else {
+                return self::$globals[$name];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Does this site (db and dataroot) appear to be used for production?
+     * We try very hard to prevent accidental damage done to production servers!!
+     *
+     * @static
+     * @return bool
+     */
+    public static function is_test_site() {
+        global $DB, $CFG;
+
+        if (!file_exists("$CFG->dataroot/phpunittestdir.txt")) {
+            // this is already tested in bootstrap script,
+            // but anyway presence of this file means the dataroot is for testing
+            return false;
+        }
+
+        $tables = $DB->get_tables(false);
+        if ($tables) {
+            if (!$DB->get_manager()->table_exists('config')) {
+                return false;
+            }
+            if (!get_config('core', 'phpunittest')) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Is this site initialised to run unit tests?
+     *
+     * @static
+     * @return int array errorcode=>message, 0 means ok
+     */
+    public static function testing_ready_problem() {
+        global $CFG, $DB;
+
+        $tables = $DB->get_tables(false);
+
+        if (!self::is_test_site()) {
+            // dataroot was verified in bootstrap, so it must be DB
+            return array(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not use database for testing, try different prefix');
+        }
+
+        if (empty($tables)) {
+            return array(PHPUNIT_EXITCODE_INSTALL, '');
+        }
+
+        if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser") or !file_exists("$CFG->dataroot/phpunit/tablestructure.ser")) {
+            return array(PHPUNIT_EXITCODE_REINSTALL, '');
+        }
+
+        if (!file_exists("$CFG->dataroot/phpunit/versionshash.txt")) {
+            return array(PHPUNIT_EXITCODE_REINSTALL, '');
+        }
+
+        $hash = phpunit_util::get_version_hash();
+        $oldhash = file_get_contents("$CFG->dataroot/phpunit/versionshash.txt");
+
+        if ($hash !== $oldhash) {
+            return array(PHPUNIT_EXITCODE_REINSTALL, '');
+        }
+
+        $dbhash = get_config('core', 'phpunittest');
+        if ($hash !== $dbhash) {
+            return array(PHPUNIT_EXITCODE_REINSTALL, '');
+        }
+
+        return array(0, '');
+    }
+
+    /**
+     * Drop all test site data.
+     *
+     * Note: To be used from CLI scripts only.
+     *
+     * @static
+     * @return void may terminate execution with exit code
+     */
+    public static function drop_site() {
+        global $DB, $CFG;
+
+        if (!self::is_test_site()) {
+            phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not drop non-test site!!');
+        }
+
+        // purge dataroot
+        self::reset_dataroot();
+        phpunit_bootstrap_initdataroot($CFG->dataroot);
+        $keep = array('.', '..', 'lock', 'webrunner.xml');
+        $files = scandir("$CFG->dataroot/phpunit");
+        foreach ($files as $file) {
+            if (in_array($file, $keep)) {
+                continue;
+            }
+            $path = "$CFG->dataroot/phpunit/$file";
+            if (is_dir($path)) {
+                remove_dir($path, false);
+            } else {
+                unlink($path);
+            }
+        }
+
+        // drop all tables
+        $tables = $DB->get_tables(false);
+        if (isset($tables['config'])) {
+            // config always last to prevent problems with interrupted drops!
+            unset($tables['config']);
+            $tables['config'] = 'config';
+        }
+        foreach ($tables as $tablename) {
+            $table = new xmldb_table($tablename);
+            $DB->get_manager()->drop_table($table);
+        }
+    }
+
+    /**
+     * Perform a fresh test site installation
+     *
+     * Note: To be used from CLI scripts only.
+     *
+     * @static
+     * @return void may terminate execution with exit code
+     */
+    public static function install_site() {
+        global $DB, $CFG;
+
+        if (!self::is_test_site()) {
+            phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not install on non-test site!!');
+        }
+
+        if ($DB->get_tables()) {
+            list($errorcode, $message) = phpunit_util::testing_ready_problem();
+            if ($errorcode) {
+                phpunit_bootstrap_error(PHPUNIT_EXITCODE_REINSTALL, 'Database tables already present, Moodle PHPUnit test environment can not be initialised');
+            } else {
+                phpunit_bootstrap_error(0, 'Moodle PHPUnit test environment is already initialised');
+            }
+        }
+
+        $options = array();
+        $options['adminpass'] = 'admin';
+        $options['shortname'] = 'phpunit';
+        $options['fullname'] = 'PHPUnit test site';
+
+        install_cli_database($options, false);
+
+        // install timezone info
+        $timezones = get_records_csv($CFG->libdir.'/timezone.txt', 'timezone');
+        update_timezone_records($timezones);
+
+        // add test db flag
+        $hash = phpunit_util::get_version_hash();
+        set_config('phpunittest', $hash);
+
+        // store data for all tables
+        $data = array();
+        $structure = array();
+        $tables = $DB->get_tables();
+        foreach ($tables as $table) {
+            $columns = $DB->get_columns($table);
+            $structure[$table] = $columns;
+            if (isset($columns['id']) and $columns['id']->auto_increment) {
+                $data[$table] = $DB->get_records($table, array(), 'id ASC');
+            } else {
+                // there should not be many of these
+                $data[$table] = $DB->get_records($table, array());
+            }
+        }
+        $data = serialize($data);
+        file_put_contents("$CFG->dataroot/phpunit/tabledata.ser", $data);
+        phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/tabledata.ser");
+
+        $structure = serialize($structure);
+        file_put_contents("$CFG->dataroot/phpunit/tablestructure.ser", $structure);
+        phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/tablestructure.ser");
+
+        // hash all plugin versions - helps with very fast detection of db structure changes
+        file_put_contents("$CFG->dataroot/phpunit/versionshash.txt", $hash);
+        phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/versionshash.txt", $hash);
+    }
+
+    /**
+     * Calculate unique version hash for all plugins and core.
+     * @static
+     * @return string sha1 hash
+     */
+    public static function get_version_hash() {
+        global $CFG;
+
+        if (self::$versionhash) {
+            return self::$versionhash;
+        }
+
+        $versions = array();
+
+        // main version first
+        $version = null;
+        include($CFG->dirroot.'/version.php');
+        $versions['core'] = $version;
+
+        // modules
+        $mods = get_plugin_list('mod');
+        ksort($mods);
+        foreach ($mods as $mod => $fullmod) {
+            $module = new stdClass();
+            $module->version = null;
+            include($fullmod.'/version.php');
+            $versions[$mod] = $module->version;
+        }
+
+        // now the rest of plugins
+        $plugintypes = get_plugin_types();
+        unset($plugintypes['mod']);
+        ksort($plugintypes);
+        foreach ($plugintypes as $type=>$unused) {
+            $plugs = get_plugin_list($type);
+            ksort($plugs);
+            foreach ($plugs as $plug=>$fullplug) {
+                $plugin = new stdClass();
+                $plugin->version = null;
+                @include($fullplug.'/version.php');
+                $versions[$plug] = $plugin->version;
+            }
+        }
+
+        self::$versionhash = sha1(serialize($versions));
+
+        return self::$versionhash;
+    }
+
+    /**
+     * Builds dirroot/phpunit.xml and dataroot/phpunit/webrunner.xml files using defaults from /phpunit.xml.dist
+     * @static
+     * @return bool true means main config file created, false means only dataroot file created
+     */
+    public static function build_config_file() {
+        global $CFG;
+
+        $template = '
+        <testsuite name="@component@">
+            <directory suffix="_test.php">@dir@</directory>
+        </testsuite>';
+        $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
+
+        $suites = '';
+
+        $plugintypes = get_plugin_types();
+        ksort($plugintypes);
+        foreach ($plugintypes as $type=>$unused) {
+            $plugs = get_plugin_list($type);
+            ksort($plugs);
+            foreach ($plugs as $plug=>$fullplug) {
+                if (!file_exists("$fullplug/tests/")) {
+                    continue;
+                }
+                $dir = substr($fullplug, strlen($CFG->dirroot)+1);
+                $dir .= '/tests';
+                $component = $type.'_'.$plug;
+
+                $suite = str_replace('@component@', $component, $template);
+                $suite = str_replace('@dir@', $dir, $suite);
+
+                $suites .= $suite;
+            }
+        }
+
+        $data = preg_replace('|<!--@plugin_suites_start@-->.*<!--@plugin_suites_end@-->|s', $suites, $data, 1);
+
+        $result = false;
+        if (is_writable($CFG->dirroot)) {
+            if ($result = file_put_contents("$CFG->dirroot/phpunit.xml", $data)) {
+                phpunit_boostrap_fix_file_permissions("$CFG->dirroot/phpunit.xml");
+            }
+        }
+
+        // relink - it seems that xml:base does not work in phpunit xml files, remove this nasty hack if you find a way to set xml base for relative refs
+        $data = str_replace('lib/phpunit/', $CFG->dirroot.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'phpunit'.DIRECTORY_SEPARATOR, $data);
+        $data = preg_replace('|<directory suffix="_test.php">([^<]+)</directory>|',
+            '<directory suffix="_test.php">'.$CFG->dirroot.(DIRECTORY_SEPARATOR === '\\' ? '\\\\' : DIRECTORY_SEPARATOR).'$1</directory>',
+            $data);
+        file_put_contents("$CFG->dataroot/phpunit/webrunner.xml", $data);
+        phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/webrunner.xml");
+
+        return (bool)$result;
+    }
+
+    /**
+     * Builds phpunit.xml files for all components using defaults from /phpunit.xml.dist
+     *
+     * @static
+     * @return void, stops if can not write files
+     */
+    public static function build_component_config_files() {
+        global $CFG;
+
+        $template = '
+        <testsuites>
+            <testsuite name="@component@">
+                <directory suffix="_test.php">.</directory>
+            </testsuite>
+        </testsuites>';
+
+        // Use the upstream file as source for the distributed configurations
+        $ftemplate = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
+        $ftemplate = preg_replace('|<!--All core suites.*</testsuites>|s', '<!--@component_suite@-->', $ftemplate);
+
+        // Get all the components
+        $components = self::get_all_plugins_with_tests() + self::get_all_subsystems_with_tests();
+
+        // Get all the directories having tests
+        $directories = self::get_all_directories_with_tests();
+
+        // Find any directory not covered by proper components
+        $remaining = array_diff($directories, $components);
+
+        // Add them to the list of components
+        $components += $remaining;
+
+        // Create the corresponding phpunit.xml file for each component
+        foreach ($components as $cname => $cpath) {
+            // Calculate the component suite
+            $ctemplate = $template;
+            $ctemplate = str_replace('@component@', $cname, $ctemplate);
+
+            // Apply it to the file template
+            $fcontents = str_replace('<!--@component_suite@-->', $ctemplate, $ftemplate);
+
+            // fix link to schema
+            $level = substr_count(str_replace('\\', '/', $cpath), '/') - substr_count(str_replace('\\', '/', $CFG->dirroot), '/');
+            $fcontents = str_replace('lib/phpunit/', str_repeat('../', $level).'lib/phpunit/', $fcontents);
+
+            // Write the file
+            $result = false;
+            if (is_writable($cpath)) {
+                if ($result = (bool)file_put_contents("$cpath/phpunit.xml", $fcontents)) {
+                    phpunit_boostrap_fix_file_permissions("$cpath/phpunit.xml");
+                }
+            }
+            // Problems writing file, throw error
+            if (!$result) {
+                phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGWARNING, "Can not create $cpath/phpunit.xml configuration file, verify dir permissions");
+            }
+        }
+    }
+
+    /**
+     * Returns all the plugins having PHPUnit tests
+     *
+     * @return array all the plugins having PHPUnit tests
+     *
+     */
+    private static function get_all_plugins_with_tests() {
+        $pluginswithtests = array();
+
+        $plugintypes = get_plugin_types();
+        ksort($plugintypes);
+        foreach ($plugintypes as $type => $unused) {
+            $plugs = get_plugin_list($type);
+            ksort($plugs);
+            foreach ($plugs as $plug => $fullplug) {
+                // Look for tests recursively
+                if (self::directory_has_tests($fullplug)) {
+                    $pluginswithtests[$type . '_' . $plug] = $fullplug;
+                }
+            }
+        }
+        return $pluginswithtests;
+    }
+
+    /**
+     * Returns all the subsystems having PHPUnit tests
+     *
+     * Note we are hacking here the list of subsystems
+     * to cover some well-known subsystems that are not properly
+     * returned by the {@link get_core_subsystems()} function.
+     *
+     * @return array all the subsystems having PHPUnit tests
+     */
+    private static function get_all_subsystems_with_tests() {
+        global $CFG;
+
+        $subsystemswithtests = array();
+
+        $subsystems = get_core_subsystems();
+
+        // Hack the list a bit to cover some well-known ones
+        $subsystems['backup'] = 'backup';
+        $subsystems['db-dml'] = 'lib/dml';
+        $subsystems['db-ddl'] = 'lib/ddl';
+
+        ksort($subsystems);
+        foreach ($subsystems as $subsys => $relsubsys) {
+            if ($relsubsys === null) {
+                continue;
+            }
+            $fullsubsys = $CFG->dirroot . '/' . $relsubsys;
+            if (!is_dir($fullsubsys)) {
+                continue;
+            }
+            // Look for tests recursively
+            if (self::directory_has_tests($fullsubsys)) {
+                $subsystemswithtests['core_' . $subsys] = $fullsubsys;
+            }
+        }
+        return $subsystemswithtests;
+    }
+
+    /**
+     * Returns all the directories having tests
+     *
+     * @return array all directories having tests
+     */
+    private static function get_all_directories_with_tests() {
+        global $CFG;
+
+        $dirs = array();
+        $dirite = new RecursiveDirectoryIterator($CFG->dirroot);
+        $iteite = new RecursiveIteratorIterator($dirite);
+        $sep = preg_quote(DIRECTORY_SEPARATOR, '|');
+        $regite = new RegexIterator($iteite, '|'.$sep.'tests'.$sep.'.*_test\.php$|');
+        foreach ($regite as $path => $element) {
+            $key = dirname(dirname($path));
+            $value = trim(str_replace('/', '_', str_replace($CFG->dirroot, '', $key)), '_');
+            $dirs[$key] = $value;
+        }
+        ksort($dirs);
+        return array_flip($dirs);
+    }
+
+    /**
+     * Returns if a given directory has tests (recursively)
+     *
+     * @param $dir string full path to the directory to look for phpunit tests
+     * @return bool if a given directory has tests (true) or no (false)
+     */
+    private static function directory_has_tests($dir) {
+        if (!is_dir($dir)) {
+            return false;
+        }
+
+        $dirite = new RecursiveDirectoryIterator($dir);
+        $iteite = new RecursiveIteratorIterator($dirite);
+        $sep = preg_quote(DIRECTORY_SEPARATOR, '|');
+        $regite = new RegexIterator($iteite, '|'.$sep.'tests'.$sep.'.*_test\.php$|');
+        $regite->rewind();
+        if ($regite->valid()) {
+            return true;
+        }
+        return false;
+    }
+}
index 2243342..f6c8897 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * PHPUnit data generator class
+ * PHPUnit data generator support
  *
  * @package    core
  * @category   phpunit
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-defined('MOODLE_INTERNAL') || die();
+// NOTE: MOODLE_INTERNAL is not verified here because we load this before setup.php!
 
+require_once(__DIR__.'/classes/data_generator.php');
+require_once(__DIR__.'/classes/module_generator.php');
+require_once(__DIR__.'/classes/block_generator.php');
 
-/**
- * Data generator for unit tests
- *
- * @package    core
- * @category   phpunit
- * @copyright  2012 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class phpunit_data_generator {
-    protected $usercounter = 0;
-    protected $categorycount = 0;
-    protected $coursecount = 0;
-    protected $scalecount = 0;
-    protected $groupcount = 0;
-    protected $groupingcount = 0;
-
-    /** @var array list of plugin generators */
-    protected $generators = array();
-
-    /** @var array lis of common last names */
-    public $lastnames = array(
-        'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Davis', 'García', 'Rodríguez', 'Wilson',
-        'Müller', 'Schmidt', 'Schneider', 'Fischer', 'Meyer', 'Weber', 'Schulz', 'Wagner', 'Becker', 'Hoffmann',
-        'Novák', 'Svoboda', 'Novotný', 'Dvořák', 'Černý', 'Procházková', 'Kučerová', 'Veselá', 'Horáková', 'Němcová',
-        'Смирнов', 'Иванов', 'Кузнецов', 'Соколов', 'Попов', 'Лебедева', 'Козлова', 'Новикова', 'Морозова', 'Петрова',
-        '王', '李', '张', '刘', '陈', '楊', '黃', '趙', '吳', '周',
-        '佐藤', '鈴木', '高橋', '田中', '渡辺', '伊藤', '山本', '中村', '小林', '斎藤',
-    );
-
-    /** @var array lis of common first names */
-    public $firstnames = array(
-        'Jacob', 'Ethan', 'Michael', 'Jayden', 'William', 'Isabella', 'Sophia', 'Emma', 'Olivia', 'Ava',
-        'Lukas', 'Leon', 'Luca', 'Timm', 'Paul', 'Leonie', 'Leah', 'Lena', 'Hanna', 'Laura',
-        'Jakub', 'Jan', 'Tomáš', 'Lukáš', 'Matěj', 'Tereza', 'Eliška', 'Anna', 'Adéla', 'Karolína',
-        'Даниил', 'Максим', 'Артем', 'Иван', 'Александр', 'София', 'Анастасия', 'Дарья', 'Мария', 'Полина',
-        '伟', '伟', '芳', '伟', '秀英', '秀英', '娜', '秀英', '伟', '敏',
-        '翔', '大翔', '拓海', '翔太', '颯太', '陽菜', 'さくら', '美咲', '葵', '美羽',
-    );
-
-    public $loremipsum = <<<EOD
-Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla non arcu lacinia neque faucibus fringilla. Vivamus porttitor turpis ac leo. Integer in sapien. Nullam eget nisl. Aliquam erat volutpat. Cras elementum. Mauris suscipit, ligula sit amet pharetra semper, nibh ante cursus purus, vel sagittis velit mauris vel metus. Integer malesuada. Nullam lectus justo, vulputate eget mollis sed, tempor sed magna. Mauris elementum mauris vitae tortor. Aliquam erat volutpat.
-Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Pellentesque ipsum. Cras pede libero, dapibus nec, pretium sit amet, tempor quis. Aliquam ante. Proin in tellus sit amet nibh dignissim sagittis. Vivamus porttitor turpis ac leo. Duis bibendum, lectus ut viverra rhoncus, dolor nunc faucibus libero, eget facilisis enim ipsum id lacus. In sem justo, commodo ut, suscipit at, pharetra vitae, orci. Aliquam erat volutpat. Nulla est.
-Vivamus luctus egestas leo. Aenean fermentum risus id tortor. Mauris dictum facilisis augue. Aliquam erat volutpat. Aliquam ornare wisi eu metus. Aliquam id dolor. Duis condimentum augue id magna semper rutrum. Donec iaculis gravida nulla. Pellentesque ipsum. Etiam dictum tincidunt diam. Quisque tincidunt scelerisque libero. Etiam egestas wisi a erat.
-Integer lacinia. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris tincidunt sem sed arcu. Nullam feugiat, turpis at pulvinar vulputate, erat libero tristique tellus, nec bibendum odio risus sit amet ante. Aliquam id dolor. Maecenas sollicitudin. Et harum quidem rerum facilis est et expedita distinctio. Mauris suscipit, ligula sit amet pharetra semper, nibh ante cursus purus, vel sagittis velit mauris vel metus. Nullam dapibus fermentum ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Pellentesque sapien. Duis risus. Mauris elementum mauris vitae tortor. Suspendisse nisl. Integer rutrum, orci vestibulum ullamcorper ultricies, lacus quam ultricies odio, vitae placerat pede sem sit amet enim.
-In laoreet, magna id viverra tincidunt, sem odio bibendum justo, vel imperdiet sapien wisi sed libero. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Nullam justo enim, consectetuer nec, ullamcorper ac, vestibulum in, elit. Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Maecenas lorem. Etiam posuere lacus quis dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Nam sed tellus id magna elementum tincidunt. Suspendisse nisl. Vivamus luctus egestas leo. Nulla non arcu lacinia neque faucibus fringilla. Etiam dui sem, fermentum vitae, sagittis id, malesuada in, quam. Etiam dictum tincidunt diam. Etiam commodo dui eget wisi. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Duis ante orci, molestie vitae vehicula venenatis, tincidunt ac pede. Pellentesque sapien.
-EOD;
-
-    /**
-     * To be called from data reset code only,
-     * do not use in tests.
-     * @return void
-     */
-    public function reset() {
-        $this->usercounter = 0;
-        $this->categorycount = 0;
-        $this->coursecount = 0;
-        $this->scalecount = 0;
-
-        foreach($this->generators as $generator) {
-            $generator->reset();
-        }
-    }
-
-    /**
-     * Return generator for given plugin
-     * @param string $component
-     * @return mixed plugin data generator
-     */
-    public function get_plugin_generator($component) {
-        list($type, $plugin) = normalize_component($component);
-
-        if ($type !== 'mod' and $type !== 'block') {
-            throw new coding_exception("Plugin type $type does not support generators yet");
-        }
-
-        $dir = get_plugin_directory($type, $plugin);
-
-        if (!isset($this->generators[$type.'_'.$plugin])) {
-            $lib = "$dir/tests/generator/lib.php";
-            if (!include_once($lib)) {
-                throw new coding_exception("Plugin $component does not support data generator, missing tests/generator/lib");
-            }
-            $classname = $type.'_'.$plugin.'_generator';
-            $this->generators[$type.'_'.$plugin] = new $classname($this);
-        }
-
-        return $this->generators[$type.'_'.$plugin];
-    }
-
-    /**
-     * Create a test user
-     * @param array|stdClass $record
-     * @param array $options
-     * @return stdClass user record
-     */
-    public function create_user($record=null, array $options=null) {
-        global $DB, $CFG;
-
-        $this->usercounter++;
-        $i = $this->usercounter;
-
-        $record = (array)$record;
-
-        if (!isset($record['auth'])) {
-            $record['auth'] = 'manual';
-        }
-
-        if (!isset($record['firstname']) and !isset($record['lastname'])) {
-            $country = rand(0, 5);
-            $firstname = rand(0, 4);
-            $lastname = rand(0, 4);
-            $female = rand(0, 1);
-            $record['firstname'] = $this->firstnames[($country*10) + $firstname + ($female*5)];
-            $record['lastname'] = $this->lastnames[($country*10) + $lastname + ($female*5)];
-
-        } else if (!isset($record['firstname'])) {
-            $record['firstname'] = 'Firstname'.$i;
-
-        } else if (!isset($record['lastname'])) {
-            $record['lastname'] = 'Lastname'.$i;
-        }
-
-        if (!isset($record['idnumber'])) {
-            $record['idnumber'] = '';
-        }
-
-        if (!isset($record['mnethostid'])) {
-            $record['mnethostid'] = $CFG->mnet_localhost_id;
-        }
-
-        if (!isset($record['username'])) {
-            $record['username'] = textlib::strtolower($record['firstname']).textlib::strtolower($record['lastname']);
-            while ($DB->record_exists('user', array('username'=>$record['username'], 'mnethostid'=>$record['mnethostid']))) {
-                $record['username'] = $record['username'].'_'.$i;
-            }
-        }
-
-        if (!isset($record['password'])) {
-            $record['password'] = 'lala';
-        }
-
-        if (!isset($record['email'])) {
-            $record['email'] = $record['username'].'@example.com';
-        }
-
-        if (!isset($record['confirmed'])) {
-            $record['confirmed'] = 1;
-        }
-
-        if (!isset($record['lang'])) {
-            $record['lang'] = 'en';
-        }
-
-        if (!isset($record['maildisplay'])) {
-            $record['maildisplay'] = 1;
-        }
-
-        if (!isset($record['deleted'])) {
-            $record['deleted'] = 0;
-        }
-
-        $record['timecreated'] = time();
-        $record['timemodified'] = $record['timecreated'];
-        $record['lastip'] = '0.0.0.0';
-
-        $record['password'] = hash_internal_user_password($record['password']);
-
-        if ($record['deleted']) {
-            $delname = $record['email'].'.'.time();
-            while ($DB->record_exists('user', array('username'=>$delname))) {
-                $delname++;
-            }
-            $record['idnumber'] = '';
-            $record['email']    = md5($record['username']);
-            $record['username'] = $delname;
-            $record['picture']  = 0;
-        }
-
-        $userid = $DB->insert_record('user', $record);
-
-        if (!$record['deleted']) {
-            context_user::instance($userid);
-        }
-
-        return $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
-    }
-
-    /**
-     * Create a test course category
-     * @param array|stdClass $record
-     * @param array $options
-     * @return stdClass course category record
-     */
-    function create_category($record=null, array $options=null) {
-        global $DB, $CFG;
-        require_once("$CFG->dirroot/course/lib.php");
-
-        $this->categorycount++;
-        $i = $this->categorycount;
-
-        $record = (array)$record;
-
-        if (!isset($record['name'])) {
-            $record['name'] = 'Course category '.$i;
-        }
-
-        if (!isset($record['idnumber'])) {
-            $record['idnumber'] = '';
-        }
-
-        if (!isset($record['description'])) {
-            $record['description'] = "Test course category $i\n$this->loremipsum";
-        }
-
-        if (!isset($record['descriptionformat'])) {
-            $record['description'] = FORMAT_MOODLE;
-        }
-
-        if (!isset($record['parent'])) {
-            $record['descriptionformat'] = 0;
-        }
-
-        if (empty($record['parent'])) {
-            $parent = new stdClass();
-            $parent->path = '';
-            $parent->depth = 0;
-        } else {
-            $parent = $DB->get_record('course_categories', array('id'=>$record['parent']), '*', MUST_EXIST);
-        }
-        $record['depth'] = $parent->depth+1;
-
-        $record['sortorder'] = 0;
-        $record['timemodified'] = time();
-        $record['timecreated'] = $record['timemodified'];
-
-        $catid = $DB->insert_record('course_categories', $record);
-        $path = $parent->path . '/' . $catid;
-        $DB->set_field('course_categories', 'path', $path, array('id'=>$catid));
-        context_coursecat::instance($catid);
-
-        fix_course_sortorder();
-
-        return $DB->get_record('course_categories', array('id'=>$catid), '*', MUST_EXIST);
-    }
-
-    /**
-     * Create a test course
-     * @param array|stdClass $record
-     * @param array $options with keys:
-     *      'createsections'=>bool precreate all sections
-     * @return stdClass course record
-     */
-    function create_course($record=null, array $options=null) {
-        global $DB, $CFG;
-        require_once("$CFG->dirroot/course/lib.php");
-
-        $this->coursecount++;
-        $i = $this->coursecount;
-
-        $record = (array)$record;
-
-        if (!isset($record['fullname'])) {
-            $record['fullname'] = 'Test course '.$i;
-        }
-
-        if (!isset($record['shortname'])) {
-            $record['shortname'] = 'tc_'.$i;
-        }
-
-        if (!isset($record['idnumber'])) {
-            $record['idnumber'] = '';
-        }
-
-        if (!isset($record['format'])) {
-            $record['format'] = 'topics';
-        }
-
-        if (!isset($record['newsitems'])) {
-            $record['newsitems'] = 0;
-        }
-
-        if (!isset($record['numsections'])) {
-            $record['numsections'] = 5;
-        }
-
-        if (!isset($record['description'])) {
-            $record['description'] = "Test course $i\n$this->loremipsum";
-        }
-
-        if (!isset($record['descriptionformat'])) {
-            $record['description'] = FORMAT_MOODLE;
-        }
-
-        if (!isset($record['category'])) {
-            $record['category'] = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
-        }
-
-        $course = create_course((object)$record);
-        context_course::instance($course->id);
-
-        if (!empty($options['createsections'])) {
-            for($i=1; $i<$record['numsections']; $i++) {
-                self::create_course_section(array('course'=>$course->id, 'section'=>$i));
-            }
-        }
-
-        return $course;
-    }
-
-    /**
-     * Create course section if does not exist yet
-     * @param mixed $record
-     * @param array|null $options
-     * @return stdClass
-     * @throws coding_exception
-     */
-    public function create_course_section($record = null, array $options = null) {
-        global $DB;
-
-        $record = (array)$record;
-
-        if (empty($record['course'])) {
-            throw new coding_exception('course must be present in phpunit_util::create_course_section() $record');
-        }
-
-        if (!isset($record['section'])) {
-            throw new coding_exception('section must be present in phpunit_util::create_course_section() $record');
-        }
-
-        if (!isset($record['name'])) {
-            $record['name'] = '';
-        }
-
-        if (!isset($record['summary'])) {
-            $record['summary'] = '';
-        }
-
-        if (!isset($record['summaryformat'])) {
-            $record['summaryformat'] = FORMAT_MOODLE;
-        }
-
-        if ($section = $DB->get_record('course_sections', array('course'=>$record['course'], 'section'=>$record['section']))) {
-            return $section;
-        }
-
-        $section = new stdClass();
-        $section->course        = $record['course'];
-        $section->section       = $record['section'];
-        $section->name          = $record['name'];
-        $section->summary       = $record['summary'];
-        $section->summaryformat = $record['summaryformat'];
-        $id = $DB->insert_record('course_sections', $section);
-
-        return $DB->get_record('course_sections', array('id'=>$id));
-    }
-
-    /**
-     * Create a test block
-     * @param string $blockname
-     * @param array|stdClass $record
-     * @param array $options
-     * @return stdClass block instance record
-     */
-    public function create_block($blockname, $record=null, array $options=null) {
-        $generator = $this->get_plugin_generator('block_'.$blockname);
-        return $generator->create_instance($record, $options);
-    }
-
-    /**
-     * Create a test module
-     * @param string $modulename
-     * @param array|stdClass $record
-     * @param array $options
-     * @return stdClass activity record
-     */
-    public function create_module($modulename, $record=null, array $options=null) {
-        $generator = $this->get_plugin_generator('mod_'.$modulename);
-        return $generator->create_instance($record, $options);
-    }
-
-    /**
-     * Create a test group for the specified course
-     *
-     * $record should be either an array or a stdClass containing infomation about the group to create.
-     * At the very least it needs to contain courseid.
-     * Default values are added for name, description, and descriptionformat if they are not present.
-     *
-     * This function calls {@see groups_create_group()} to create the group within the database.
-     *
-     * @param array|stdClass $record
-     * @return stdClass group record
-     */
-    public function create_group($record) {
-        global $DB, $CFG;
-
-        require_once($CFG->dirroot . '/group/lib.php');
-
-        $this->groupcount++;
-        $i = $this->groupcount;
-
-        $record = (array)$record;
-
-        if (empty($record['courseid'])) {
-            throw new coding_exception('courseid must be present in phpunit_util::create_group() $record');
-        }
-
-        if (!isset($record['name'])) {
-            $record['name'] = 'group-' . $i;
-        }
-
-        if (!isset($record['description'])) {
-            $record['description'] = "Test Group $i\n{$this->loremipsum}";
-        }
-
-        if (!isset($record['descriptionformat'])) {
-            $record['descriptionformat'] = FORMAT_MOODLE;
-        }
-
-        $id = groups_create_group((object)$record);
-
-        return $DB->get_record('groups', array('id'=>$id));
-    }
-
-    /**
-     * Create a test grouping for the specified course
-     *
-     * $record should be either an array or a stdClass containing infomation about the grouping to create.
-     * At the very least it needs to contain courseid.
-     * Default values are added for name, description, and descriptionformat if they are not present.
-     *
-     * This function calls {@see groups_create_grouping()} to create the grouping within the database.
-     *
-     * @param array|stdClass $record
-     * @return stdClass grouping record
-     */
-    public function create_grouping($record) {
-        global $DB, $CFG;
-
-        require_once($CFG->dirroot . '/group/lib.php');
-
-        $this->groupingcount++;
-        $i = $this->groupingcount;
-
-        $record = (array)$record;
-
-        if (empty($record['courseid'])) {
-            throw new coding_exception('courseid must be present in phpunit_util::create_grouping() $record');
-        }
-
-        if (!isset($record['name'])) {
-            $record['name'] = 'grouping-' . $i;
-        }
-
-        if (!isset($record['description'])) {
-            $record['description'] = "Test Grouping $i\n{$this->loremipsum}";
-        }
-
-        if (!isset($record['descriptionformat'])) {
-            $record['descriptionformat'] = FORMAT_MOODLE;
-        }
-
-        $id = groups_create_grouping((object)$record);
-
-        return $DB->get_record('groupings', array('id'=>$id));
-    }
-
-    /**
-     * Create a test scale
-     * @param array|stdClass $record
-     * @param array $options
-     * @return stdClass block instance record
-     */
-    public function create_scale($record=null, array $options=null) {
-        global $DB;
-
-        $this->scalecount++;
-        $i = $this->scalecount;
-
-        $record = (array)$record;
-
-        if (!isset($record['name'])) {
-            $record['name'] = 'Test scale '.$i;
-        }
-
-        if (!isset($record['scale'])) {
-            $record['scale'] = 'A,B,C,D,F';
-        }
-
-        if (!isset($record['courseid'])) {
-            $record['courseid'] = 0;
-        }
-
-        if (!isset($record['userid'])) {
-            $record['userid'] = 0;
-        }
-
-        if (!isset($record['description'])) {
-            $record['description'] = 'Test scale description '.$i;
-        }
-
-        if (!isset($record['descriptionformat'])) {
-            $record['descriptionformat'] = FORMAT_MOODLE;
-        }
-
-        $record['timemodified'] = time();
-
-        if (isset($record['id'])) {
-            $DB->import_record('scale', $record);
-            $DB->get_manager()->reset_sequence('scale');
-            $id = $record['id'];
-        } else {
-            $id = $DB->insert_record('scale', $record);
-        }
-
-        return $DB->get_record('scale', array('id'=>$id), '*', MUST_EXIST);
-    }
-}
-
-
-/**
- * Module generator base class.
- *
- * Extend in mod/xxxx/tests/generator/lib.php as class mod_xxxx_generator.
- *
- * @package    core
- * @category   phpunit
- * @copyright  2012 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class phpunit_module_generator {
-    /** @var phpunit_data_generator@var  */
-    protected $datagenerator;
-
-    /** @var number of created instances */
-    protected $instancecount = 0;
-
-    public function __construct(phpunit_data_generator $datagenerator) {
-        $this->datagenerator = $datagenerator;
-    }
-
-    /**
-     * To be called from data reset code only,
-     * do not use in tests.
-     * @return void
-     */
-    public function reset() {
-        $this->instancecount = 0;
-    }
-
-    /**
-     * Returns module name
-     * @return string name of module that this class describes
-     * @throws coding_exception if class invalid
-     */
-    public function get_modulename() {
-        $matches = null;
-        if (!preg_match('/^mod_([a-z0-9]+)_generator$/', get_class($this), $matches)) {
-            throw new coding_exception('Invalid module generator class name: '.get_class($this));
-        }
-
-        if (empty($matches[1])) {
-            throw new coding_exception('Invalid module generator class name: '.get_class($this));
-        }
-        return $matches[1];
-    }
-
-    /**
-     * Create course module and link it to course
-     * @param int $courseid
-     * @param array $options: section, visible
-     * @return int $cm instance id
-     */
-    protected function precreate_course_module($courseid, array $options) {
-        global $DB, $CFG;
-        require_once("$CFG->dirroot/course/lib.php");
-
-        $modulename = $this->get_modulename();
-
-        $cm = new stdClass();
-        $cm->course             = $courseid;
-        $cm->module             = $DB->get_field('modules', 'id', array('name'=>$modulename));
-        $cm->instance           = 0;
-        $cm->section            = isset($options['section']) ? $options['section'] : 0;
-        $cm->idnumber           = isset($options['idnumber']) ? $options['idnumber'] : 0;
-        $cm->added              = time();
-
-        $columns = $DB->get_columns('course_modules');
-        foreach ($options as $key=>$value) {
-            if ($key === 'id' or !isset($columns[$key])) {
-                continue;
-            }
-            if (property_exists($cm, $key)) {
-                continue;
-            }
-            $cm->$key = $value;
-        }
-
-        $cm->id = $DB->insert_record('course_modules', $cm);
-        $cm->coursemodule = $cm->id;
-
-        add_mod_to_section($cm);
-
-        return $cm->id;
-    }
-
-    /**
-     * Called after *_add_instance()
-     * @param int $id
-     * @param int $cmid
-     * @return stdClass module instance
-     */
-    protected function post_add_instance($id, $cmid) {
-        global $DB;
-
-        $DB->set_field('course_modules', 'instance', $id, array('id'=>$cmid));
-
-        $instance = $DB->get_record($this->get_modulename(), array('id'=>$id), '*', MUST_EXIST);
-
-        $cm = get_coursemodule_from_id($this->get_modulename(), $cmid, $instance->course, true, MUST_EXIST);
-        context_module::instance($cm->id);
-
-        $instance->cmid = $cm->id;
-
-        return $instance;
-    }
-
-    /**
-     * Create a test module
-     * @param array|stdClass $record
-     * @param array $options
-     * @return stdClass activity record
-     */
-    abstract public function create_instance($record = null, array $options = null);
-}
-
-
-/**
- * Block generator base class.
- *
- * Extend in blocks/xxxx/tests/generator/lib.php as class block_xxxx_generator.
- *
- * @package    core
- * @category   phpunit
- * @copyright  2012 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class phpunit_block_generator {
-    /** @var phpunit_data_generator@var  */
-    protected $datagenerator;
-
-    /** @var number of created instances */
-    protected $instancecount = 0;
-
-    public function __construct(phpunit_data_generator $datagenerator) {
-        $this->datagenerator = $datagenerator;
-    }
-
-    /**
-     * To be called from data reset code only,
-     * do not use in tests.
-     * @return void
-     */
-    public function reset() {
-        $this->instancecount = 0;
-    }
-
-    /**
-     * Returns block name
-     * @return string name of block that this class describes
-     * @throws coding_exception if class invalid
-     */
-    public function get_blockname() {
-        $matches = null;
-        if (!preg_match('/^block_([a-z0-9_]+)_generator$/', get_class($this), $matches)) {
-            throw new coding_exception('Invalid block generator class name: '.get_class($this));
-        }
-
-        if (empty($matches[1])) {
-            throw new coding_exception('Invalid block generator class name: '.get_class($this));
-        }
-        return $matches[1];
-    }
-
-    /**
-     * Fill in record defaults
-     * @param stdClass $record
-     * @return stdClass
-     */
-    protected function prepare_record(stdClass $record) {
-        $record->blockname = $this->get_blockname();
-        if (!isset($record->parentcontextid)) {
-            $record->parentcontextid = context_system::instance()->id;
-        }
-        if (!isset($record->showinsubcontexts)) {
-            $record->showinsubcontexts = 1;
-        }
-        if (!isset($record->pagetypepattern)) {
-            $record->pagetypepattern = '';
-        }
-        if (!isset($record->subpagepattern)) {
-            $record->subpagepattern = null;
-        }
-        if (!isset($record->defaultregion)) {
-            $record->defaultregion = '';
-        }
-        if (!isset($record->defaultweight)) {
-            $record->defaultweight = '';
-        }
-        if (!isset($record->configdata)) {
-            $record->configdata = null;
-        }
-        return $record;
-    }
-
-    /**
-     * Create a test block
-     * @param array|stdClass $record
-     * @param array $options
-     * @return stdClass activity record
-     */
-    abstract public function create_instance($record = null, array $options = null);
-}
index ce05ae8..9cd8585 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Various PHPUnit classes and functions
+ * Moodle PHPUnit integration
  *
  * @package    core
  * @category   phpunit
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-require_once 'PHPUnit/Autoload.php';
-require_once 'PHPUnit/Extensions/Database/Autoload.php';
+// NOTE: MOODLE_INTERNAL is not verified here because we load this before setup.php!
 
+require_once('PHPUnit/Autoload.php');
+require_once('PHPUnit/Extensions/Database/Autoload.php');
 
-/**
- * Collection of utility methods.
- *
- * @package    core
- * @category   phpunit
- * @copyright  2012 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class phpunit_util {
-    /** @var string current version hash from php files */
-    protected static $versionhash = null;
-
-    /** @var array original content of all database tables*/
-    protected static $tabledata = null;
-
-    /** @var array original structure of all database tables */
-    protected static $tablestructure = null;
-
-    /** @var array original structure of all database tables */
-    protected static $sequencenames = null;
-
-    /** @var array An array of original globals, restored after each test */
-    protected static $globals = array();
-
-    /** @var int last value of db writes counter, used for db resetting */
-    public static $lastdbwrites = null;
-
-    /** @var phpunit_data_generator */
-    protected static $generator = null;
-
-    /** @var resource used for prevention of parallel test execution */
-    protected static $lockhandle = null;
-
-    /**
-     * Prevent parallel test execution - this can not work in Moodle because we modify database and dataroot.
-     *
-     * Note: do not call manually!
-     *
-     * @internal
-     * @static
-     * @return void
-     */
-    public static function acquire_test_lock() {
-        global $CFG;
-        if (!file_exists("$CFG->phpunit_dataroot/phpunit")) {
-            // dataroot not initialised yet
-            return;
-        }
-        if (!file_exists("$CFG->phpunit_dataroot/phpunit/lock")) {
-            file_put_contents("$CFG->phpunit_dataroot/phpunit/lock", 'This file prevents concurrent execution of Moodle PHPUnit tests');
-            phpunit_boostrap_fix_file_permissions("$CFG->phpunit_dataroot/phpunit/lock");
-        }
-        if (self::$lockhandle = fopen("$CFG->phpunit_dataroot/phpunit/lock", 'r')) {
-            $wouldblock = null;
-            $locked = flock(self::$lockhandle, (LOCK_EX | LOCK_NB), $wouldblock);
-            if (!$locked) {
-                if ($wouldblock) {
-                    echo "Waiting for other test execution to complete...\n";
-                }
-                $locked = flock(self::$lockhandle, LOCK_EX);
-            }
-            if (!$locked) {
-                fclose(self::$lockhandle);
-                self::$lockhandle = null;
-            }
-        }
-        register_shutdown_function(array('phpunit_util', 'release_test_lock'));
-    }
-
-    /**
-     * Note: do not call manually!
-     * @internal
-     * @static
-     * @return void
-     */
-    public static function release_test_lock() {
-        if (self::$lockhandle) {
-            flock(self::$lockhandle, LOCK_UN);
-            fclose(self::$lockhandle);
-            self::$lockhandle = null;
-        }
-    }
-
-    /**
-     * Load global $CFG;
-     * @internal
-     * @static
-     * @return void
-     */
-    public static function initialise_cfg() {
-        global $DB;
-        $dbhash = false;
-        try {
-            $dbhash = $DB->get_field('config', 'value', array('name'=>'phpunittest'));
-        } catch (Exception $e) {
-            // not installed yet
-            initialise_cfg();
-            return;
-        }
-        if ($dbhash !== phpunit_util::get_version_hash()) {
-            // do not set CFG - the only way forward is to drop and reinstall
-            return;
-        }
-        // standard CFG init
-        initialise_cfg();
-    }
-
-    /**
-     * Get data generator
-     * @static
-     * @return phpunit_data_generator
-     */
-    public static function get_data_generator() {
-        if (is_null(self::$generator)) {
-            require_once(__DIR__.'/generatorlib.php');
-            self::$generator = new phpunit_data_generator();
-        }
-        return self::$generator;
-    }
-
-    /**
-     * Returns contents of all tables right after installation.
-     * @static
-     * @return array $table=>$records
-     */
-    protected static function get_tabledata() {
-        global $CFG;
-
-        if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
-            // not initialised yet
-            return array();
-        }
-
-        if (!isset(self::$tabledata)) {
-            $data = file_get_contents("$CFG->dataroot/phpunit/tabledata.ser");
-            self::$tabledata = unserialize($data);
-        }
-
-        if (!is_array(self::$tabledata)) {
-            phpunit_bootstrap_error(1, 'Can not read dataroot/phpunit/tabledata.ser or invalid format, reinitialize test database.');
-        }
-
-        return self::$tabledata;
-    }
-
-    /**
-     * Returns structure of all tables right after installation.
-     * @static
-     * @return array $table=>$records
-     */
-    public static function get_tablestructure() {
-        global $CFG;
-
-        if (!file_exists("$CFG->dataroot/phpunit/tablestructure.ser")) {
-            // not initialised yet
-            return array();
-        }
-
-        if (!isset(self::$tablestructure)) {
-            $data = file_get_contents("$CFG->dataroot/phpunit/tablestructure.ser");
-            self::$tablestructure = unserialize($data);
-        }
-
-        if (!is_array(self::$tablestructure)) {
-            phpunit_bootstrap_error(1, 'Can not read dataroot/phpunit/tablestructure.ser or invalid format, reinitialize test database.');
-        }
-
-        return self::$tablestructure;
-    }
-
-    /**
-     * Returns the names of sequences for each autoincrementing id field in all standard tables.
-     * @static
-     * @return array $table=>$sequencename
-     */
-    public static function get_sequencenames() {
-        global $DB;
-
-        if (isset(self::$sequencenames)) {
-            return self::$sequencenames;
-        }
-
-        if (!$structure = self::get_tablestructure()) {
-            return array();
-        }
-
-        self::$sequencenames = array();
-        foreach ($structure as $table=>$ignored) {
-            $name = $DB->get_manager()->generator->getSequenceFromDB(new xmldb_table($table));
-            if ($name !== false) {
-                self::$sequencenames[$table] = $name;
-            }
-        }
-
-        return self::$sequencenames;
-    }
-
-    /**
-     * Returns list of tables that are unmodified and empty.
-     *
-     * @static
-     * @return array of table names, empty if unknown
-     */
-    protected static function guess_unmodified_empty_tables() {
-        global $DB;
-
-        $dbfamily = $DB->get_dbfamily();
-
-        if ($dbfamily === 'mysql') {
-            $empties = array();
-            $prefix = $DB->get_prefix();
-            $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
-            foreach ($rs as $info) {
-                $table = strtolower($info->name);
-                if (strpos($table, $prefix) !== 0) {
-                    // incorrect table match caused by _
-                    continue;
-                }
-                if (!is_null($info->auto_increment)) {
-                    $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
-                    if ($info->auto_increment == 1) {
-                        $empties[$table] = $table;
-                    }
-                }
-            }
-            $rs->close();
-            return $empties;
-
-        } else if ($dbfamily === 'mssql') {
-            $empties = array();
-            $prefix = $DB->get_prefix();
-            $sql = "SELECT t.name
-                      FROM sys.identity_columns i
-                      JOIN sys.tables t ON t.object_id = i.object_id
-                     WHERE t.name LIKE ?
-                       AND i.name = 'id'
-                       AND i.last_value IS NULL";
-            $rs = $DB->get_recordset_sql($sql, array($prefix.'%'));
-            foreach ($rs as $info) {
-                $table = strtolower($info->name);
-                if (strpos($table, $prefix) !== 0) {
-                    // incorrect table match caused by _
-                    continue;
-                }
-                $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
-                $empties[$table] = $table;
-            }
-            $rs->close();
-            return $empties;
-
-        } else if ($dbfamily === 'oracle') {
-            $sequences = phpunit_util::get_sequencenames();
-            $sequences = array_map('strtoupper', $sequences);
-            $lookup = array_flip($sequences);
-            $empties = array();
-            list($seqs, $params) = $DB->get_in_or_equal($sequences);
-            $sql = "SELECT sequence_name FROM user_sequences WHERE last_number = 1 AND sequence_name $seqs";
-            $rs = $DB->get_recordset_sql($sql, $params);
-            foreach ($rs as $seq) {
-                $table = $lookup[$seq->sequence_name];
-                $empties[$table] = $table;
-            }
-            $rs->close();
-            return $empties;
-
-        } else {
-            return array();
-        }
-    }
-
-    /**
-     * Reset all database sequences to initial values.
-     *
-     * @static
-     * @param array $empties tables that are known to be unmodified and empty
-     * @return void
-     */
-    public static function reset_all_database_sequences(array $empties = null) {
-        global $DB;
-
-        if (!$data = self::get_tabledata()) {
-            // not initialised yet
-            return;
-        }
-        if (!$structure = self::get_tablestructure()) {
-            // not initialised yet
-            return;
-        }
-
-        $dbfamily = $DB->get_dbfamily();
-        if ($dbfamily === 'postgres') {
-            $queries = array();
-            $prefix = $DB->get_prefix();
-            foreach ($data as $table=>$records) {
-                if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
-                    if (empty($records)) {
-                        $nextid = 1;
-                    } else {
-                        $lastrecord = end($records);
-                        $nextid = $lastrecord->id + 1;
-                    }
-                    $queries[] = "ALTER SEQUENCE {$prefix}{$table}_id_seq RESTART WITH $nextid";
-                }
-            }
-            if ($queries) {
-                $DB->change_database_structure(implode(';', $queries));
-            }
-
-        } else if ($dbfamily === 'mysql') {
-            $sequences = array();
-            $prefix = $DB->get_prefix();
-            $rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
-            foreach ($rs as $info) {
-                $table = strtolower($info->name);
-                if (strpos($table, $prefix) !== 0) {
-                    // incorrect table match caused by _
-                    continue;
-                }
-                if (!is_null($info->auto_increment)) {
-                    $table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
-                    $sequences[$table] = $info->auto_increment;
-                }
-            }
-            $rs->close();
-            $prefix = $DB->get_prefix();
-            foreach ($data as $table=>$records) {
-                if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
-                    if (isset($sequences[$table])) {
-                        if (empty($records)) {
-                            $nextid = 1;
-                        } else {
-                            $lastrecord = end($records);
-                            $nextid = $lastrecord->id + 1;
-                        }
-                        if ($sequences[$table] != $nextid) {
-                            $DB->change_database_structure("ALTER TABLE {$prefix}{$table} AUTO_INCREMENT = $nextid");
-                        }
-
-                    } else {
-                        // some problem exists, fallback to standard code
-                        $DB->get_manager()->reset_sequence($table);
-                    }
-                }
-            }
-
-        } else if ($dbfamily === 'oracle') {
-            $sequences = phpunit_util::get_sequencenames();
-            $sequences = array_map('strtoupper', $sequences);
-            $lookup = array_flip($sequences);
-
-            $current = array();
-            list($seqs, $params) = $DB->get_in_or_equal($sequences);
-            $sql = "SELECT sequence_name, last_number FROM user_sequences WHERE sequence_name $seqs";
-            $rs = $DB->get_recordset_sql($sql, $params);
-            foreach ($rs as $seq) {
-                $table = $lookup[$seq->sequence_name];
-                $current[$table] = $seq->last_number;
-            }
-            $rs->close();
-
-            foreach ($data as $table=>$records) {
-                if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
-                    $lastrecord = end($records);
-                    if ($lastrecord) {
-                        $nextid = $lastrecord->id + 1;
-                    } else {
-                        $nextid = 1;
-                    }
-                    if (!isset($current[$table])) {
-                        $DB->get_manager()->reset_sequence($table);
-                    } else if ($nextid == $current[$table]) {
-                        continue;
-                    }
-                    // reset as fast as possible - alternatively we could use http://stackoverflow.com/questions/51470/how-do-i-reset-a-sequence-in-oracle
-                    $seqname = $sequences[$table];
-                    $cachesize = $DB->get_manager()->generator->sequence_cache_size;
-                    $DB->change_database_structure("DROP SEQUENCE $seqname");
-                    $DB->change_database_structure("CREATE SEQUENCE $seqname START WITH $nextid INCREMENT BY 1 NOMAXVALUE CACHE $cachesize");
-                }
-            }
-
-        } else {
-            // note: does mssql support any kind of faster reset?
-            if (is_null($empties)) {
-                $empties = self::guess_unmodified_empty_tables();
-            }
-            foreach ($data as $table=>$records) {
-                if (isset($empties[$table])) {
-                    continue;
-                }
-                if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
-                    $DB->get_manager()->reset_sequence($table);
-                }
-            }
-        }
-    }
-
-    /**
-     * Reset all database tables to default values.
-     * @static
-     * @return bool true if reset done, false if skipped
-     */
-    public static function reset_database() {
-        global $DB;
-
-        if (!is_null(self::$lastdbwrites) and self::$lastdbwrites == $DB->perf_get_writes()) {
-            return false;
-        }
-
-        $tables = $DB->get_tables(false);
-        if (!$tables or empty($tables['config'])) {
-            // not installed yet
-            return false;
-        }
-
-        if (!$data = self::get_tabledata()) {
-            // not initialised yet
-            return false;
-        }
-        if (!$structure = self::get_tablestructure()) {
-            // not initialised yet
-            return false;
-        }
-
-        $empties = self::guess_unmodified_empty_tables();
-
-        foreach ($data as $table=>$records) {
-            if (empty($records)) {
-                if (isset($empties[$table])) {
-                    // table was not modified and is empty
-                } else {
-                    $DB->delete_records($table, array());
-                }
-                continue;
-            }
-
-            if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
-                $currentrecords = $DB->get_records($table, array(), 'id ASC');
-                $changed = false;
-                foreach ($records as $id=>$record) {
-                    if (!isset($currentrecords[$id])) {
-                        $changed = true;
-                        break;
-                    }
-                    if ((array)$record != (array)$currentrecords[$id]) {
-                        $changed = true;
-                        break;
-                    }
-                    unset($currentrecords[$id]);
-                }
-                if (!$changed) {
-                    if ($currentrecords) {
-                        $lastrecord = end($records);
-                        $DB->delete_records_select($table, "id > ?", array($lastrecord->id));
-                        continue;
-                    } else {
-                        continue;
-                    }
-                }
-            }
-
-            $DB->delete_records($table, array());
-            foreach ($records as $record) {
-                $DB->import_record($table, $record, false, true);
-            }
-        }
-
-        // reset all next record ids - aka sequences
-        self::reset_all_database_sequences($empties);
-
-        // remove extra tables
-        foreach ($tables as $table) {
-            if (!isset($data[$table])) {
-                $DB->get_manager()->drop_table(new xmldb_table($table));
-            }
-        }
-
-        self::$lastdbwrites = $DB->perf_get_writes();
-
-        return true;
-    }
-
-    /**
-     * Purge dataroot directory
-     * @static
-     * @return void
-     */
-    public static function reset_dataroot() {
-        global $CFG;
-
-        $handle = opendir($CFG->dataroot);
-        $skip = array('.', '..', 'phpunittestdir.txt', 'phpunit', '.htaccess');
-        while (false !== ($item = readdir($handle))) {
-            if (in_array($item, $skip)) {
-                continue;
-            }
-            if (is_dir("$CFG->dataroot/$item")) {
-                remove_dir("$CFG->dataroot/$item", false);
-            } else {
-                unlink("$CFG->dataroot/$item");
-            }
-        }
-        closedir($handle);
-        make_temp_directory('');
-        make_cache_directory('');
-        make_cache_directory('htmlpurifier');
-    }
-
-    /**
-     * Reset contents of all database tables to initial values, reset caches, etc.
-     *
-     * Note: this is relatively slow (cca 2 seconds for pg and 7 for mysql) - please use with care!
-     *
-     * @static
-     * @param bool $logchanges log changes in global state and database in error log
-     * @return void
-     */
-    public static function reset_all_data($logchanges = false) {
-        global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION;
-
-        // reset global $DB in case somebody mocked it
-        $DB = self::get_global_backup('DB');
-
-        if ($DB->is_transaction_started()) {
-            // we can not reset inside transaction
-            $DB->force_transaction_rollback();
-        }
-
-        $resetdb = self::reset_database();
-        $warnings = array();
-
-        if ($logchanges) {
-            if ($resetdb) {
-                $warnings[] = 'Warning: unexpected database modification, resetting DB state';
-            }
-
-            $oldcfg = self::get_global_backup('CFG');
-            $oldsite = self::get_global_backup('SITE');
-            foreach($CFG as $k=>$v) {
-                if (!property_exists($oldcfg, $k)) {
-                    $warnings[] = 'Warning: unexpected new $CFG->'.$k.' value';
-                } else if ($oldcfg->$k !== $CFG->$k) {
-                    $warnings[] = 'Warning: unexpected change of $CFG->'.$k.' value';
-                }
-                unset($oldcfg->$k);
-
-            }
-            if ($oldcfg) {
-                foreach($oldcfg as $k=>$v) {
-                    $warnings[] = 'Warning: unexpected removal of $CFG->'.$k;
-                }
-            }
-
-            if ($USER->id != 0) {
-                $warnings[] = 'Warning: unexpected change of $USER';
-            }
-
-            if ($COURSE->id != $oldsite->id) {
-                $warnings[] = 'Warning: unexpected change of $COURSE';
-            }
-        }
-
-        // restore original globals
-        $_SERVER = self::get_global_backup('_SERVER');
-        $CFG = self::get_global_backup('CFG');
-        $SITE = self::get_global_backup('SITE');
-        $COURSE = $SITE;
-
-        // reinitialise following globals
-        $OUTPUT = new bootstrap_renderer();
-        $PAGE = new moodle_page();
-        $FULLME = null;
-        $ME = null;
-        $SCRIPT = null;
-        $SESSION = new stdClass();
-        $_SESSION['SESSION'] =& $SESSION;
-
-        // set fresh new not-logged-in user
-        $user = new stdClass();
-        $user->id = 0;
-        $user->mnethostid = $CFG->mnet_localhost_id;
-        session_set_user($user);
-
-        // reset all static caches
-        accesslib_clear_all_caches(true);
-        get_string_manager()->reset_caches();
-        events_get_handlers('reset');
-        textlib::reset_caches();
-        //TODO: add more resets here and probably refactor them to new core function
-
-        // purge dataroot directory
-        self::reset_dataroot();
-
-        // restore original config once more in case resetting of caches changed CFG
-        $CFG = self::get_global_backup('CFG');
-
-        // inform data generator
-        self::get_data_generator()->reset();
-
-        // fix PHP settings
-        error_reporting($CFG->debug);
-
-        // verify db writes just in case something goes wrong in reset
-        if (self::$lastdbwrites != $DB->perf_get_writes()) {
-            error_log('Unexpected DB writes in phpunit_util::reset_all_data()');
-            self::$lastdbwrites = $DB->perf_get_writes();
-        }
-
-        if ($warnings) {
-            $warnings = implode("\n", $warnings);
-            trigger_error($warnings, E_USER_WARNING);
-        }
-    }
-
-    /**
-     * Called during bootstrap only!
-     * @internal
-     * @static
-     * @return void
-     */
-    public static function bootstrap_init() {
-        global $CFG, $SITE, $DB;
-
-        // backup the globals
-        self::$globals['_SERVER'] = $_SERVER;
-        self::$globals['CFG'] = clone($CFG);
-        self::$globals['SITE'] = clone($SITE);
-        self::$globals['DB'] = $DB;
-
-        // refresh data in all tables, clear caches, etc.
-        phpunit_util::reset_all_data();
-    }
-
-    /**
-     * Returns original state of global variable.
-     * @static
-     * @param string $name
-     * @return mixed
-     */
-    public static function get_global_backup($name) {
-        if ($name === 'DB') {
-            // no cloning of database object,
-            // we just need the original reference, not original state
-            return self::$globals['DB'];
-        }
-        if (isset(self::$globals[$name])) {
-            if (is_object(self::$globals[$name])) {
-                $return = clone(self::$globals[$name]);
-                return $return;
-            } else {
-                return self::$globals[$name];
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Does this site (db and dataroot) appear to be used for production?
-     * We try very hard to prevent accidental damage done to production servers!!
-     *
-     * @static
-     * @return bool
-     */
-    public static function is_test_site() {
-        global $DB, $CFG;
-
-        if (!file_exists("$CFG->dataroot/phpunittestdir.txt")) {
-            // this is already tested in bootstrap script,
-            // but anyway presence of this file means the dataroot is for testing
-            return false;
-        }
-
-        $tables = $DB->get_tables(false);
-        if ($tables) {
-            if (!$DB->get_manager()->table_exists('config')) {
-                return false;
-            }
-            if (!get_config('core', 'phpunittest')) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Is this site initialised to run unit tests?
-     *
-     * @static
-     * @return int array errorcode=>message, 0 means ok
-     */
-    public static function testing_ready_problem() {
-        global $CFG, $DB;
-
-        $tables = $DB->get_tables(false);
-
-        if (!self::is_test_site()) {
-            // dataroot was verified in bootstrap, so it must be DB
-            return array(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not use database for testing, try different prefix');
-        }
-
-        if (empty($tables)) {
-            return array(PHPUNIT_EXITCODE_INSTALL, '');
-        }
-
-        if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser") or !file_exists("$CFG->dataroot/phpunit/tablestructure.ser")) {
-            return array(PHPUNIT_EXITCODE_REINSTALL, '');
-        }
-
-        if (!file_exists("$CFG->dataroot/phpunit/versionshash.txt")) {
-            return array(PHPUNIT_EXITCODE_REINSTALL, '');
-        }
-
-        $hash = phpunit_util::get_version_hash();
-        $oldhash = file_get_contents("$CFG->dataroot/phpunit/versionshash.txt");
-
-        if ($hash !== $oldhash) {
-            return array(PHPUNIT_EXITCODE_REINSTALL, '');
-        }
-
-        $dbhash = get_config('core', 'phpunittest');
-        if ($hash !== $dbhash) {
-            return array(PHPUNIT_EXITCODE_REINSTALL, '');
-        }
-
-        return array(0, '');
-    }
-
-    /**
-     * Drop all test site data.
-     *
-     * Note: To be used from CLI scripts only.
-     *
-     * @static
-     * @return void may terminate execution with exit code
-     */
-    public static function drop_site() {
-        global $DB, $CFG;
-
-        if (!self::is_test_site()) {
-            phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not drop non-test site!!');
-        }
-
-        // purge dataroot
-        self::reset_dataroot();
-        phpunit_bootstrap_initdataroot($CFG->dataroot);
-        $keep = array('.', '..', 'lock', 'webrunner.xml');
-        $files = scandir("$CFG->dataroot/phpunit");
-        foreach ($files as $file) {
-            if (in_array($file, $keep)) {
-                continue;
-            }
-            $path = "$CFG->dataroot/phpunit/$file";
-            if (is_dir($path)) {
-                remove_dir($path, false);
-            } else {
-                unlink($path);
-            }
-        }
-
-        // drop all tables
-        $tables = $DB->get_tables(false);
-        if (isset($tables['config'])) {
-            // config always last to prevent problems with interrupted drops!
-            unset($tables['config']);
-            $tables['config'] = 'config';
-        }
-        foreach ($tables as $tablename) {
-            $table = new xmldb_table($tablename);
-            $DB->get_manager()->drop_table($table);
-        }
-    }
-
-    /**
-     * Perform a fresh test site installation
-     *
-     * Note: To be used from CLI scripts only.
-     *
-     * @static
-     * @return void may terminate execution with exit code
-     */
-    public static function install_site() {
-        global $DB, $CFG;
-
-        if (!self::is_test_site()) {
-            phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not install on non-test site!!');
-        }
-
-        if ($DB->get_tables()) {
-            list($errorcode, $message) = phpunit_util::testing_ready_problem();
-            if ($errorcode) {
-                phpunit_bootstrap_error(PHPUNIT_EXITCODE_REINSTALL, 'Database tables already present, Moodle PHPUnit test environment can not be initialised');
-            } else {
-                phpunit_bootstrap_error(0, 'Moodle PHPUnit test environment is already initialised');
-            }
-        }
-
-        $options = array();
-        $options['adminpass'] = 'admin';
-        $options['shortname'] = 'phpunit';
-        $options['fullname'] = 'PHPUnit test site';
-
-        install_cli_database($options, false);
-
-        // install timezone info
-        $timezones = get_records_csv($CFG->libdir.'/timezone.txt', 'timezone');
-        update_timezone_records($timezones);
-
-        // add test db flag
-        $hash = phpunit_util::get_version_hash();
-        set_config('phpunittest', $hash);
-
-        // store data for all tables
-        $data = array();
-        $structure = array();
-        $tables = $DB->get_tables();
-        foreach ($tables as $table) {
-            $columns = $DB->get_columns($table);
-            $structure[$table] = $columns;
-            if (isset($columns['id']) and $columns['id']->auto_increment) {
-                $data[$table] = $DB->get_records($table, array(), 'id ASC');
-            } else {
-                // there should not be many of these
-                $data[$table] = $DB->get_records($table, array());
-            }
-        }
-        $data = serialize($data);
-        file_put_contents("$CFG->dataroot/phpunit/tabledata.ser", $data);
-        phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/tabledata.ser");
-
-        $structure = serialize($structure);
-        file_put_contents("$CFG->dataroot/phpunit/tablestructure.ser", $structure);
-        phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/tablestructure.ser");
-
-        // hash all plugin versions - helps with very fast detection of db structure changes
-        file_put_contents("$CFG->dataroot/phpunit/versionshash.txt", $hash);
-        phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/versionshash.txt", $hash);
-    }
-
-    /**
-     * Calculate unique version hash for all plugins and core.
-     * @static
-     * @return string sha1 hash
-     */
-    public static function get_version_hash() {
-        global $CFG;
-
-        if (self::$versionhash) {
-            return self::$versionhash;
-        }
-
-        $versions = array();
-
-        // main version first
-        $version = null;
-        include($CFG->dirroot.'/version.php');
-        $versions['core'] = $version;
-
-        // modules
-        $mods = get_plugin_list('mod');
-        ksort($mods);
-        foreach ($mods as $mod => $fullmod) {
-            $module = new stdClass();
-            $module->version = null;
-            include($fullmod.'/version.php');
-            $versions[$mod] = $module->version;
-        }
-
-        // now the rest of plugins
-        $plugintypes = get_plugin_types();
-        unset($plugintypes['mod']);
-        ksort($plugintypes);
-        foreach ($plugintypes as $type=>$unused) {
-            $plugs = get_plugin_list($type);
-            ksort($plugs);
-            foreach ($plugs as $plug=>$fullplug) {
-                $plugin = new stdClass();
-                $plugin->version = null;
-                @include($fullplug.'/version.php');
-                $versions[$plug] = $plugin->version;
-            }
-        }
-
-        self::$versionhash = sha1(serialize($versions));
-
-        return self::$versionhash;
-    }
-
-    /**
-     * Builds dirroot/phpunit.xml and dataroot/phpunit/webrunner.xml files using defaults from /phpunit.xml.dist
-     * @static
-     * @return bool true means main config file created, false means only dataroot file created
-     */
-    public static function build_config_file() {
-        global $CFG;
-
-        $template = '
-        <testsuite name="@component@">
-            <directory suffix="_test.php">@dir@</directory>
-        </testsuite>';
-        $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
-
-        $suites = '';
-
-        $plugintypes = get_plugin_types();
-        ksort($plugintypes);
-        foreach ($plugintypes as $type=>$unused) {
-            $plugs = get_plugin_list($type);
-            ksort($plugs);
-            foreach ($plugs as $plug=>$fullplug) {
-                if (!file_exists("$fullplug/tests/")) {
-                    continue;
-                }
-                $dir = substr($fullplug, strlen($CFG->dirroot)+1);
-                $dir .= '/tests';
-                $component = $type.'_'.$plug;
-
-                $suite = str_replace('@component@', $component, $template);
-                $suite = str_replace('@dir@', $dir, $suite);
-
-                $suites .= $suite;
-            }
-        }
-
-        $data = preg_replace('|<!--@plugin_suites_start@-->.*<!--@plugin_suites_end@-->|s', $suites, $data, 1);
-
-        $result = false;
-        if (is_writable($CFG->dirroot)) {
-            if ($result = file_put_contents("$CFG->dirroot/phpunit.xml", $data)) {
-                phpunit_boostrap_fix_file_permissions("$CFG->dirroot/phpunit.xml");
-            }
-        }
-
-        // relink - it seems that xml:base does not work in phpunit xml files, remove this nasty hack if you find a way to set xml base for relative refs
-        $data = str_replace('lib/phpunit/', $CFG->dirroot.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'phpunit'.DIRECTORY_SEPARATOR, $data);
-        $data = preg_replace('|<directory suffix="_test.php">([^<]+)</directory>|',
-            '<directory suffix="_test.php">'.$CFG->dirroot.(DIRECTORY_SEPARATOR === '\\' ? '\\\\' : DIRECTORY_SEPARATOR).'$1</directory>',
-            $data);
-        file_put_contents("$CFG->dataroot/phpunit/webrunner.xml", $data);
-        phpunit_boostrap_fix_file_permissions("$CFG->dataroot/phpunit/webrunner.xml");
-
-        return (bool)$result;
-    }
-
-    /**
-     * Builds phpunit.xml files for all components using defaults from /phpunit.xml.dist
-     *
-     * @static
-     * @return void, stops if can not write files
-     */
-    public static function build_component_config_files() {
-        global $CFG;
-
-        $template = '
-        <testsuites>
-            <testsuite name="@component@">
-                <directory suffix="_test.php">.</directory>
-            </testsuite>
-        </testsuites>';
-
-        // Use the upstream file as source for the distributed configurations
-        $ftemplate = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
-        $ftemplate = preg_replace('|<!--All core suites.*</testsuites>|s', '<!--@component_suite@-->', $ftemplate);
-
-        // Get all the components
-        $components = self::get_all_plugins_with_tests() + self::get_all_subsystems_with_tests();
-
-        // Get all the directories having tests
-        $directories = self::get_all_directories_with_tests();
-
-        // Find any directory not covered by proper components
-        $remaining = array_diff($directories, $components);
-
-        // Add them to the list of components
-        $components += $remaining;
-
-        // Create the corresponding phpunit.xml file for each component
-        foreach ($components as $cname => $cpath) {
-            // Calculate the component suite
-            $ctemplate = $template;
-            $ctemplate = str_replace('@component@', $cname, $ctemplate);
-
-            // Apply it to the file template
-            $fcontents = str_replace('<!--@component_suite@-->', $ctemplate, $ftemplate);
-
-            // fix link to schema
-            $level = substr_count(str_replace('\\', '/', $cpath), '/') - substr_count(str_replace('\\', '/', $CFG->dirroot), '/');
-            $fcontents = str_replace('lib/phpunit/phpunit.xsd', str_repeat('../', $level).'lib/phpunit/phpunit.xsd', $fcontents);
-            $fcontents = str_replace('lib/phpunit/bootstrap.php', str_repeat('../', $level).'lib/phpunit/bootstrap.php', $fcontents);
-
-            // Write the file
-            $result = false;
-            if (is_writable($cpath)) {
-                if ($result = (bool)file_put_contents("$cpath/phpunit.xml", $fcontents)) {
-                    phpunit_boostrap_fix_file_permissions("$cpath/phpunit.xml");
-                }
-            }
-            // Problems writing file, throw error
-            if (!$result) {
-                phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGWARNING, "Can not create $cpath/phpunit.xml configuration file, verify dir permissions");
-            }
-        }
-    }
-
-    /**
-     * Returns all the plugins having PHPUnit tests
-     *
-     * @return array all the plugins having PHPUnit tests
-     *
-     */
-    private static function get_all_plugins_with_tests() {
-        $pluginswithtests = array();
-
-        $plugintypes = get_plugin_types();
-        ksort($plugintypes);
-        foreach ($plugintypes as $type => $unused) {
-            $plugs = get_plugin_list($type);
-            ksort($plugs);
-            foreach ($plugs as $plug => $fullplug) {
-                // Look for tests recursively
-                if (self::directory_has_tests($fullplug)) {
-                    $pluginswithtests[$type . '_' . $plug] = $fullplug;
-                }
-            }
-        }
-        return $pluginswithtests;
-    }
-
-    /**
-     * Returns all the subsystems having PHPUnit tests
-     *
-     * Note we are hacking here the list of subsystems
-     * to cover some well-known subsystems that are not properly
-     * returned by the {@link get_core_subsystems()} function.
-     *
-     * @return array all the subsystems having PHPUnit tests
-     */
-    private static function get_all_subsystems_with_tests() {
-        global $CFG;
-
-        $subsystemswithtests = array();
-
-        $subsystems = get_core_subsystems();
-
-        // Hack the list a bit to cover some well-known ones
-        $subsystems['backup'] = 'backup';
-        $subsystems['db-dml'] = 'lib/dml';
-        $subsystems['db-ddl'] = 'lib/ddl';
-
-        ksort($subsystems);
-        foreach ($subsystems as $subsys => $relsubsys) {
-            if ($relsubsys === null) {
-                continue;
-            }
-            $fullsubsys = $CFG->dirroot . '/' . $relsubsys;
-            if (!is_dir($fullsubsys)) {
-                continue;
-            }
-            // Look for tests recursively
-            if (self::directory_has_tests($fullsubsys)) {
-                $subsystemswithtests['core_' . $subsys] = $fullsubsys;
-            }
-        }
-        return $subsystemswithtests;
-    }
-
-    /**
-     * Returns all the directories having tests
-     *
-     * @return array all directories having tests
-     */
-    private static function get_all_directories_with_tests() {
-        global $CFG;
-
-        $dirs = array();
-        $dirite = new RecursiveDirectoryIterator($CFG->dirroot);
-        $iteite = new RecursiveIteratorIterator($dirite);
-        $sep = preg_quote(DIRECTORY_SEPARATOR, '|');
-        $regite = new RegexIterator($iteite, '|'.$sep.'tests'.$sep.'.*_test\.php$|');
-        foreach ($regite as $path => $element) {
-            $key = dirname(dirname($path));
-            $value = trim(str_replace('/', '_', str_replace($CFG->dirroot, '', $key)), '_');
-            $dirs[$key] = $value;
-        }
-        ksort($dirs);
-        return array_flip($dirs);
-    }
-
-    /**
-     * Returns if a given directory has tests (recursively)
-     *
-     * @param $dir string full path to the directory to look for phpunit tests
-     * @return bool if a given directory has tests (true) or no (false)
-     */
-    private static function directory_has_tests($dir) {
-        if (!is_dir($dir)) {
-            return false;
-        }
-
-        $dirite = new RecursiveDirectoryIterator($dir);
-        $iteite = new RecursiveIteratorIterator($dirite);
-        $sep = preg_quote(DIRECTORY_SEPARATOR, '|');
-        $regite = new RegexIterator($iteite, '|'.$sep.'tests'.$sep.'.*_test\.php$|');
-        $regite->rewind();
-        if ($regite->valid()) {
-            return true;
-        }
-        return false;
-    }
-}
-
-
-/**
- * Simplified emulation test case for legacy SimpleTest.
- *
- * Note: this is supposed to work for very simple tests only.
- *
- * @deprecated since 2.3
- * @package    core
- * @category   phpunit
- * @author     Petr Skoda
- * @copyright  2012 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class UnitTestCase extends PHPUnit_Framework_TestCase {
-
-    /**
-     * @deprecated since 2.3
-     * @param bool $expected
-     * @param string $message
-     * @return void
-     */
-    public function expectException($expected, $message = '') {
-        // alternatively use phpdocs: @expectedException ExceptionClassName
-        if (!$expected) {
-            return;
-        }
-        $this->setExpectedException('moodle_exception', $message);
-    }
-
-    /**
-     * @deprecated since 2.3
-     * @param bool $expected
-     * @param string $message
-     * @return void
-     */
-    public function expectError($expected = false, $message = '') {
-        // alternatively use phpdocs: @expectedException PHPUnit_Framework_Error
-        if (!$expected) {
-            return;
-        }
-        $this->setExpectedException('PHPUnit_Framework_Error', $message);
-    }
-
-    /**
-     * @deprecated since 2.3
-     * @static
-     * @param mixed $actual
-     * @param string $messages
-     * @return void
-     */
-    public static function assertTrue($actual, $messages = '') {
-        parent::assertTrue((bool)$actual, $messages);
-    }
-
-    /**
-     * @deprecated since 2.3
-     * @static
-     * @param mixed $actual
-     * @param string $messages
-     * @return void
-     */
-    public static function assertFalse($actual, $messages = '') {
-        parent::assertFalse((bool)$actual, $messages);
-    }
-
-    /**
-     * @deprecated since 2.3
-     * @static
-     * @param mixed $expected
-     * @param mixed $actual
-     * @param string $message
-     * @return void
-     */
-    public static function assertEqual($expected, $actual, $message = '') {
-        parent::assertEquals($expected, $actual, $message);
-    }
-
-    /**
-     * @deprecated since 2.3
-     * @static
-     * @param mixed $expected
-     * @param mixed $actual
-     * @param float|int $margin
-     * @param string $message
-     * @return void
-     */
-    public static function assertWithinMargin($expected, $actual, $margin, $message = '') {
-        parent::assertEquals($expected, $actual, '', $margin, $message);
-    }
-
-    /**
-     * @deprecated since 2.3
-     * @static
-     * @param mixed $expected
-     * @param mixed $actual
-     * @param string $message
-     * @return void
-     */
-    public static function assertNotEqual($expected, $actual, $message = '') {
-        parent::assertNotEquals($expected, $actual, $message);
-    }
-
-    /**
-     * @deprecated since 2.3
-     * @static
-     * @param mixed $expected
-     * @param mixed $actual
-     * @param string $message
-     * @return void
-     */
-    public static function assertIdentical($expected, $actual, $message = '') {
-        parent::assertSame($expected, $actual, $message);
-    }
-
-    /**
-     * @deprecated since 2.3
-     * @static
-     * @param mixed $expected
-     * @param mixed $actual
-     * @param string $message
-     * @return void
-     */
-    public static function assertNotIdentical($expected, $actual, $message = '') {
-        parent::assertNotSame($expected, $actual, $message);
-    }
-
-    /**
-     * @deprecated since 2.3
-     * @static
-     * @param mixed $actual
-     * @param mixed $expected
-     * @param string $message
-     * @return void
-     */
-    public static function assertIsA($actual, $expected, $message = '') {
-        if ($expected === 'array') {
-            parent::assertEquals('array', gettype($actual), $message);
-        } else {
-            parent::assertInstanceOf($expected, $actual, $message);
-        }
-    }
-
-    /**
-     * @deprecated since 2.3
-     * @static
-     * @param mixed $pattern
-     * @param mixed $string
-     * @param string $message
-     * @return void
-     */
-    public static function assertPattern($pattern, $string, $message = '') {
-        parent::assertRegExp($pattern, $string, $message);
-    }
-
-    /**
-     * @deprecated since 2.3
-     * @static
-     * @param mixed $pattern
-     * @param mixed $string
-     * @param string $message
-     * @return void
-     */
-    public static function assertNotPattern($pattern, $string, $message = '') {
-        parent::assertNotRegExp($pattern, $string, $message);
-    }
-}
-
-
-/**
- * The simplest PHPUnit test case customised for Moodle
- *
- * It is intended for isolated tests that do not modify database or any globals.
- *
- * @package    core
- * @category   phpunit
- * @copyright  2012 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class basic_testcase extends PHPUnit_Framework_TestCase {
-
-    /**
-     * Constructs a test case with the given name.
-     *
-     * Note: use setUp() or setUpBeforeClass() in your test cases.
-     *
-     * @param string $name
-     * @param array  $data
-     * @param string $dataName
-     */
-    final public function __construct($name = null, array $data = array(), $dataName = '') {
-        parent::__construct($name, $data, $dataName);
-
-        $this->setBackupGlobals(false);
-        $this->setBackupStaticAttributes(false);
-        $this->setRunTestInSeparateProcess(false);
-    }
-
-    /**
-     * Runs the bare test sequence and log any changes in global state or database.
-     * @return void
-     */
-    final public function runBare() {
-        global $DB;
-
-        try {
-            parent::runBare();
-        } catch (Exception $e) {
-            // cleanup after failed expectation
-            phpunit_util::reset_all_data();
-            throw $e;
-        }
-
-        if ($DB->is_transaction_started()) {
-            phpunit_util::reset_all_data();
-            throw new coding_exception('basic_testcase '.$this->getName().' is not supposed to use database transactions!');
-        }
-
-        phpunit_util::reset_all_data(true);
-    }
-}
-
-
-/**
- * Advanced PHPUnit test case customised for Moodle.
- *
- * @package    core
- * @category   phpunit
- * @copyright  2012 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class advanced_testcase extends PHPUnit_Framework_TestCase {
-    /** @var bool automatically reset everything? null means log changes */
-    private $resetAfterTest;
-
-    /** @var moodle_transaction */
-    private $testdbtransaction;
-
-    /**
-     * Constructs a test case with the given name.
-     *
-     * Note: use setUp() or setUpBeforeClass() in your test cases.
-     *
-     * @param string $name
-     * @param array  $data
-     * @param string $dataName
-     */
-    final public function __construct($name = null, array $data = array(), $dataName = '') {
-        parent::__construct($name, $data, $dataName);
-
-        $this->setBackupGlobals(false);
-        $this->setBackupStaticAttributes(false);
-        $this->setRunTestInSeparateProcess(false);
-    }
-
-    /**
-     * Runs the bare test sequence.
-     * @return void
-     */
-    final public function runBare() {
-        global $DB;
-
-        if (phpunit_util::$lastdbwrites != $DB->perf_get_writes()) {
-            // this happens when previous test does not reset, we can not use transactions
-            $this->testdbtransaction = null;
-
-        } else if ($DB->get_dbfamily() === 'postgres' or $DB->get_dbfamily() === 'mssql') {
-            // database must allow rollback of DDL, so no mysql here
-            $this->testdbtransaction = $DB->start_delegated_transaction();
-        }
-
-        try {
-            parent::runBare();
-            // set DB reference in case somebody mocked it in test
-            $DB = phpunit_util::get_global_backup('DB');
-        } catch (Exception $e) {
-            // cleanup after failed expectation
-            phpunit_util::reset_all_data();
-            throw $e;
-        }
-
-        if (!$this->testdbtransaction or $this->testdbtransaction->is_disposed()) {
-            $this->testdbtransaction = null;
-        }
-
-        if ($this->resetAfterTest === true) {
-            if ($this->testdbtransaction) {
-                $DB->force_transaction_rollback();
-                phpunit_util::reset_all_database_sequences();
-                phpunit_util::$lastdbwrites = $DB->perf_get_writes(); // no db reset necessary
-            }
-            phpunit_util::reset_all_data();
-
-        } else if ($this->resetAfterTest === false) {
-            if ($this->testdbtransaction) {
-                $this->testdbtransaction->allow_commit();
-            }
-            // keep all data untouched for other tests
-
-        } else {
-            // reset but log what changed
-            if ($this->testdbtransaction) {
-                try {
-                    $this->testdbtransaction->allow_commit();
-                } catch (dml_transaction_exception $e) {
-                    phpunit_util::reset_all_data();
-                    throw new coding_exception('Invalid transaction state detected in test '.$this->getName());
-                }
-            }
-            phpunit_util::reset_all_data(true);
-        }
-
-        // make sure test did not forget to close transaction
-        if ($DB->is_transaction_started()) {
-            phpunit_util::reset_all_data();
-            if ($this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED
-                    or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED
-                    or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) {
-                throw new coding_exception('Test '.$this->getName().' did not close database transaction');
-            }
-        }
-    }
-
-    /**
-     * Creates a new FlatXmlDataSet with the given $xmlFile. (absolute path.)
-     *
-     * @param string $xmlFile
-     * @return PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet
-     */
-    protected function createFlatXMLDataSet($xmlFile) {
-        return new PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet($xmlFile);
-    }
-
-    /**
-     * Creates a new XMLDataSet with the given $xmlFile. (absolute path.)
-     *
-     * @param string $xmlFile
-     * @return PHPUnit_Extensions_Database_DataSet_XmlDataSet
-     */
-    protected function createXMLDataSet($xmlFile) {
-        return new PHPUnit_Extensions_Database_DataSet_XmlDataSet($xmlFile);
-    }
-
-    /**
-     * Creates a new CsvDataSet from the given array of csv files. (absolute paths.)
-     *
-     * @param array $files array tablename=>cvsfile
-     * @param string $delimiter
-     * @param string $enclosure
-     * @param string $escape
-     * @return PHPUnit_Extensions_Database_DataSet_CsvDataSet
-     */
-    protected function createCsvDataSet($files, $delimiter = ',', $enclosure = '"', $escape = '"') {
-        $dataSet = new PHPUnit_Extensions_Database_DataSet_CsvDataSet($delimiter, $enclosure, $escape);
-        foreach($files as $table=>$file) {
-            $dataSet->addTable($table, $file);
-        }
-        return $dataSet;
-    }
-
-    /**
-     * Creates new ArrayDataSet from given array
-     *
-     * @param array $data array of tables, first row in each table is columns
-     * @return phpunit_ArrayDataSet
-     */
-    protected function createArrayDataSet(array $data) {
-        return new phpunit_ArrayDataSet($data);
-    }
-
-    /**
-     * Load date into moodle database tables from standard PHPUnit data set.
-     *
-     * Note: it is usually better to use data generators
-     *
-     * @param PHPUnit_Extensions_Database_DataSet_IDataSet $dataset
-     * @return void
-     */
-    protected function loadDataSet(PHPUnit_Extensions_Database_DataSet_IDataSet $dataset) {
-        global $DB;
-
-        $structure = phpunit_util::get_tablestructure();
-
-        foreach($dataset->getTableNames() as $tablename) {
-            $table = $dataset->getTable($tablename);
-            $metadata = $dataset->getTableMetaData($tablename);
-            $columns = $metadata->getColumns();
-
-            $doimport = false;
-            if (isset($structure[$tablename]['id']) and $structure[$tablename]['id']->auto_increment) {
-                $doimport = in_array('id', $columns);
-            }
-
-            for($r=0; $r<$table->getRowCount(); $r++) {
-                $record = $table->getRow($r);
-                if ($doimport) {
-                    $DB->import_record($tablename, $record);
-                } else {
-                    $DB->insert_record($tablename, $record);
-                }
-            }
-            if ($doimport) {
-                $DB->get_manager()->reset_sequence(new xmldb_table($tablename));
-            }
-        }
-    }
-
-    /**
-     * Call this method from test if you want to make sure that
-     * the resetting of database is done the slow way without transaction
-     * rollback.
-     *
-     * This is useful especially when testing stuff that is not compatible with transactions.
-     *
-     * @return void
-     */
-    public function preventResetByRollback() {
-        if ($this->testdbtransaction and !$this->testdbtransaction->is_disposed()) {
-            $this->testdbtransaction->allow_commit();
-            $this->testdbtransaction = null;
-        }
-    }
-
-    /**
-     * Reset everything after current test.
-     * @param bool $reset true means reset state back, false means keep all data for the next test,
-     *      null means reset state and show warnings if anything changed
-     * @return void
-     */
-    public function resetAfterTest($reset = true) {
-        $this->resetAfterTest = $reset;
-    }
-
-    /**
-     * Cleanup after all tests are executed.
-     *
-     * Note: do not forget to call this if overridden...
-     *
-     * @static
-     * @return void
-     */
-    public static function tearDownAfterClass() {
-        phpunit_util::reset_all_data();
-    }
-
-    /**
-     * Reset all database tables, restore global state and clear caches and optionally purge dataroot dir.
-     * @static
-     * @return void
-     */
-    public static function resetAllData() {
-        phpunit_util::reset_all_data();
-    }
-
-    /**
-     * Set current $USER, reset access cache.
-     * @static
-     * @param null|int|stdClass $user user record, null means non-logged-in, integer means userid
-     * @return void
-     */
-    public static function setUser($user = null) {
-        global $CFG, $DB;
-
-        if (is_object($user)) {
-            $user = clone($user);
-        } else if (!$user) {
-            $user = new stdClass();
-            $user->id = 0;
-            $user->mnethostid = $CFG->mnet_localhost_id;
-        } else {
-            $user = $DB->get_record('user', array('id'=>$user));
-        }
-        unset($user->description);
-        unset($user->access);
-
-        session_set_user($user);
-    }
-
-    /**
-     * Get data generator
-     * @static
-     * @return phpunit_data_generator
-     */
-    public static function getDataGenerator() {
-        return phpunit_util::get_data_generator();
-    }
-
-    /**
-     * Recursively visit all the files in the source tree. Calls the callback
-     * function with the pathname of each file found.
-     *
-     * @param string $path the folder to start searching from.
-     * @param string $callback the method of this class to call with the name of each file found.
-     * @param string $fileregexp a regexp used to filter the search (optional).
-     * @param bool $exclude If true, pathnames that match the regexp will be ignored. If false,
-     *     only files that match the regexp will be included. (default false).
-     * @param array $ignorefolders will not go into any of these folders (optional).
-     * @return void
-     */
-    public function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) {
-        $files = scandir($path);
-
-        foreach ($files as $file) {
-            $filepath = $path .'/'. $file;
-            if (strpos($file, '.') === 0) {
-                /// Don't check hidden files.
-                continue;
-            } else if (is_dir($filepath)) {
-                if (!in_array($filepath, $ignorefolders)) {
-                    $this->recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders);
-                }
-            } else if ($exclude xor preg_match($fileregexp, $filepath)) {
-                $this->$callback($filepath);
-            }
-        }
-    }
-}
-
-
-/**
- * based on array iterator code from PHPUnit documentation by Sebastian Bergmann
- * and added new constructor parameter for different array types.
- */
-class phpunit_ArrayDataSet extends PHPUnit_Extensions_Database_DataSet_AbstractDataSet {
-    /**
-     * @var array
-     */
-    protected $tables = array();
-
-    /**
-     * @param array $data
-     */
-    public function __construct(array $data) {
-        foreach ($data AS $tableName => $rows) {
-            $firstrow = reset($rows);
-
-            if (array_key_exists(0, $firstrow)) {
-                // columns in first row
-                $columnsInFirstRow = true;
-                $columns = $firstrow;
-                $key = key($rows);
-                unset($rows[$key]);
-            } else {
-                // column name is in each row as key
-                $columnsInFirstRow = false;
-                $columns = array_keys($firstrow);
-            }
-
-            $metaData = new PHPUnit_Extensions_Database_DataSet_DefaultTableMetaData($tableName, $columns);
-            $table = new PHPUnit_Extensions_Database_DataSet_DefaultTable($metaData);
-
-            foreach ($rows AS $row) {
-                if ($columnsInFirstRow) {
-                    $row = array_combine($columns, $row);
-                }
-                $table->addRow($row);
-            }
-            $this->tables[$tableName] = $table;
-        }
-    }
-
-    protected function createIterator($reverse = FALSE) {
-        return new PHPUnit_Extensions_Database_DataSet_DefaultTableIterator($this->tables, $reverse);
-    }
-
-    public function getTable($tableName) {
-        if (!isset($this->tables[$tableName])) {
-            throw new InvalidArgumentException("$tableName is not a table in the current database.");
-        }
-
-        return $this->tables[$tableName];
-    }
-}
-
-
-/**
- * Special test case for testing of DML drivers and DDL layer.
- *
- * Note: Use only 'test_table*' names when creating new tables.
- *
- * For DML/DDL developers: you can add following settings to config.php if you want to test different driver than the main one,
- *                         the reason is to allow testing of incomplete drivers that do not allow full PHPUnit environment
- *                         initialisation (the database can be empty).
- * $CFG->phpunit_extra_drivers = array(
- *      1=>array('dbtype'=>'mysqli', 'dbhost'=>'localhost', 'dbname'=>'moodle', 'dbuser'=>'root', 'dbpass'=>'', 'prefix'=>'phpu2_'),
- *      2=>array('dbtype'=>'pgsql', 'dbhost'=>'localhost', 'dbname'=>'moodle', 'dbuser'=>'postgres', 'dbpass'=>'', 'prefix'=>'phpu2_'),
- *      3=>array('dbtype'=>'sqlsrv', 'dbhost'=>'127.0.0.1', 'dbname'=>'moodle', 'dbuser'=>'sa', 'dbpass'=>'', 'prefix'=>'phpu2_'),
- *      4=>array('dbtype'=>'oci', 'dbhost'=>'127.0.0.1', 'dbname'=>'XE', 'dbuser'=>'sa', 'dbpass'=>'', 'prefix'=>'t_'),
- * );
- * define('PHPUNIT_TEST_DRIVER')=1; //number is index in the previous array
- *
- * @package    core
- * @category   phpunit
- * @copyright  2012 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class database_driver_testcase extends PHPUnit_Framework_TestCase {
-    /** @var moodle_database connection to extra database */
-    private static $extradb = null;
-
-    /** @var moodle_database used in these tests*/
-    protected $tdb;
-
-    /**
-     * Constructs a test case with the given name.
-     *
-     * @param string $name
-     * @param array  $data
-     * @param string $dataName
-     */
-    final public function __construct($name = null, array $data = array(), $dataName = '') {
-        parent::__construct($name, $data, $dataName);
-
-        $this->setBackupGlobals(false);
-        $this->setBackupStaticAttributes(false);
-        $this->setRunTestInSeparateProcess(false);
-    }
-
-    public static function setUpBeforeClass() {
-        global $CFG;
-        parent::setUpBeforeClass();
-
-        if (!defined('PHPUNIT_TEST_DRIVER')) {
-            // use normal $DB
-            return;
-        }
-
-        if (!isset($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER])) {
-            throw new exception('Can not find driver configuration options with index: '.PHPUNIT_TEST_DRIVER);
-        }
-
-        $dblibrary = empty($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dblibrary']) ? 'native' : $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dblibrary'];
-        $dbtype = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbtype'];
-        $dbhost = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbhost'];
-        $dbname = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbname'];
-        $dbuser = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbuser'];
-        $dbpass = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbpass'];
-        $prefix = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['prefix'];
-        $dboptions = empty($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dboptions']) ? array() : $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dboptions'];
-
-        $classname = "{$dbtype}_{$dblibrary}_moodle_database";
-        require_once("$CFG->libdir/dml/$classname.php");
-        $d = new $classname();
-        if (!$d->driver_installed()) {
-            throw new exception('Database driver for '.$classname.' is not installed');
-        }
-
-        $d->connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
-
-        self::$extradb = $d;
-    }
-
-    protected function setUp() {
-        global $DB;
-        parent::setUp();
-
-        if (self::$extradb) {
-            $this->tdb = self::$extradb;
-        } else {
-            $this->tdb = $DB;
-        }
-    }
-
-    protected function tearDown() {
-        // delete all test tables
-        $dbman = $this->tdb->get_manager();
-        $tables = $this->tdb->get_tables(false);
-        foreach($tables as $tablename) {
-            if (strpos($tablename, 'test_table') === 0) {
-                $table = new xmldb_table($tablename);
-                $dbman->drop_table($table);
-            }
-        }
-        parent::tearDown();
-    }
+require_once(__DIR__.'/classes/util.php');
+require_once(__DIR__.'/classes/basic_testcase.php');
+require_once(__DIR__.'/classes/database_driver_testcase.php');
+require_once(__DIR__.'/classes/arraydataset.php');
+require_once(__DIR__.'/classes/advanced_testcase.php');
+require_once(__DIR__.'/classes/unittestcase.php');
 
-    public static function tearDownAfterClass() {
-        if (self::$extradb) {
-            self::$extradb->dispose();
-            self::$extradb = null;
-        }
-        phpunit_util::reset_all_data();
-        parent::tearDownAfterClass();
-    }
-}
similarity index 63%
rename from lib/tests/phpunit_test.php
rename to lib/phpunit/tests/advanced_test.php
index d24a0d1..302ab1f 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * PHPUnit integration unit tests
+ * PHPUnit integration tests
  *
  * @package    core
  * @category   phpunit
 defined('MOODLE_INTERNAL') || die();
 
 
-/**
- * Test basic_testcase extra features and PHPUnit Moodle integration.
- *
- * @package    core
- * @category   phpunit
- * @copyright  2012 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class core_phpunit_basic_testcase extends basic_testcase {
-
-    /**
-     * Tests that bootstrapping has occurred correctly
-     * @return void
-     */
-    public function test_bootstrap() {
-        global $CFG;
-        $this->assertTrue(isset($CFG->httpswwwroot));
-        $this->assertEquals($CFG->httpswwwroot, $CFG->wwwroot);
-        $this->assertEquals($CFG->prefix, $CFG->phpunit_prefix);
-    }
-
-    /**
-     * This is just a verification if I understand the PHPUnit assert docs right --skodak
-     * @return void
-     */
-    public function test_assert_behaviour() {
-        // arrays
-        $a = array('a', 'b', 'c');
-        $b = array('a', 'c', 'b');
-        $c = array('a', 'b', 'c');
-        $d = array('a', 'b', 'C');
-        $this->assertNotEquals($a, $b);
-        $this->assertNotEquals($a, $d);
-        $this->assertEquals($a, $c);
-        $this->assertEquals($a, $b, '', 0, 10, true);
-
-        // objects
-        $a = new stdClass();
-        $a->x = 'x';
-        $a->y = 'y';
-        $b = new stdClass(); // switched order
-        $b->y = 'y';
-        $b->x = 'x';
-        $c = $a;
-        $d = new stdClass();
-        $d->x = 'x';
-        $d->y = 'y';
-        $d->z = 'z';
-        $this->assertEquals($a, $b);
-        $this->assertNotSame($a, $b);
-        $this->assertEquals($a, $c);
-        $this->assertSame($a, $c);
-        $this->assertNotEquals($a, $d);
-
-        // string comparison
-        $this->assertEquals(1, '1');
-        $this->assertEquals(null, '');
-
-        $this->assertNotEquals(1, '1 ');
-        $this->assertNotEquals(0, '');
-        $this->assertNotEquals(null, '0');
-        $this->assertNotEquals(array(), '');
-
-        // other comparison
-        $this->assertEquals(null, null);
-        $this->assertEquals(false, null);
-        $this->assertEquals(0, null);
-
-        // emptiness
-        $this->assertEmpty(0);
-        $this->assertEmpty(0.0);
-        $this->assertEmpty('');
-        $this->assertEmpty('0');
-        $this->assertEmpty(false);
-        $this->assertEmpty(null);
-        $this->assertEmpty(array());
-
-        $this->assertNotEmpty(1);
-        $this->assertNotEmpty(0.1);
-        $this->assertNotEmpty(-1);
-        $this->assertNotEmpty(' ');
-        $this->assertNotEmpty('0 ');
-        $this->assertNotEmpty(true);
-        $this->assertNotEmpty(array(null));
-        $this->assertNotEmpty(new stdClass());
-    }
-
-// Uncomment following tests to see logging of unexpected changes in global state and database
-/*
-    public function test_db_modification() {
-        global $DB;
-        $DB->set_field('user', 'confirmed', 1, array('id'=>-1));
-    }
-
-    public function test_cfg_modification() {
-        global $CFG;
-        $CFG->xx = 'yy';
-        unset($CFG->admin);
-        $CFG->rolesactive = 0;
-    }
-
-    public function test_user_modification() {
-        global $USER;
-        $USER->id = 10;
-    }
-
-    public function test_course_modification() {
-        global $COURSE;
-        $COURSE->id = 10;
-    }
-
-    public function test_all_modifications() {
-        global $DB, $CFG, $USER, $COURSE;
-        $DB->set_field('user', 'confirmed', 1, array('id'=>-1));
-        $CFG->xx = 'yy';
-        unset($CFG->admin);
-        $CFG->rolesactive = 0;
-        $USER->id = 10;
-        $COURSE->id = 10;
-    }
-
-    public function test_transaction_problem() {
-        global $DB;
-        $DB->start_delegated_transaction();
-    }
-*/
-}
-
-
 /**
  * Test advanced_testcase extra features.
  *
@@ -172,6 +43,7 @@ class core_phpunit_advanced_testcase extends advanced_testcase {
         $this->assertSame($_SESSION['USER'], $USER);
 
         $user = $DB->get_record('user', array('id'=>2));
+        $this->assertNotEmpty($user);
         $this->setUser($user);
         $this->assertEquals(2, $USER->id);
         $this->assertEquals(2, $_SESSION['USER']->id);
@@ -188,6 +60,7 @@ class core_phpunit_advanced_testcase extends advanced_testcase {
         $this->assertSame($_SESSION['USER'], $USER);
 
         $USER = $DB->get_record('user', array('id'=>1));
+        $this->assertNotEmpty($USER);
         $this->assertEquals(1, $USER->id);
         $this->assertEquals(1, $_SESSION['USER']->id);
         $this->assertSame($_SESSION['USER'], $USER);
@@ -197,6 +70,26 @@ class core_phpunit_advanced_testcase extends advanced_testcase {
         $this->assertSame($_SESSION['USER'], $USER);
     }
 
+    public function test_set_admin_user() {
+        global $USER;
+
+        $this->resetAfterTest(true);
+
+        $this->setAdminUser();
+        $this->assertEquals($USER->id, 2);
+        $this->assertTrue(is_siteadmin());
+    }
+
+    public function test_set_guest_user() {
+        global $USER;
+
+        $this->resetAfterTest(true);
+
+        $this->setGuestUser();
+        $this->assertEquals($USER->id, 1);
+        $this->assertTrue(isguestuser());
+    }
+
     public function test_database_reset() {
         global $DB;
 
@@ -391,65 +284,3 @@ class core_phpunit_advanced_testcase extends advanced_testcase {
         $this->assertTrue($DB->record_exists('user', array('username'=>'onemore')));
     }
 }
-
-
-/**
- * Test data generator
- *
- * @package    core
- * @category   phpunit
- * @copyright  2012 Petr Skoda {@link http://skodak.org}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class core_phpunit_generator_testcase extends advanced_testcase {
-    public function test_create() {
-        global $DB;
-
-        $this->resetAfterTest(true);
-        $generator = $this->getDataGenerator();
-
-        $count = $DB->count_records('user');
-        $user = $generator->create_user();
-        $this->assertEquals($count+1, $DB->count_records('user'));
-
-        $count = $DB->count_records('course_categories');
-        $category = $generator->create_category();
-        $this->assertEquals($count+1, $DB->count_records('course_categories'));
-
-        $count = $DB->count_records('course');
-        $course = $generator->create_course();
-        $this->assertEquals($count+1, $DB->count_records('course'));
-
-        $section = $generator->create_course_section(array('course'=>$course->id, 'section'=>3));
-        $this->assertEquals($course->id, $section->course);
-
-        $scale = $generator->create_scale();
-        $this->assertNotEmpty($scale);
-    }
-
-    public function test_create_module() {
-        global $CFG, $SITE;
-        if (!file_exists("$CFG->dirroot/mod/page/")) {
-            $this->markTestSkipped('Can not find standard Page module');
-        }
-
-        $this->resetAfterTest(true);
-        $generator = $this->getDataGenerator();
-
-        $page = $generator->create_module('page', array('course'=>$SITE->id));
-        $this->assertNotEmpty($page);
-    }
-
-    public function test_create_block() {
-        global $CFG;
-        if (!file_exists("$CFG->dirroot/blocks/online_users/")) {
-            $this->markTestSkipped('Can not find standard Online users block');
-        }
-
-        $this->resetAfterTest(true);
-        $generator = $this->getDataGenerator();
-
-        $page = $generator->create_block('online_users');
-        $this->assertNotEmpty($page);
-    }
-}
diff --git a/lib/phpunit/tests/basic_test.php b/lib/phpunit/tests/basic_test.php
new file mode 100644 (file)
index 0000000..2ad03cb
--- /dev/null
@@ -0,0 +1,155 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * PHPUnit integration tests
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Test basic_testcase extra features and PHPUnit Moodle integration.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_phpunit_basic_testcase extends basic_testcase {
+
+    /**
+     * Tests that bootstrapping has occurred correctly
+     * @return void
+     */
+    public function test_bootstrap() {
+        global $CFG;
+        $this->assertTrue(isset($CFG->httpswwwroot));
+        $this->assertEquals($CFG->httpswwwroot, $CFG->wwwroot);
+        $this->assertEquals($CFG->prefix, $CFG->phpunit_prefix);
+    }
+
+    /**
+     * This is just a verification if I understand the PHPUnit assert docs right --skodak
+     * @return void
+     */
+    public function test_assert_behaviour() {
+        // arrays
+        $a = array('a', 'b', 'c');
+        $b = array('a', 'c', 'b');
+        $c = array('a', 'b', 'c');
+        $d = array('a', 'b', 'C');
+        $this->assertNotEquals($a, $b);
+        $this->assertNotEquals($a, $d);
+        $this->assertEquals($a, $c);
+        $this->assertEquals($a, $b, '', 0, 10, true);
+
+        // objects
+        $a = new stdClass();
+        $a->x = 'x';
+        $a->y = 'y';
+        $b = new stdClass(); // switched order
+        $b->y = 'y';
+        $b->x = 'x';
+        $c = $a;
+        $d = new stdClass();
+        $d->x = 'x';
+        $d->y = 'y';
+        $d->z = 'z';
+        $this->assertEquals($a, $b);
+        $this->assertNotSame($a, $b);
+        $this->assertEquals($a, $c);
+        $this->assertSame($a, $c);
+        $this->assertNotEquals($a, $d);
+
+        // string comparison
+        $this->assertEquals(1, '1');
+        $this->assertEquals(null, '');
+
+        $this->assertNotEquals(1, '1 ');
+        $this->assertNotEquals(0, '');
+        $this->assertNotEquals(null, '0');
+        $this->assertNotEquals(array(), '');
+
+        // other comparison
+        $this->assertEquals(null, null);
+        $this->assertEquals(false, null);
+        $this->assertEquals(0, null);
+
+        // emptiness
+        $this->assertEmpty(0);
+        $this->assertEmpty(0.0);
+        $this->assertEmpty('');
+        $this->assertEmpty('0');
+        $this->assertEmpty(false);
+        $this->assertEmpty(null);
+        $this->assertEmpty(array());
+
+        $this->assertNotEmpty(1);
+        $this->assertNotEmpty(0.1);
+        $this->assertNotEmpty(-1);
+        $this->assertNotEmpty(' ');
+        $this->assertNotEmpty('0 ');
+        $this->assertNotEmpty(true);
+        $this->assertNotEmpty(array(null));
+        $this->assertNotEmpty(new stdClass());
+    }
+
+// Uncomment following tests to see logging of unexpected changes in global state and database
+    /*
+        public function test_db_modification() {
+            global $DB;
+            $DB->set_field('user', 'confirmed', 1, array('id'=>-1));
+        }
+
+        public function test_cfg_modification() {
+            global $CFG;
+            $CFG->xx = 'yy';
+            unset($CFG->admin);
+            $CFG->rolesactive = 0;
+        }
+
+        public function test_user_modification() {
+            global $USER;
+            $USER->id = 10;
+        }
+
+        public function test_course_modification() {
+            global $COURSE;
+            $COURSE->id = 10;
+        }
+
+        public function test_all_modifications() {
+            global $DB, $CFG, $USER, $COURSE;
+            $DB->set_field('user', 'confirmed', 1, array('id'=>-1));
+            $CFG->xx = 'yy';
+            unset($CFG->admin);
+            $CFG->rolesactive = 0;
+            $USER->id = 10;
+            $COURSE->id = 10;
+        }
+
+        public function test_transaction_problem() {
+            global $DB;
+            $DB->start_delegated_transaction();
+        }
+    */
+}
diff --git a/lib/phpunit/tests/generator_test.php b/lib/phpunit/tests/generator_test.php
new file mode 100644 (file)
index 0000000..eb5df6b
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * PHPUnit integration tests
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Test data generator
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_phpunit_generator_testcase extends advanced_testcase {
+    public function test_create() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+        $generator = $this->getDataGenerator();
+
+        $count = $DB->count_records('user');
+        $user = $generator->create_user();
+        $this->assertEquals($count+1, $DB->count_records('user'));
+
+        $count = $DB->count_records('course_categories');
+        $category = $generator->create_category();
+        $this->assertEquals($count+1, $DB->count_records('course_categories'));
+
+        $count = $DB->count_records('course');
+        $course = $generator->create_course();
+        $this->assertEquals($count+1, $DB->count_records('course'));
+
+        $section = $generator->create_course_section(array('course'=>$course->id, 'section'=>3));
+        $this->assertEquals($course->id, $section->course);
+
+        $scale = $generator->create_scale();
+        $this->assertNotEmpty($scale);
+    }
+
+    public function test_create_module() {
+        global $CFG, $SITE;
+        if (!file_exists("$CFG->dirroot/mod/page/")) {
+            $this->markTestSkipped('Can not find standard Page module');
+        }
+
+        $this->resetAfterTest(true);
+        $generator = $this->getDataGenerator();
+
+        $page = $generator->create_module('page', array('course'=>$SITE->id));
+        $this->assertNotEmpty($page);
+    }
+
+    public function test_create_block() {
+        global $CFG;
+        if (!file_exists("$CFG->dirroot/blocks/online_users/")) {
+            $this->markTestSkipped('Can not find standard Online users block');
+        }