Merge branch 'MDL-37585' of git://github.com/timhunt/moodle
authorAparup Banerjee <aparup@moodle.com>
Tue, 22 Jan 2013 03:08:16 +0000 (11:08 +0800)
committerAparup Banerjee <aparup@moodle.com>
Tue, 22 Jan 2013 03:08:16 +0000 (11:08 +0800)
85 files changed:
admin/mnet/index.php
admin/tool/customlang/filter_form.php
admin/tool/phpunit/cli/init.php
admin/tool/phpunit/cli/util.php
admin/tool/xmldb/actions/XMLDBCheckAction.class.php
admin/tool/xmldb/actions/check_bigints/check_bigints.class.php
admin/tool/xmldb/actions/check_defaults/check_defaults.class.php
admin/tool/xmldb/actions/check_foreign_keys/check_foreign_keys.class.php
admin/tool/xmldb/actions/check_indexes/check_indexes.class.php
admin/tool/xmldb/actions/check_oracle_semantics/check_oracle_semantics.class.php
admin/tool/xmldb/actions/delete_field/delete_field.class.php
admin/tool/xmldb/actions/delete_index/delete_index.class.php
admin/tool/xmldb/actions/delete_key/delete_key.class.php
admin/tool/xmldb/actions/delete_table/delete_table.class.php
admin/tool/xmldb/actions/delete_xml_file/delete_xml_file.class.php
admin/tool/xmldb/actions/revert_changes/revert_changes.class.php
blocks/online_users/tests/generator/lib.php
calendar/lib.php
calendar/managesubscriptions.php
calendar/managesubscriptions_form.php
course/lib.php
enrol/locallib.php
files/renderer.php
grade/report/grader/lib.php
grade/report/grader/styles.css
lib/ajax/blocks.php
lib/dml/mysqli_native_moodle_database.php
lib/filelib.php
lib/form/filemanager.js
lib/javascript-static.js
lib/moodlelib.php
lib/outputrequirementslib.php
lib/phpunit/bootstrap.php
lib/phpunit/bootstraplib.php
lib/phpunit/classes/advanced_testcase.php
lib/phpunit/classes/hint_resultprinter.php
lib/phpunit/classes/util.php
lib/phpunit/generatorlib.php
lib/phpunit/lib.php
lib/phpunit/tests/advanced_test.php
lib/phpunit/tests/basic_test.php
lib/resourcelib.php
lib/setup.php
lib/testing/classes/test_lock.php [new file with mode: 0644]
lib/testing/classes/tests_finder.php [new file with mode: 0644]
lib/testing/classes/util.php [new file with mode: 0644]
lib/testing/generator/block_generator.php [moved from lib/phpunit/classes/block_generator.php with 73% similarity]
lib/testing/generator/data_generator.php [moved from lib/phpunit/classes/data_generator.php with 94% similarity]
lib/testing/generator/lib.php [new file with mode: 0644]
lib/testing/generator/module_generator.php [moved from lib/phpunit/classes/module_generator.php with 74% similarity]
lib/testing/lib.php [new file with mode: 0644]
lib/testing/tests/generator_test.php [moved from lib/phpunit/tests/generator_test.php with 98% similarity]
lib/tests/moodlelib_test.php
lib/tests/weblib_test.php
lib/weblib.php
lib/yui/blocks/blocks.js
login/index.php
login/index_form.html
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/file/locallib.php
mod/assign/lib.php
mod/assign/submission/comments/locallib.php
mod/assign/submission/file/locallib.php
mod/assign/submission/onlinetext/locallib.php
mod/assign/tests/generator/lib.php
mod/assign/tests/upgradelib_test.php
mod/assign/upgradelib.php
mod/assignment/tests/generator/lib.php
mod/data/tests/generator/lib.php
mod/forum/backup/moodle2/restore_forum_activity_task.class.php
mod/forum/tests/generator/lib.php
mod/lesson/locallib.php
mod/lesson/pagetypes/matching.php
mod/lti/tests/generator/lib.php
mod/page/tests/generator/lib.php
mod/quiz/cronlib.php
mod/quiz/locallib.php
mod/quiz/tests/generator/lib.php
mod/scorm/locallib.php
phpunit.xml.dist
question/type/essay/db/upgrade.php
question/type/essay/questiontype.php
question/type/essay/version.php
repository/wikimedia/wikimedia.php
webservice/rest/locallib.php

index d81e8a9..fb4e2c4 100644 (file)
@@ -85,7 +85,7 @@
     echo $OUTPUT->header();
 ?>
 <form method="post" action="index.php">
-    <table align="center" width="635" class="generalbox" border="0" cellpadding="5" cellspacing="0">
+    <table align="center" width="635" class="generaltable" border="0" cellpadding="5" cellspacing="0">
         <tr>
             <td  class="generalboxcontent">
             <table cellpadding="9" cellspacing="0" >
     </table>
 </form>
 <form method="post" action="index.php">
-    <table align="center" width="635" class="generalbox" border="0" cellpadding="5" cellspacing="0">
+    <table align="center" width="635" class="generaltable" border="0" cellpadding="5" cellspacing="0">
         <tr>
             <td  class="generalboxcontent">
             <table cellpadding="9" cellspacing="0" >
index 4bfcd58..547a8a4 100644 (file)
@@ -60,8 +60,8 @@ class tool_customlang_filter_form extends moodleform {
 
         // Modified only
         $mform->addElement('advcheckbox', 'modified', get_string('filtermodified', 'tool_customlang'));
-        $mform->setType('filtermodified', PARAM_BOOL);
-        $mform->setDefault('filtermodified', 0);
+        $mform->setType('modified', PARAM_BOOL);
+        $mform->setDefault('modified', 0);
 
         // Substring
         $mform->addElement('text', 'substring', get_string('filtersubstring', 'tool_customlang'));
index ca00d56..bf026a4 100644 (file)
@@ -28,6 +28,7 @@ if (isset($_SERVER['REMOTE_ADDR'])) {
 
 require_once(__DIR__.'/../../../../lib/clilib.php');
 require_once(__DIR__.'/../../../../lib/phpunit/bootstraplib.php');
+require_once(__DIR__.'/../../../../lib/testing/lib.php');
 
 echo "Initialising Moodle PHPUnit test environment...\n";
 
index 5cfd128..111be5d 100644 (file)
@@ -30,6 +30,7 @@ if (isset($_SERVER['REMOTE_ADDR'])) {
 
 require_once(__DIR__.'/../../../../lib/clilib.php');
 require_once(__DIR__.'/../../../../lib/phpunit/bootstraplib.php');
+require_once(__DIR__.'/../../../../lib/testing/lib.php');
 
 // now get cli options
 list($options, $unrecognized) = cli_get_params(
@@ -110,7 +111,7 @@ Options:
 -h, --help     Print out this help
 
 Example:
-\$ php ".phpunit_bootstrap_cli_argument_path('/admin/tool/phpunit/cli/util.php')." --install
+\$ php ".testing_cli_argument_path('/admin/tool/phpunit/cli/util.php')." --install
 ";
     echo $help;
     exit(0);
@@ -136,7 +137,7 @@ if ($diag) {
 
 } else if ($drop) {
     // make sure tests do not run in parallel
-    phpunit_util::acquire_test_lock();
+    test_lock::acquire('phpunit');
     phpunit_util::drop_site(true);
     // note: we must stop here because $CFG is messed up and we can not reinstall, sorry
     exit(0);
index 06787a6..2514c61 100644 (file)
@@ -90,7 +90,7 @@ abstract class XMLDBCheckAction extends XMLDBAction {
 
         // If  not confirmed, show confirmation box
         if (!$confirmed) {
-            $o = '<table class="generalbox" border="0" cellpadding="5" cellspacing="0" id="notice">';
+            $o = '<table class="generaltable" border="0" cellpadding="5" cellspacing="0" id="notice">';
             $o.= '  <tr><td class="generalboxcontent">';
             $o.= '    <p class="centerpara">' . $this->str[$this->introstr] . '</p>';
             $o.= '    <table class="boxaligncenter" cellpadding="20"><tr><td>';
index 69bfb84..1118906 100644 (file)
@@ -99,7 +99,7 @@ class check_bigints extends XMLDBCheckAction {
         $dbman = $DB->get_manager();
 
         $s = '';
-        $r = '<table class="generalbox boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
+        $r = '<table class="generaltable boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
         $r.= '  <tr><td class="generalboxcontent">';
         $r.= '    <h2 class="main">' . $this->str['searchresults'] . '</h2>';
         $r.= '    <p class="centerpara">' . $this->str['wrongints'] . ': ' . count($wrong_fields) . '</p>';
index 15a9365..73306ea 100644 (file)
@@ -111,7 +111,7 @@ class check_defaults extends XMLDBCheckAction {
         $dbman = $DB->get_manager();
 
         $s = '';
-        $r = '<table class="generalbox boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
+        $r = '<table class="generaltable boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
         $r.= '  <tr><td class="generalboxcontent">';
         $r.= '    <h2 class="main">' . $this->str['searchresults'] . '</h2>';
         $r.= '    <p class="centerpara">' . $this->str['wrongdefaults'] . ': ' . count($wrong_fields) . '</p>';
index cd897d4..67e886f 100644 (file)
@@ -130,7 +130,7 @@ class check_foreign_keys extends XMLDBCheckAction {
     }
 
     protected function display_results(array $violatedkeys) {
-        $r = '<table class="generalbox boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
+        $r = '<table class="generaltable boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
         $r.= '  <tr><td class="generalboxcontent">';
         $r.= '    <h2 class="main">' . $this->str['searchresults'] . '</h2>';
         $r.= '    <p class="centerpara">' . $this->str['violatedforeignkeys'] . ': ' . count($violatedkeys) . '</p>';
index dc6ec90..ba03bac 100644 (file)
@@ -130,7 +130,7 @@ class check_indexes extends XMLDBCheckAction {
         $dbman = $DB->get_manager();
 
         $s = '';
-        $r = '<table class="generalbox boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
+        $r = '<table class="generaltable boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
         $r.= '  <tr><td class="generalboxcontent">';
         $r.= '    <h2 class="main">' . $this->str['searchresults'] . '</h2>';
         $r.= '    <p class="centerpara">' . $this->str['missingindexes'] . ': ' . count($missing_indexes) . '</p>';
index f7488c1..8041672 100644 (file)
@@ -110,7 +110,7 @@ class check_oracle_semantics extends XMLDBCheckAction {
         $dbman = $DB->get_manager();
 
         $s = '';
-        $r = '<table class="generalbox boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
+        $r = '<table class="generaltable boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
         $r.= '  <tr><td class="generalboxcontent">';
         $r.= '    <h2 class="main">' . $this->str['searchresults'] . '</h2>';
         $r.= '    <p class="centerpara">' . $this->str['wrongoraclesemantics'] . ': ' . count($wrong_fields) . '</p>';
index 1d8d414..66096f4 100644 (file)
@@ -73,7 +73,7 @@ class delete_field extends XMLDBAction {
 
         // If  not confirmed, show confirmation box
         if (!$confirmed) {
-            $o = '<table width="60" class="generalbox" border="0" cellpadding="5" cellspacing="0" id="notice">';
+            $o = '<table width="60" class="generaltable" border="0" cellpadding="5" cellspacing="0" id="notice">';
             $o.= '  <tr><td class="generalboxcontent">';
             $o.= '    <p class="centerpara">' . $this->str['confirmdeletefield'] . '<br /><br />' . $fieldparam . '</p>';
             $o.= '    <table class="boxaligncenter" cellpadding="20"><tr><td>';
index 7dadedc..ad6f801 100644 (file)
@@ -73,7 +73,7 @@ class delete_index extends XMLDBAction {
 
         // If  not confirmed, show confirmation box
         if (!$confirmed) {
-            $o = '<table width="60" class="generalbox" border="0" cellpadding="5" cellspacing="0" id="notice">';
+            $o = '<table width="60" class="generaltable" border="0" cellpadding="5" cellspacing="0" id="notice">';
             $o.= '  <tr><td class="generalboxcontent">';
             $o.= '    <p class="centerpara">' . $this->str['confirmdeleteindex'] . '<br /><br />' . $indexparam . '</p>';
             $o.= '    <table class="boxaligncenter" cellpadding="20"><tr><td>';
index 3c1b232..ea55010 100644 (file)
@@ -73,7 +73,7 @@ class delete_key extends XMLDBAction {
 
         // If  not confirmed, show confirmation box
         if (!$confirmed) {
-            $o = '<table width="60" class="generalbox" border="0" cellpadding="5" cellspacing="0" id="notice">';
+            $o = '<table width="60" class="generaltable" border="0" cellpadding="5" cellspacing="0" id="notice">';
             $o.= '  <tr><td class="generalboxcontent">';
             $o.= '    <p class="centerpara">' . $this->str['confirmdeletekey'] . '<br /><br />' . $keyparam . '</p>';
             $o.= '    <table class="boxaligncenter" cellpadding="20"><tr><td>';
index 9d32eec..e577b96 100644 (file)
@@ -72,7 +72,7 @@ class delete_table extends XMLDBAction {
 
         // If  not confirmed, show confirmation box
         if (!$confirmed) {
-            $o = '<table width="60" class="generalbox" border="0" cellpadding="5" cellspacing="0" id="notice">';
+            $o = '<table width="60" class="generaltable" border="0" cellpadding="5" cellspacing="0" id="notice">';
             $o.= '  <tr><td class="generalboxcontent">';
             $o.= '    <p class="centerpara">' . $this->str['confirmdeletetable'] . '<br /><br />' . $tableparam . '</p>';
             $o.= '    <table class="boxaligncenter" cellpadding="20"><tr><td>';
index 02a2bc4..05cdd79 100644 (file)
@@ -71,7 +71,7 @@ class delete_xml_file extends XMLDBAction {
 
         // If  not confirmed, show confirmation box
         if (!$confirmed) {
-            $o = '<table width="60" class="generalbox" border="0" cellpadding="5" cellspacing="0" id="notice">';
+            $o = '<table width="60" class="generaltable" border="0" cellpadding="5" cellspacing="0" id="notice">';
             $o.= '  <tr><td class="generalboxcontent">';
             $o.= '    <p class="centerpara">' . $this->str['confirmdeletexmlfile'] . '<br /><br />' . $dirpath . '/install.php</p>';
             $o.= '    <table class="boxaligncenter" cellpadding="20"><tr><td>';
index a9d1ef9..1649d36 100644 (file)
@@ -71,7 +71,7 @@ class revert_changes extends XMLDBAction {
 
         // If  not confirmed, show confirmation box
         if (!$confirmed) {
-            $o = '<table width="60" class="generalbox boxaligncenter" border="0" cellpadding="5" cellspacing="0" id="notice">';
+            $o = '<table width="60" class="generaltable boxaligncenter" border="0" cellpadding="5" cellspacing="0" id="notice">';
             $o.= '  <tr><td class="generalboxcontent">';
             $o.= '    <p class="centerpara">' . $this->str['confirmrevertchanges'] . '<br /><br />' . $dirpath . '</p>';
             $o.= '    <table class="boxaligncenter" cellpadding="20"><tr><td>';
index d762f42..dce490b 100644 (file)
@@ -18,7 +18,7 @@
  * block_online_users data generator
  *
  * @package    block_online_users
- * @category   phpunit
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -27,14 +27,14 @@ defined('MOODLE_INTERNAL') || die();
 
 
 /**
- * Page module PHPUnit data generator class
+ * Online users block data generator class
  *
- * @package    mod_page
- * @category   phpunit
+ * @package    block_online_users
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class block_online_users_generator extends phpunit_block_generator {
+class block_online_users_generator extends testing_block_generator {
 
     /**
      * Create new block instance
index dc56f91..8200b98 100644 (file)
@@ -2866,8 +2866,7 @@ function calendar_process_subscription_row($subscriptionid, $pollinterval, $acti
             return "<p>".get_string('subscriptionupdated', 'calendar', $sub->name)."</p>" . calendar_update_subscription_events($subscriptionid);
 
         case $strremove:
-            $DB->delete_records('event', array('subscriptionid' => $subscriptionid));
-            $DB->delete_records('event_subscriptions', array('id' => $subscriptionid));
+            calendar_delete_subscription($subscriptionid);
             return get_string('subscriptionremoved', 'calendar', $sub->name);
             break;
 
@@ -2877,6 +2876,21 @@ function calendar_process_subscription_row($subscriptionid, $pollinterval, $acti
     return '';
 }
 
+/**
+ * Delete subscription and all related events.
+ *
+ * @param int|stdClass $subscription subscription or it's id, which needs to be deleted.
+ */
+function calendar_delete_subscription($subscription) {
+    global $DB;
+
+    if (is_object($subscription)) {
+        $subscription = $subscription->id;
+    }
+    // Delete subscription and related events.
+    $DB->delete_records('event', array('subscriptionid' => $subscription));
+    $DB->delete_records('event_subscriptions', array('id' => $subscription));
+}
 /**
  * From a URL, fetch the calendar and return an iCalendar object.
  *
index cc726f1..07265bc 100644 (file)
@@ -75,7 +75,13 @@ if (!empty($formdata)) {
         $ical->unserialize($calendar);
         $importresults = calendar_import_icalendar_events($ical, $courseid, $subscriptionid);
     } else {
-        $importresults = calendar_update_subscription_events($subscriptionid);
+        try {
+            $importresults = calendar_update_subscription_events($subscriptionid);
+        } catch (moodle_exception $e) {
+            // Delete newly added subscription and show invalid url error.
+            calendar_delete_subscription($subscriptionid);
+            print_error($e->errorcode, $e->module, $PAGE->url);
+        }
     }
     // Redirect to prevent refresh issues.
     redirect($PAGE->url, $importresults);
@@ -83,7 +89,12 @@ if (!empty($formdata)) {
     // The user is wanting to perform an action upon an existing subscription.
     require_sesskey(); // Must have sesskey for all actions.
     if (calendar_can_edit_subscription($subscriptionid)) {
-        $importresults = calendar_process_subscription_row($subscriptionid, $pollinterval, $action);
+        try {
+            $importresults = calendar_process_subscription_row($subscriptionid, $pollinterval, $action);
+        } catch (moodle_exception $e) {
+            // If exception caught, then user should be redirected to page where he/she came from.
+            print_error($e->errorcode, $e->module, $PAGE->url);
+        }
     } else {
         print_error('nopermissions', 'error', $PAGE->url, get_string('managesubscriptions', 'calendar'));
     }
index 28344cd..a969fbd 100644 (file)
@@ -123,7 +123,9 @@ class calendar_addsubscription_form extends moodleform {
                 }
             }
         } else if (($data['importfrom'] == CALENDAR_IMPORT_FROM_URL)) {
-            if (clean_param($data['url'], PARAM_URL) !== $data['url']) {
+            // Clean input calendar url.
+            $url = clean_param($data['url'], PARAM_URL);
+            if (empty($url) || ($url !== $data['url'])) {
                 $errors['url']  = get_string('invalidurl', 'error');
             }
         } else {
index 92268fd..2d1e113 100644 (file)
@@ -2149,6 +2149,13 @@ function set_coursemodule_visible($id, $visible) {
     if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
         return false;
     }
+
+    // Create events and propagate visibility to associated grade items if the value has changed.
+    // Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades.
+    if ($cm->visible == $visible) {
+        return true;
+    }
+
     if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
         return false;
     }
@@ -2162,7 +2169,7 @@ function set_coursemodule_visible($id, $visible) {
         }
     }
 
-    // hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there
+    // Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there.
     $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
     if ($grade_items) {
         foreach ($grade_items as $grade_item) {
@@ -3753,16 +3760,6 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules =
             )), null, true);
     }
 
-    // Include blocks dragdrop
-    $params = array(
-        'courseid' => $course->id,
-        'pagetype' => $PAGE->pagetype,
-        'pagelayout' => $PAGE->pagelayout,
-        'subpage' => $PAGE->subpage,
-        'regions' => $PAGE->blocks->get_regions(),
-    );
-    $PAGE->requires->yui_module('moodle-core-blocks', 'M.core_blocks.init_dragdrop', array($params), null, true);
-
     // Require various strings for the command toolbox
     $PAGE->requires->strings_for_js(array(
             'moveleft',
index c4a222e..5e947b6 100644 (file)
@@ -875,7 +875,9 @@ class course_enrolment_manager {
                         break;
                 }
             }
-            $users[$userrole->id]['roles'] = array();
+            if (!isset($users[$userrole->id]['roles'])) {
+                $users[$userrole->id]['roles'] = array();
+            }
             $users[$userrole->id]['roles'][$userrole->roleid] = array(
                 'text' => $roletext,
                 'unchangeable' => !$changeable
index 0715c6b..53c74f0 100644 (file)
@@ -305,7 +305,10 @@ class core_files_renderer extends plugin_renderer_base {
     private function fm_js_template_mkdir() {
         $rv = '
 <div class="filemanager fp-mkdir-dlg">
-    <div class="fp-mkdir-dlg-text">'.get_string('newfoldername','repository').'<br/><input type="text" /></div>
+    <div class="fp-mkdir-dlg-text">
+        <label>' . get_string('newfoldername', 'repository') . '</label><br/>
+        <input type="text" />
+    </div>
     <button class="{!}fp-dlg-butcreate">'.get_string('makeafolder').'</button>
     <button class="{!}fp-dlg-butcancel">'.get_string('cancel').'</button>
 </div>';
index 089b168..2890159 100644 (file)
@@ -181,7 +181,7 @@ class grade_report_grader extends grade_report {
         // Were any changes made?
         $changedgrades = false;
 
-        foreach ($data as $varname => $postedvalue) {
+        foreach ($data as $varname => $students) {
 
             $needsupdate = false;
 
@@ -194,113 +194,116 @@ class grade_report_grader extends grade_report {
                 continue;
             }
 
-            $gradeinfo = explode("_", $varname);
-            $userid = clean_param($gradeinfo[1], PARAM_INT);
-            $itemid = clean_param($gradeinfo[2], PARAM_INT);
+            foreach ($students as $userid => $items) {
+                $userid = clean_param($userid, PARAM_INT);
+                foreach ($items as $itemid => $postedvalue) {
+                    $itemid = clean_param($itemid, PARAM_INT);
+
+                    // Was change requested?
+                    $oldvalue = $this->grades[$userid][$itemid];
+                    if ($datatype === 'grade') {
+                        // If there was no grade and there still isn't
+                        if (is_null($oldvalue->finalgrade) && $postedvalue == -1) {
+                            // -1 means no grade
+                            continue;
+                        }
 
-            // Was change requested?
-            $oldvalue = $this->grades[$userid][$itemid];
-            if ($datatype === 'grade') {
-                // If there was no grade and there still isn't
-                if (is_null($oldvalue->finalgrade) && $postedvalue == -1) {
-                    // -1 means no grade
-                    continue;
-                }
+                        // If the grade item uses a custom scale
+                        if (!empty($oldvalue->grade_item->scaleid)) {
 
-                // If the grade item uses a custom scale
-                if (!empty($oldvalue->grade_item->scaleid)) {
+                            if ((int)$oldvalue->finalgrade === (int)$postedvalue) {
+                                continue;
+                            }
+                        } else {
+                            // The grade item uses a numeric scale
 
-                    if ((int)$oldvalue->finalgrade === (int)$postedvalue) {
-                        continue;
-                    }
-                } else {
-                    // The grade item uses a numeric scale
+                            // Format the finalgrade from the DB so that it matches the grade from the client
+                            if ($postedvalue === format_float($oldvalue->finalgrade, $oldvalue->grade_item->get_decimals())) {
+                                continue;
+                            }
+                        }
 
-                    // Format the finalgrade from the DB so that it matches the grade from the client
-                    if ($postedvalue === format_float($oldvalue->finalgrade, $oldvalue->grade_item->get_decimals())) {
-                        continue;
+                        $changedgrades = true;
+
+                    } else if ($datatype === 'feedback') {
+                        if (($oldvalue->feedback === $postedvalue) or ($oldvalue->feedback === NULL and empty($postedvalue))) {
+                            continue;
+                        }
                     }
-                }
 
-                $changedgrades = true;
+                    if (!$gradeitem = grade_item::fetch(array('id'=>$itemid, 'courseid'=>$this->courseid))) {
+                        print_error('invalidgradeitemid');
+                    }
 
-            } else if ($datatype === 'feedback') {
-                if (($oldvalue->feedback === $postedvalue) or ($oldvalue->feedback === NULL and empty($postedvalue))) {
-                    continue;
-                }
-            }
+                    // Pre-process grade
+                    if ($datatype == 'grade') {
+                        $feedback = false;
+                        $feedbackformat = false;
+                        if ($gradeitem->gradetype == GRADE_TYPE_SCALE) {
+                            if ($postedvalue == -1) { // -1 means no grade
+                                $finalgrade = null;
+                            } else {
+                                $finalgrade = $postedvalue;
+                            }
+                        } else {
+                            $finalgrade = unformat_float($postedvalue);
+                        }
 
-            if (!$gradeitem = grade_item::fetch(array('id'=>$itemid, 'courseid'=>$this->courseid))) { // we must verify course id here!
-                print_error('invalidgradeitemid');
-            }
+                        $errorstr = '';
+                        // Warn if the grade is out of bounds.
+                        if (is_null($finalgrade)) {
+                            // ok
+                        } else {
+                            $bounded = $gradeitem->bounded_grade($finalgrade);
+                            if ($bounded > $finalgrade) {
+                            $errorstr = 'lessthanmin';
+                            } else if ($bounded < $finalgrade) {
+                                $errorstr = 'morethanmax';
+                            }
+                        }
+                        if ($errorstr) {
+                            $user = $DB->get_record('user', array('id' => $userid), 'id, firstname, lastname');
+                            $gradestr = new stdClass();
+                            $gradestr->username = fullname($user);
+                            $gradestr->itemname = $gradeitem->get_name();
+                            $warnings[] = get_string($errorstr, 'grades', $gradestr);
+                        }
 
-            // Pre-process grade
-            if ($datatype == 'grade') {
-                $feedback = false;
-                $feedbackformat = false;
-                if ($gradeitem->gradetype == GRADE_TYPE_SCALE) {
-                    if ($postedvalue == -1) { // -1 means no grade
-                        $finalgrade = null;
-                    } else {
-                        $finalgrade = $postedvalue;
+                    } else if ($datatype == 'feedback') {
+                        $finalgrade = false;
+                        $trimmed = trim($postedvalue);
+                        if (empty($trimmed)) {
+                             $feedback = NULL;
+                        } else {
+                             $feedback = $postedvalue;
+                        }
                     }
-                } else {
-                    $finalgrade = unformat_float($postedvalue);
-                }
 
-                $errorstr = '';
-                // Warn if the grade is out of bounds.
-                if (is_null($finalgrade)) {
-                    // ok
-                } else {
-                    $bounded = $gradeitem->bounded_grade($finalgrade);
-                    if ($bounded > $finalgrade) {
-                    $errorstr = 'lessthanmin';
-                    } else if ($bounded < $finalgrade) {
-                        $errorstr = 'morethanmax';
+                    // group access control
+                    if ($separategroups) {
+                        // note: we can not use $this->currentgroup because it would fail badly
+                        //       when having two browser windows each with different group
+                        $sharinggroup = false;
+                        foreach($mygroups as $groupid) {
+                            if (groups_is_member($groupid, $userid)) {
+                                $sharinggroup = true;
+                                break;
+                            }
+                        }
+                        if (!$sharinggroup) {
+                            // either group membership changed or somebody is hacking grades of other group
+                            $warnings[] = get_string('errorsavegrade', 'grades');
+                            continue;
+                        }
                     }
-                }
-                if ($errorstr) {
-                    $user = $DB->get_record('user', array('id' => $userid), 'id, firstname, lastname');
-                    $gradestr = new stdClass();
-                    $gradestr->username = fullname($user);
-                    $gradestr->itemname = $gradeitem->get_name();
-                    $warnings[] = get_string($errorstr, 'grades', $gradestr);
-                }
 
-            } else if ($datatype == 'feedback') {
-                $finalgrade = false;
-                $trimmed = trim($postedvalue);
-                if (empty($trimmed)) {
-                     $feedback = NULL;
-                } else {
-                     $feedback = $postedvalue;
-                }
-            }
+                    $gradeitem->update_final_grade($userid, $finalgrade, 'gradebook', $feedback, FORMAT_MOODLE);
 
-            // group access control
-            if ($separategroups) {
-                // note: we can not use $this->currentgroup because it would fail badly
-                //       when having two browser windows each with different group
-                $sharinggroup = false;
-                foreach($mygroups as $groupid) {
-                    if (groups_is_member($groupid, $userid)) {
-                        $sharinggroup = true;
-                        break;
+                    // We can update feedback without reloading the grade item as it doesn't affect grade calculations
+                    if ($datatype === 'feedback') {
+                        $this->grades[$userid][$itemid]->feedback = $feedback;
                     }
                 }
-                if (!$sharinggroup) {
-                    // either group membership changed or somebody is hacking grades of other group
-                    $warnings[] = get_string('errorsavegrade', 'grades');
-                    continue;
-                }
-            }
-
-            $gradeitem->update_final_grade($userid, $finalgrade, 'gradebook', $feedback, FORMAT_MOODLE);
-
-            // We can update feedback without reloading the grade item as it doesn't affect grade calculations
-            if ($datatype === 'feedback') {
-                $this->grades[$userid][$itemid]->feedback = $feedback;
             }
         }
 
@@ -1018,7 +1021,7 @@ class grade_report_grader extends grade_report {
                             }
                             $attributes = array('tabindex' => $tabindices[$item->id]['grade'], 'id'=>'grade_'.$userid.'_'.$item->id);
                             $itemcell->text .= html_writer::label(get_string('typescale', 'grades'), $attributes['id'], false, array('class' => 'accesshide'));
-                            $itemcell->text .= html_writer::select($scaleopt, 'grade_'.$userid.'_'.$item->id, $gradeval, array(-1=>$nogradestr), $attributes);
+                            $itemcell->text .= html_writer::select($scaleopt, 'grade['.$userid.']['.$item->id.']', $gradeval, array(-1=>$nogradestr), $attributes);
                         } elseif(!empty($scale)) {
                             $scales = explode(",", $scale->scale);
 
@@ -1040,8 +1043,8 @@ class grade_report_grader extends grade_report {
                             $itemcell->text .= '<label class="accesshide" for="grade_'.$userid.'_'.$item->id.'">'
                                           .get_string('useractivitygrade', 'gradereport_grader', $gradelabel).'</label>';
                             $itemcell->text .= '<input size="6" tabindex="' . $tabindices[$item->id]['grade']
-                                          . '" type="text" class="text" title="'. $strgrade .'" name="grade_'
-                                          .$userid.'_' .$item->id.'" id="grade_'.$userid.'_'.$item->id.'" value="'.$value.'" />';
+                                          . '" type="text" class="text" title="'. $strgrade .'" name="grade['
+                                          .$userid.'][' .$item->id.']" id="grade_'.$userid.'_'.$item->id.'" value="'.$value.'" />';
                         } else {
                             $itemcell->text .= html_writer::tag('span', format_float($gradeval, $decimalpoints), array('class'=>"gradevalue$hidden$gradepass"));
                         }
@@ -1054,7 +1057,7 @@ class grade_report_grader extends grade_report {
                         $itemcell->text .= '<label class="accesshide" for="feedback_'.$userid.'_'.$item->id.'">'
                                       .get_string('useractivityfeedback', 'gradereport_grader', $feedbacklabel).'</label>';
                         $itemcell->text .= '<input class="quickfeedback" tabindex="' . $tabindices[$item->id]['feedback'].'" id="feedback_'.$userid.'_'.$item->id
-                                      . '" size="6" title="' . $strfeedback . '" type="text" name="feedback_'.$userid.'_'.$item->id.'" value="' . s($grade->feedback) . '" />';
+                                      . '" size="6" title="' . $strfeedback . '" type="text" name="feedback['.$userid.']['.$item->id.']" value="' . s($grade->feedback) . '" />';
                     }
 
                 } else { // Not editing
@@ -1738,25 +1741,21 @@ class grade_report_grader extends grade_report {
             // Will this number of students result in more fields that we are allowed?
             $maxinputvars = ini_get('max_input_vars');
             if ($maxinputvars !== false) {
-                $fieldspergradeitem = 0; // The number of fields output per grade item for each student
+                // We can't do anything about there being more grade items than max_input_vars,
+                // but we can decrease number of students per page if there are >= max_input_vars
+                $fieldsperstudent = 0; // The number of fields output per student
 
-                if ($this->get_pref('quickgrading')) {
-                    // One grade field
-                    $fieldspergradeitem ++;
-                }
-                if ($this->get_pref('showquickfeedback')) {
-                    // One feedback field
-                    $fieldspergradeitem ++;
+                if ($this->get_pref('quickgrading') || $this->get_pref('showquickfeedback')) {
+                    // Each array (grade, feedback) will gain one element
+                    $fieldsperstudent ++;
                 }
 
-                $fieldsperstudent = $fieldspergradeitem * count($this->gtree->get_items());
                 $fieldsrequired = $studentsperpage * $fieldsperstudent;
-                if ($fieldsrequired > $maxinputvars) {
-                    $studentsperpage = floor($maxinputvars / $fieldsperstudent);
+                if ($fieldsrequired >= $maxinputvars) {
+                    $studentsperpage = $maxinputvars - 1; // Subtract one to be on the safe side
                     if ($studentsperpage<1) {
-                        // Make sure students per page doesn't fall below 1
-                        // PHP max_input_vars could potentially be reached with 1 student
-                        // if there are >500 grade items and quickgrading and showquickfeedback are on
+                        // Make sure students per page doesn't fall below 1, though if your
+                        // max_input_vars is only 1 you've got bigger problems!
                         $studentsperpage = 1;
                     }
 
index 977e305..e5c8380 100644 (file)
@@ -586,7 +586,7 @@ background-color:#f3ead8;
 
 .path-grade-report-grader .grade_icons { margin-bottom: .3em;}
 
-.path-grade-report-grader .yui-overlay {
+.path-grade-report-grader .yui3-overlay {
     background-color: #FFEE69;
     border-color: #D4C237 #A6982B #A6982B;
     border-style: solid;
@@ -596,15 +596,15 @@ background-color:#f3ead8;
     font-size: 0.7em;
 }
 
-.path-grade-report-grader .yui-overlay .fullname {
+.path-grade-report-grader .yui3-overlay .fullname {
     color: #5F3E00;
     font-weight: bold;
 }
-.path-grade-report-grader .yui-overlay .itemname {
+.path-grade-report-grader .yui3-overlay .itemname {
     color: #194F3E;
     font-weight: bold;
 }
-.path-grade-report-grader .yui-overlay .feedback {
+.path-grade-report-grader .yui3-overlay .feedback {
     color: #5F595E;
 }
 /* table#user-grades td */
@@ -613,7 +613,7 @@ background-color:#f3ead8;
   text-align: left;
 }
 
-.path-grade-report-grader .yui-overlay a.container-close {
+.path-grade-report-grader .yui3-overlay a.container-close {
   margin-top: -3px;
 }
 
index 470106d..8615405 100644 (file)
@@ -53,6 +53,20 @@ require_sesskey();
 // Setting layout to replicate blocks configuration for the page we edit
 $PAGE->set_pagelayout($pagelayout);
 $PAGE->set_subpage($subpage);
+$pagetype = explode('-', $pagetype);
+switch ($pagetype[0]) {
+    case 'admin':
+        // Admin pages need to be in the system context, not Site Course context.
+        $PAGE->set_context(context_system::instance());
+        break;
+    case 'my':
+        // My Home page needs to be in user context, and to have 'content' block region set up.
+        $PAGE->set_context(context_user::instance($USER->id));
+        $PAGE->set_blocks_editing_capability('moodle/my:manageblocks');
+        $PAGE->blocks->add_region('content');
+        break;
+}
+
 echo $OUTPUT->header(); // send headers
 
 switch ($action) {
index 0127895..e819824 100644 (file)
@@ -377,6 +377,9 @@ class mysqli_native_moodle_database extends moodle_database {
         if (empty($dbport)) {
             $dbport = 3306;
         }
+        if ($dbhost and !empty($this->dboptions['dbpersist'])) {
+            $dbhost = "p:$dbhost";
+        }
         ob_start();
         $this->mysqli = new mysqli($dbhost, $dbuser, $dbpass, $dbname, $dbport, $dbsocket);
         $dberr = ob_get_contents();
index 328b695..013f6c5 100644 (file)
@@ -558,11 +558,11 @@ function file_get_user_used_space() {
  * @param string $str
  * @return string path
  */
-function file_correct_filepath($str) { //TODO: what is this? (skodak)
+function file_correct_filepath($str) { //TODO: what is this? (skodak) - No idea (Fred)
     if ($str == '/' or empty($str)) {
         return '/';
     } else {
-        return '/'.trim($str, './@#$ ').'/';
+        return '/'.trim($str, '/').'/';
     }
 }
 
index 86fc03a..19080d9 100644 (file)
@@ -319,6 +319,7 @@ M.form_filemanager.init = function(Y, options) {
                             on('keydown', function(e){
                                 if (e.keyCode == 13) {Y.bind(perform_action, this)(e);}
                             }, this);
+                        node.one('label').set('for', 'fm-newname-' + this.client_id);
                         node.all('.fp-dlg-butcancel').on('click', function(e){e.preventDefault();this.mkdir_dialog.hide();}, this);
                         node.all('.fp-dlg-curpath').set('id', 'fm-curpath-'+this.client_id);
                     }
index 6572ecd..2a5712e 100644 (file)
@@ -854,6 +854,16 @@ M.util.focus_login_form = function(Y) {
     }
 }
 
+/**
+ * Set focus on login error message
+ */
+M.util.focus_login_error = function(Y) {
+    var errorlog = Y.one('#loginerrormessage');
+
+    if (errorlog) {
+        errorlog.focus();
+    }
+}
 /**
  * Adds lightbox hidden element that covers the whole node.
  *
index a98062a..f09d6ec 100644 (file)
@@ -920,8 +920,7 @@ function clean_param($param, $type) {
         case PARAM_FILE:         // Strip all suspicious characters from filename
             $param = fix_utf8($param);
             $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
-            $param = preg_replace('~\.\.+~', '', $param);
-            if ($param === '.') {
+            if ($param === '.' || $param === '..') {
                 $param = '';
             }
             return $param;
@@ -929,10 +928,23 @@ function clean_param($param, $type) {
         case PARAM_PATH:         // Strip all suspicious characters from file path
             $param = fix_utf8($param);
             $param = str_replace('\\', '/', $param);
-            $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
-            $param = preg_replace('~\.\.+~', '', $param);
+
+            // Explode the path and clean each element using the PARAM_FILE rules.
+            $breadcrumb = explode('/', $param);
+            foreach ($breadcrumb as $key => $crumb) {
+                if ($crumb === '.' && $key === 0) {
+                    // Special condition to allow for relative current path such as ./currentdirfile.txt.
+                } else {
+                    $crumb = clean_param($crumb, PARAM_FILE);
+                }
+                $breadcrumb[$key] = $crumb;
+            }
+            $param = implode('/', $breadcrumb);
+
+            // Remove multiple current path (./././) and multiple slashes (///).
             $param = preg_replace('~//+~', '/', $param);
-            return preg_replace('~/(\./)+~', '/', $param);
+            $param = preg_replace('~/(\./)+~', '/', $param);
+            return $param;
 
         case PARAM_HOST:         // allow FQDN or IPv4 dotted quad
             $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
index d4eff1c..008d091 100644 (file)
@@ -282,6 +282,21 @@ class page_requirements_manager {
         if ($page->pagelayout === 'frametop') {
             $this->js_init_call('M.util.init_frametop');
         }
+
+        // Include block drag/drop if editing is on
+        if ($page->user_is_editing()) {
+            $params = array(
+                'courseid' => $page->course->id,
+                'pagetype' => $page->pagetype,
+                'pagelayout' => $page->pagelayout,
+                'subpage' => $page->subpage,
+                'regions' => $page->blocks->get_regions(),
+            );
+            if (!empty($page->cm->id)) {
+                $params['cmid'] = $page->cm->id;
+            }
+            $page->requires->yui_module('moodle-core-blocks', 'M.core_blocks.init_dragdrop', array($params), null, true);
+        }
     }
 
     /**
index f94c295..f63fdb0 100644 (file)
@@ -32,6 +32,7 @@ ini_set('display_errors', '1');
 ini_set('log_errors', '1');
 
 require_once(__DIR__.'/bootstraplib.php');
+require_once(__DIR__.'/../testing/lib.php');
 
 if (isset($_SERVER['REMOTE_ADDR'])) {
     phpunit_bootstrap_error(1, 'Unit tests can be executed only from command line!');
@@ -133,7 +134,7 @@ if (!file_exists("$CFG->phpunit_dataroot/phpunittestdir.txt")) {
     }
 
     // now we are 100% sure this dir is used only for phpunit tests
-    phpunit_bootstrap_initdataroot($CFG->phpunit_dataroot);
+    testing_initdataroot($CFG->phpunit_dataroot, 'phpunit');
 }
 
 // verify db prefix
index b7c9fde..28f4345 100644 (file)
@@ -17,7 +17,7 @@
 /**
  * PHPUnit bootstrap function
  *
- * Note: these functions must be self contained and must not rely on any library or include
+ * Note: these functions must be self contained and must not rely on any other library or include
  *
  * @package    core
  * @category   phpunit
@@ -25,6 +25,8 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+require_once(__DIR__ . '/../testing/lib.php');
+
 define('PHPUNIT_EXITCODE_PHPUNITMISSING', 129);
 define('PHPUNIT_EXITCODE_PHPUNITWRONG', 130);
 define('PHPUNIT_EXITCODE_PHPUNITEXTMISSING', 131);
@@ -63,11 +65,11 @@ function phpunit_bootstrap_error($errorcode, $text = '') {
             $text = "Moodle PHPUnit environment configuration warning:\n".$text;
             break;
         case PHPUNIT_EXITCODE_INSTALL:
-            $path = phpunit_bootstrap_cli_argument_path('/admin/tool/phpunit/cli/init.php');
+            $path = testing_cli_argument_path('/admin/tool/phpunit/cli/init.php');
             $text = "Moodle PHPUnit environment is not initialised, please use:\n php $path";
             break;
         case PHPUNIT_EXITCODE_REINSTALL:
-            $path = phpunit_bootstrap_cli_argument_path('/admin/tool/phpunit/cli/init.php');
+            $path = testing_cli_argument_path('/admin/tool/phpunit/cli/init.php');
             $text = "Moodle PHPUnit environment was initialised for different version, please use:\n php $path";
             break;
         default:
@@ -76,90 +78,5 @@ function phpunit_bootstrap_error($errorcode, $text = '') {
             break;
     }
 
-    // do not write to error stream because we need the error message in PHP exec result from web ui
-    echo($text."\n");
-    exit($errorcode);
-}
-
-/**
- * Returns relative path against current working directory,
- * to be used for shell execution hints.
- * @param string $moodlepath starting with "/", ex: "/admin/tool/cli/init.php"
- * @return string path relative to current directory or absolute path
- */
-function phpunit_bootstrap_cli_argument_path($moodlepath) {
-    global $CFG;
-
-    if (isset($CFG->admin) and $CFG->admin !== 'admin') {
-        $moodlepath = preg_replace('|^/admin/|', "/$CFG->admin/", $moodlepath);
-    }
-
-    $cwd = getcwd();
-    if (substr($cwd, -1) !== DIRECTORY_SEPARATOR) {
-        $cwd .= DIRECTORY_SEPARATOR;
-    }
-    $path = realpath($CFG->dirroot.$moodlepath);
-
-    if (strpos($path, $cwd) === 0) {
-        $path = substr($path, strlen($cwd));
-    }
-
-    if (phpunit_bootstrap_is_cygwin()) {
-        $path = str_replace('\\', '/', $path);
-    }
-
-    return $path;
-}
-
-/**
- * Mark empty dataroot to be used for testing.
- * @param string $dataroot The dataroot directory
- * @return void
- */
-function phpunit_bootstrap_initdataroot($dataroot) {
-    global $CFG;
-    umask(0);
-    if (!file_exists("$dataroot/phpunittestdir.txt")) {
-        file_put_contents("$dataroot/phpunittestdir.txt", 'Contents of this directory are used during tests only, do not delete this file!');
-    }
-    phpunit_boostrap_fix_file_permissions("$dataroot/phpunittestdir.txt");
-    if (!file_exists("$CFG->phpunit_dataroot/phpunit")) {
-        mkdir("$CFG->phpunit_dataroot/phpunit", $CFG->directorypermissions);
-    }
-}
-
-/**
- * Try to change permissions to $CFG->dirroot or $CFG->dataroot if possible
- * @param string $file
- * @return bool success
- */
-function phpunit_boostrap_fix_file_permissions($file) {
-    global $CFG;
-
-    $permissions = fileperms($file);
-    if ($permissions & $CFG->filepermissions != $CFG->filepermissions) {
-        $permissions = $permissions | $CFG->filepermissions;
-        return chmod($file, $permissions);
-    }
-
-    return true;
-}
-
-/**
- * Find out if running under Cygwin on Windows.
- * @return bool
- */
-function phpunit_bootstrap_is_cygwin() {
-    if (empty($_SERVER['OS']) or $_SERVER['OS'] !== 'Windows_NT') {
-        return false;
-
-    } else if (!empty($_SERVER['SHELL']) and $_SERVER['SHELL'] === '/bin/bash') {
-        return true;
-
-    } else if (!empty($_SERVER['TERM']) and $_SERVER['TERM'] === 'cygwin') {
-        return true;
-
-    } else {
-        return false;
-    }
+    testing_error($errorcode, $text);
 }
index c63eed9..9b9bb99 100644 (file)
@@ -386,7 +386,7 @@ abstract class advanced_testcase extends PHPUnit_Framework_TestCase {
     /**
      * Get data generator
      * @static
-     * @return phpunit_data_generator
+     * @return testing_data_generator
      */
     public static function getDataGenerator() {
         return phpunit_util::get_data_generator();
index 4f68206..a04b326 100644 (file)
@@ -106,7 +106,7 @@ class Hint_ResultPrinter extends PHPUnit_TextUI_ResultPrinter {
 
         if (!$executable) {
             $executable = 'phpunit';
-            if (phpunit_bootstrap_is_cygwin()) {
+            if (testing_is_cygwin()) {
                 $file = str_replace('\\', '/', $file);
                 $executable = 'phpunit.bat';
             }
index fc41b2f..23a34d9 100644 (file)
@@ -23,6 +23,7 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+require_once(__DIR__.'/../../testing/classes/util.php');
 
 /**
  * Collection of utility methods.
  * @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;
-
+class phpunit_util extends testing_util {
     /** @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;
-
     /** @var array list of debugging messages triggered during the last test execution */
     protected static $debuggings = array();
 
@@ -64,54 +44,14 @@ class phpunit_util {
     protected static $messagesink = 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
+     * @var array Files to skip when resetting dataroot folder
      */
-    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'));
-    }
+    protected static $datarootskiponreset = array('.', '..', 'phpunittestdir.txt', 'phpunit', '.htaccess');
 
     /**
-     * Note: do not call manually!
-     * @internal
-     * @static
-     * @return void
+     * @var array Files to skip when dropping dataroot folder
      */
-    public static function release_test_lock() {
-        if (self::$lockhandle) {
-            flock(self::$lockhandle, LOCK_UN);
-            fclose(self::$lockhandle);
-            self::$lockhandle = null;
-        }
-    }
+    protected static $datarootskipondrop = array('.', '..', 'lock', 'webrunner.xml');
 
     /**
      * Load global $CFG;
@@ -129,7 +69,7 @@ class phpunit_util {
             initialise_cfg();
             return;
         }
-        if ($dbhash !== phpunit_util::get_version_hash()) {
+        if ($dbhash !== self::get_version_hash()) {
             // do not set CFG - the only way forward is to drop and reinstall
             return;
         }
@@ -137,413 +77,6 @@ class phpunit_util {
         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 the cache API so that it recreates it's required directories as well.
-        cache_factory::reset();
-        // Purge all data from the caches. This is required for consistency.
-        // Any file caches that happened to be within the data root will have already been clearer (because we just deleted cache)
-        // and now we will purge any other caches as well.
-        cache_helper::purge_all();
-    }
-
     /**
      * Reset contents of all database tables to initial values, reset caches, etc.
      *
@@ -724,35 +257,6 @@ class phpunit_util {
         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?
      *
@@ -760,36 +264,19 @@ class phpunit_util {
      * @return int array errorcode=>message, 0 means ok
      */
     public static function testing_ready_problem() {
-        global $CFG, $DB;
-
-        $tables = $DB->get_tables(false);
+        global $DB;
 
         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');
         }
 
+        $tables = $DB->get_tables(false);
         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) {
+        if (!self::is_test_data_updated()) {
             return array(PHPUNIT_EXITCODE_REINSTALL, '');
         }
 
@@ -816,52 +303,13 @@ class phpunit_util {
         if ($displayprogress) {
             echo "Purging dataroot:\n";
         }
+
         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);
-            }
-        }
+        testing_initdataroot($CFG->dataroot, 'phpunit');
+        self::drop_dataroot();
 
         // 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';
-        }
-
-        if ($displayprogress) {
-            echo "Dropping tables:\n";
-        }
-        $dotsonline = 0;
-        foreach ($tables as $tablename) {
-            $table = new xmldb_table($tablename);
-            $DB->get_manager()->drop_table($table);
-
-            if ($dotsonline == 60) {
-                if ($displayprogress) {
-                    echo "\n";
-                }
-                $dotsonline = 0;
-            }
-            if ($displayprogress) {
-                echo '.';
-            }
-            $dotsonline += 1;
-        }
-        if ($displayprogress) {
-            echo "\n";
-        }
+        self::drop_database($displayprogress);
     }
 
     /**
@@ -899,84 +347,11 @@ class phpunit_util {
         $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));
+        // Store version hash in the database and in a file.
+        self::store_versions_hash();
 
-        return self::$versionhash;
+        // Store database data and structure.
+        self::store_database_state();
     }
 
     /**
@@ -1020,7 +395,7 @@ class phpunit_util {
         $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");
+                testing_fix_file_permissions("$CFG->dirroot/phpunit.xml");
             }
         }
 
@@ -1030,7 +405,7 @@ class phpunit_util {
             '<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");
+        testing_fix_file_permissions("$CFG->dataroot/phpunit/webrunner.xml");
 
         return (bool)$result;
     }
@@ -1055,17 +430,8 @@ class phpunit_util {
         $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;
+        // Gets all the components with tests
+        $components = tests_finder::get_components_with_tests('phpunit');
 
         // Create the corresponding phpunit.xml file for each component
         foreach ($components as $cname => $cpath) {
@@ -1084,7 +450,7 @@ class phpunit_util {
             $result = false;
             if (is_writable($cpath)) {
                 if ($result = (bool)file_put_contents("$cpath/phpunit.xml", $fcontents)) {
-                    phpunit_boostrap_fix_file_permissions("$cpath/phpunit.xml");
+                    testing_fix_file_permissions("$cpath/phpunit.xml");
                 }
             }
             // Problems writing file, throw error
@@ -1094,112 +460,6 @@ class phpunit_util {
         }
     }
 
-    /**
-     * 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;
-    }
-
     /**
      * To be called from debugging() only.
      * @param string $message
index f6c8897..6dfbdd4 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * PHPUnit data generator support
+ * Adds data generator support
  *
+ * Deprecated file in favour of lib/testing/generator/lib.php, keeping
+ * file for backwards reference just in case 3rd party applications are
+ * using them.
+ *
+ * @deprecated
+ * @todo       MDL-37517 This will be deleted in Moodle 2.7
+ * @see        lib/testing/generator/lib.php
  * @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
  */
 
-// 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');
+debugging('File lib/phpunit/generatorlib.php is deprecated, please use lib/testing/generator/lib.php instead', DEBUG_DEVELOPER);
 
+require_once(__DIR__ . '/../testing/generator/lib.php');
index 95b7f73..2ceaf3e 100644 (file)
@@ -35,5 +35,6 @@ 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');
-require_once(__DIR__.'/classes/hint_resultprinter.php'); // Loaded here because phpunit.xml does not support relative links for printerFile
-
+require_once(__DIR__.'/classes/hint_resultprinter.php'); // Loaded here because phpunit.xml does not support relative links for printerFile.
+require_once(__DIR__.'/../testing/classes/test_lock.php');
+require_once(__DIR__.'/../testing/classes/tests_finder.php');
index d1d1db8..60e058c 100644 (file)
@@ -249,7 +249,7 @@ class core_phpunit_advanced_testcase extends advanced_testcase {
 
     public function test_getDataGenerator() {
         $generator = $this->getDataGenerator();
-        $this->assertInstanceOf('phpunit_data_generator', $generator);
+        $this->assertInstanceOf('testing_data_generator', $generator);
     }
 
     public function test_database_mock1() {
index 2ad03cb..4b8799f 100644 (file)
@@ -35,6 +35,15 @@ defined('MOODLE_INTERNAL') || die();
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class core_phpunit_basic_testcase extends basic_testcase {
+    protected $testassertexecuted = false;
+
+    public function setUp() {
+        if ($this->getName() === 'test_setup_assert') {
+            $this->assertTrue(true);
+            $this->testassertexecuted = true;
+            return;
+        }
+    }
 
     /**
      * Tests that bootstrapping has occurred correctly
@@ -113,6 +122,14 @@ class core_phpunit_basic_testcase extends basic_testcase {
         $this->assertNotEmpty(new stdClass());
     }
 
+    /**
+     * Make sure asserts in setUp() do not create problems.
+     */
+    public function test_setup_assert() {
+        $this->assertTrue($this->testassertexecuted);
+        $this->testassertexecuted = false;
+    }
+
 // Uncomment following tests to see logging of unexpected changes in global state and database
     /*
         public function test_db_modification() {
index 6cf51b4..10b0ea9 100644 (file)
@@ -262,35 +262,17 @@ function resourcelib_embed_general($fullurl, $title, $clicktoopen, $mimetype) {
         $fullurl = $fullurl->out();
     }
 
-    $iframe = false;
-
     $param = '<param name="src" value="'.$fullurl.'" />';
 
-    // IE can not embed stuff properly, that is why we use iframe instead.
-    // Unfortunately this tag does not validate in xhtml strict mode,
-    // but in any case it is undeprecated in HTML 5 - we will use it everywhere soon!
-    if ($mimetype === 'text/html' and check_browser_version('MSIE', 5)) {
-        $iframe = true;
-    }
-
-    if ($iframe) {
-        $code = <<<EOT
+    // Always use iframe embedding because object tag does not work much,
+    // this is ok in HTML5.
+    $code = <<<EOT
 <div class="resourcecontent resourcegeneral">
   <iframe id="resourceobject" src="$fullurl">
     $clicktoopen
   </iframe>
 </div>
 EOT;
-    } else {
-        $code = <<<EOT
-<div class="resourcecontent resourcegeneral">
-  <object id="resourceobject" data="$fullurl" type="$mimetype"  width="800" height="600">
-    $param
-    $clicktoopen
-  </object>
-</div>
-EOT;
-    }
 
     // the size is hardcoded in the boject obove intentionally because it is adjusted by the following function on-the-fly
     $PAGE->requires->js_init_call('M.util.init_maximised_embed', array('resourceobject'), true);
index fdcfc04..f25301f 100644 (file)
@@ -480,7 +480,7 @@ setup_DB();
 
 if (PHPUNIT_TEST and !PHPUNIT_UTIL) {
     // make sure tests do not run in parallel
-    phpunit_util::acquire_test_lock();
+    test_lock::acquire('phpunit');
     $dbhash = null;
     try {
         if ($dbhash = $DB->get_field('config', 'value', array('name'=>'phpunittest'))) {
diff --git a/lib/testing/classes/test_lock.php b/lib/testing/classes/test_lock.php
new file mode 100644 (file)
index 0000000..710cdea
--- /dev/null
@@ -0,0 +1,98 @@
+<?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/>.
+
+/**
+ * Tests lock
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__.'/../lib.php');
+
+/**
+ * Tests lock to prevent concurrent executions of the same test suite
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class test_lock {
+
+    /**
+     * @var array Array of resource used for prevention of parallel test execution
+     */
+    protected static $lockhandles = array();
+
+    /**
+     * Prevent parallel test execution - this can not work in Moodle because we modify database and dataroot.
+     *
+     * Note: do not call manually!
+     *
+     * @internal
+     * @static
+     * @param    string  $framework Test framework
+     * @return   void
+     */
+    public static function acquire($framework) {
+        global $CFG;
+
+        $datarootpath = $CFG->{$framework . '_dataroot'} . '/' . $framework;
+        $lockfile = $datarootpath . '/lock';
+        if (!file_exists($datarootpath)) {
+            // Dataroot not initialised yet.
+            return;
+        }
+        if (!file_exists($lockfile)) {
+            file_put_contents($lockfile, 'This file prevents concurrent execution of Moodle ' . $framework . ' tests');
+            testing_fix_file_permissions($lockfile);
+        }
+        if (self::$lockhandles[$framework] = fopen($lockfile, 'r')) {
+            $wouldblock = null;
+            $locked = flock(self::$lockhandles[$framework], (LOCK_EX | LOCK_NB), $wouldblock);
+            if (!$locked) {
+                if ($wouldblock) {
+                    echo "Waiting for other test execution to complete...\n";
+                }
+                $locked = flock(self::$lockhandles[$framework], LOCK_EX);
+            }
+            if (!$locked) {
+                fclose(self::$lockhandles[$framework]);
+                self::$lockhandles[$framework] = null;
+            }
+        }
+        register_shutdown_function(array('test_lock', 'release'), $framework);
+    }
+
+    /**
+     * Note: do not call manually!
+     * @internal
+     * @static
+     * @param    string  $framework phpunit|behat
+     * @return   void
+     */
+    public static function release($framework) {
+        if (self::$lockhandles[$framework]) {
+            flock(self::$lockhandles[$framework], LOCK_UN);
+            fclose(self::$lockhandles[$framework]);
+            self::$lockhandles[$framework] = null;
+        }
+    }
+
+}
diff --git a/lib/testing/classes/tests_finder.php b/lib/testing/classes/tests_finder.php
new file mode 100644 (file)
index 0000000..0a89cfa
--- /dev/null
@@ -0,0 +1,190 @@
+<?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/>.
+
+/**
+ * Tests finder
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Finds components and plugins with tests
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tests_finder {
+
+    /**
+     * Returns all the components with tests of the specified type
+     * @param string $testtype The kind of test we are looking for
+     * @return array
+     */
+    public static function get_components_with_tests($testtype) {
+
+        // Get all the components
+        $components = self::get_all_plugins_with_tests($testtype) + self::get_all_subsystems_with_tests($testtype);
+
+        // Get all the directories having tests
+        $directories = self::get_all_directories_with_tests($testtype);
+
+        // Find any directory not covered by proper components
+        $remaining = array_diff($directories, $components);
+
+        // Add them to the list of components
+        $components += $remaining;
+
+        return $components;
+    }
+
+    /**
+     * Returns all the plugins having tests
+     * @param string $testtype The kind of test we are looking for
+     * @return array  all the plugins having tests
+     */
+    private static function get_all_plugins_with_tests($testtype) {
+        $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, $testtype)) {
+                    $pluginswithtests[$type . '_' . $plug] = $fullplug;
+                }
+            }
+        }
+        return $pluginswithtests;
+    }
+
+    /**
+     * Returns all the subsystems having 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.
+     *
+     * @param string $testtype The kind of test we are looking for
+     * @return array all the subsystems having tests
+     */
+    private static function get_all_subsystems_with_tests($testtype) {
+        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, $testtype)) {
+                $subsystemswithtests['core_' . $subsys] = $fullsubsys;
+            }
+        }
+        return $subsystemswithtests;
+    }
+
+    /**
+     * Returns all the directories having tests
+     *
+     * @param string $testtype The kind of test we are looking for
+     * @return array all directories having tests
+     */
+    private static function get_all_directories_with_tests($testtype) {
+        global $CFG;
+
+        $dirs = array();
+        $dirite = new RecursiveDirectoryIterator($CFG->dirroot);
+        $iteite = new RecursiveIteratorIterator($dirite);
+        $regexp = self::get_regexp($testtype);
+        $regite = new RegexIterator($iteite, $regexp);
+        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 string $dir full path to the directory to look for phpunit tests
+     * @param string $testtype phpunit|behat
+     * @return bool if a given directory has tests (true) or no (false)
+     */
+    private static function directory_has_tests($dir, $testtype) {
+        if (!is_dir($dir)) {
+            return false;
+        }
+
+        $dirite = new RecursiveDirectoryIterator($dir);
+        $iteite = new RecursiveIteratorIterator($dirite);
+        $regexp = self::get_regexp($testtype);
+        $regite = new RegexIterator($iteite, $regexp);
+        $regite->rewind();
+        if ($regite->valid()) {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Returns the regular expression to match by the test files
+     * @param string $testtype
+     * @return string
+     */
+    private static function get_regexp($testtype) {
+
+        $sep = preg_quote(DIRECTORY_SEPARATOR, '|');
+
+        switch ($testtype) {
+            case 'phpunit':
+                $regexp = '|'.$sep.'tests'.$sep.'.*_test\.php$|';
+                break;
+            case 'features':
+                $regexp = '|'.$sep.'tests'.$sep.'behat'.$sep.'.*\.feature$|';
+                break;
+            case 'stepsdefinitions':
+                $regexp = '|'.$sep.'tests'.$sep.'behat'.$sep.'.*\.php$|';
+                break;
+        }
+
+        return $regexp;
+    }
+}
diff --git a/lib/testing/classes/util.php b/lib/testing/classes/util.php
new file mode 100644 (file)
index 0000000..8588b1b
--- /dev/null
@@ -0,0 +1,727 @@
+<?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/>.
+
+/**
+ * Testing util classes
+ *
+ * @abstract
+ * @package    core
+ * @category   test
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Utils for test sites creation
+ *
+ * @package   core
+ * @category  test
+ * @copyright 2012 Petr Skoda {@link http://skodak.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class testing_util {
+
+    /**
+     * @var int last value of db writes counter, used for db resetting
+     */
+    public static $lastdbwrites = null;
+
+    /**
+     * @var testing_data_generator
+     */
+    protected static $generator = null;
+
+    /**
+     * @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;
+
+    /**
+     * Returns the testing framework name
+     * @static
+     * @return string
+     */
+    protected static final function get_framework() {
+        $classname = get_called_class();
+        return substr($classname, 0, strpos($classname, '_'));
+    }
+
+    /**
+     * Get data generator
+     * @static
+     * @return testing_data_generator
+     */
+    public static function get_data_generator() {
+        if (is_null(self::$generator)) {
+            require_once(__DIR__.'/../generator/lib.php');
+            self::$generator = new testing_data_generator();
+        }
+        return self::$generator;
+    }
+
+    /**
+     * 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;
+
+        $framework = self::get_framework();
+
+        if (!file_exists($CFG->dataroot . '/' . $framework . 'testdir.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', $framework . 'test')) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns whether test database and dataroot were created using the current version codebase
+     *
+     * @return boolean
+     */
+    protected static function is_test_data_updated() {
+        global $CFG;
+
+        $framework = self::get_framework();
+
+        $datarootpath = $CFG->dataroot . '/' . $framework;
+        if (!file_exists($datarootpath . '/tabledata.ser') or !file_exists($datarootpath . '/tablestructure.ser')) {
+            return false;
+        }
+
+        if (!file_exists($datarootpath . '/versionshash.txt')) {
+            return false;
+        }
+
+        $hash = self::get_version_hash();
+        $oldhash = file_get_contents($datarootpath . '/versionshash.txt');
+
+        if ($hash !== $oldhash) {
+            return false;
+        }
+
+        $dbhash = get_config('core', $framework . 'test');
+        if ($hash !== $dbhash) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Stores the status of the database
+     *
+     * Serializes the contents and the structure and
+     * stores it in the test framework space in dataroot
+     */
+    protected static function store_database_state() {
+        global $DB, $CFG;
+
+        $framework = self::get_framework();
+
+        // 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);
+        $datafile = $CFG->dataroot . '/' . $framework . '/tabledata.ser';
+        file_put_contents($datafile, $data);
+        testing_fix_file_permissions($datafile);
+
+        $structure = serialize($structure);
+        $structurefile = $CFG->dataroot . '/' . $framework . '/tablestructure.ser';
+        file_put_contents($structurefile, $structure);
+        testing_fix_file_permissions($structurefile);
+    }
+
+    /**
+     * Stores the version hash in both database and dataroot
+     */
+    protected static function store_versions_hash() {
+        global $CFG;
+
+        $framework = self::get_framework();
+        $hash = self::get_version_hash();
+
+        // add test db flag
+        set_config($framework . 'test', $hash);
+
+        // hash all plugin versions - helps with very fast detection of db structure changes
+        $hashfile = $CFG->dataroot . '/' . $framework . '/versionshash.txt';
+        file_put_contents($hashfile, $hash);
+        testing_fix_file_permissions($hashfile);
+    }
+
+    /**
+     * Returns contents of all tables right after installation.
+     * @static
+     * @return array  $table=>$records
+     */
+    protected static function get_tabledata() {
+        global $CFG;
+
+        $framework = self::get_framework();
+
+        $datafile = $CFG->dataroot . '/' . $framework . '/tabledata.ser';
+        if (!file_exists($datafile)) {
+            // Not initialised yet.
+            return array();
+        }
+
+        if (!isset(self::$tabledata)) {
+            $data = file_get_contents($datafile);
+            self::$tabledata = unserialize($data);
+        }
+
+        if (!is_array(self::$tabledata)) {
+            testing_error(1, 'Can not read dataroot/' . $framework . '/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;
+
+        $framework = self::get_framework();
+
+        $structurefile = $CFG->dataroot . '/' . $framework . '/tablestructure.ser';
+        if (!file_exists($structurefile)) {
+            // Not initialised yet.
+            return array();
+        }
+
+        if (!isset(self::$tablestructure)) {
+            $data = file_get_contents($structurefile);
+            self::$tablestructure = unserialize($data);
+        }
+
+        if (!is_array(self::$tablestructure)) {
+            testing_error(1, 'Can not read dataroot/' . $framework . '/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 = self::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 = self::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);
+                }
+            }
+        }
+    }
+
+    /**
+     * Resets the database
+     * @static
+     * @return boolean Returns whether database has been modified or not
+     */
+    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;
+
+        $childclassname = self::get_framework() . '_util';
+
+        $handle = opendir($CFG->dataroot);
+        while (false !== ($item = readdir($handle))) {
+            if (in_array($item, $childclassname::$datarootskiponreset)) {
+                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 the cache API so that it recreates it's required directories as well.
+        cache_factory::reset();
+        // Purge all data from the caches. This is required for consistency.
+        // Any file caches that happened to be within the data root will have already been clearer (because we just deleted cache)
+        // and now we will purge any other caches as well.
+        cache_helper::purge_all();
+    }
+
+    /**
+     * Drop the whole test database
+     * @static
+     * @param boolean $displayprogress
+     */
+    protected static function drop_database($displayprogress = false) {
+        global $DB;
+
+        $tables = $DB->get_tables(false);
+        if (isset($tables['config'])) {
+            // config always last to prevent problems with interrupted drops!
+            unset($tables['config']);
+            $tables['config'] = 'config';
+        }
+
+        if ($displayprogress) {
+            echo "Dropping tables:\n";
+        }
+        $dotsonline = 0;
+        foreach ($tables as $tablename) {
+            $table = new xmldb_table($tablename);
+            $DB->get_manager()->drop_table($table);
+
+            if ($dotsonline == 60) {
+                if ($displayprogress) {
+                    echo "\n";
+                }
+                $dotsonline = 0;
+            }
+            if ($displayprogress) {
+                echo '.';
+            }
+            $dotsonline += 1;
+        }
+        if ($displayprogress) {
+            echo "\n";
+        }
+    }
+
+    /**
+     * Drops the test framework dataroot
+     * @static
+     */
+    protected static function drop_dataroot() {
+        global $CFG;
+
+        $framework = self::get_framework();
+        $childclassname = $framework . '_util';
+
+        $files = scandir($CFG->dataroot . '/' . $framework);
+        foreach ($files as $file) {
+            if (in_array($file, $childclassname::$datarootskipondrop)) {
+                continue;
+            }
+            $path = $CFG->dataroot . '/' . $framework . '/' . $file;
+            if (is_dir($path)) {
+                remove_dir($path, false);
+            } else {
+                unlink($path);
+            }
+        }
+    }
+
+    /**
+     * Reset all database tables to default values.
+     * @static
+     * @return bool true if reset done, false if skipped
+     */
+    /**
+     * 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;
+    }
+
+}
similarity index 73%
rename from lib/phpunit/classes/block_generator.php
rename to lib/testing/generator/block_generator.php
index a1cb635..02a2288 100644 (file)
@@ -18,7 +18,7 @@
  * Block generator base class.
  *
  * @package    core
- * @category   phpunit
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
  * Extend in blocks/xxxx/tests/generator/lib.php as class block_xxxx_generator.
  *
  * @package    core
- * @category   phpunit
+ * @category   test
  * @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  */
+abstract class testing_block_generator {
+    /** @var testing_data_generator */
     protected $datagenerator;
 
     /** @var number of created instances */
     protected $instancecount = 0;
 
-    public function __construct(phpunit_data_generator $datagenerator) {
+    /**
+     * Dumb constructor to throw the deprecated notification
+     * @param testing_data_generator $datagenerator
+     */
+    public function __construct(testing_data_generator $datagenerator) {
         $this->datagenerator = $datagenerator;
     }
 
@@ -110,3 +114,26 @@ abstract class phpunit_block_generator {
      */
     abstract public function create_instance($record = null, array $options = null);
 }
+
+/**
+ * Deprecated in favour of testing_block_generator
+ *
+ * @deprecated since Moodle 2.5 MDL-37457 - please do not use this function any more.
+ * @todo       MDL-37517 This will be deleted in Moodle 2.7
+ * @see        testing_block_generator
+ * @package    core
+ * @category   test
+ * @copyright  2012 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class phpunit_block_generator extends testing_block_generator {
+
+    /**
+     * Dumb constructor to throw the deprecated notification
+     * @param testing_data_generator $datagenerator
+     */
+    public function __construct(testing_data_generator $datagenerator) {
+        debugging('Class phpunit_block_generator is deprecated, please use class testing_block_generator instead', DEBUG_DEVELOPER);
+        parent::__construct($datagenerator);
+    }
+}
similarity index 94%
rename from lib/phpunit/classes/data_generator.php
rename to lib/testing/generator/data_generator.php
index 7e43c10..7f99a93 100644 (file)
  * Data generator.
  *
  * @package    core
- * @category   phpunit
+ * @category   test
  * @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.
+ * Data generator class for unit tests and other tools that need to create fake test sites.
  *
  * @package    core
- * @category   phpunit
+ * @category   test
  * @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 {
+class testing_data_generator {
     protected $usercounter = 0;
     protected $categorycount = 0;
     protected $cohortcount = 0;
@@ -84,7 +83,7 @@ EOD;
         $this->coursecount = 0;
         $this->scalecount = 0;
 
-        foreach($this->generators as $generator) {
+        foreach ($this->generators as $generator) {
             $generator->reset();
         }
     }
@@ -395,11 +394,11 @@ EOD;
         $record = (array)$record;
 
         if (empty($record['course'])) {
-            throw new coding_exception('course must be present in phpunit_util::create_course_section() $record');
+            throw new coding_exception('course must be present in testing_data_generator::create_course_section() $record');
         }
 
         if (!isset($record['section'])) {
-            throw new coding_exception('section must be present in phpunit_util::create_course_section() $record');
+            throw new coding_exception('section must be present in testing_data_generator::create_course_section() $record');
         }
 
         course_create_sections_if_missing($record['course'], $record['section']);
@@ -437,8 +436,8 @@ EOD;
      * 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.
-     *
+     * This function calls groups_create_group() to create the group within the database.
+     * @see groups_create_group
      * @param array|stdClass $record
      * @return stdClass group record
      */
@@ -453,7 +452,7 @@ EOD;
         $record = (array)$record;
 
         if (empty($record['courseid'])) {
-            throw new coding_exception('courseid must be present in phpunit_util::create_group() $record');
+            throw new coding_exception('courseid must be present in testing_data_generator::create_group() $record');
         }
 
         if (!isset($record['name'])) {
@@ -480,8 +479,8 @@ EOD;
      * 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.
-     *
+     * This function calls groups_create_grouping() to create the grouping within the database.
+     * @see groups_create_grouping
      * @param array|stdClass $record
      * @return stdClass grouping record
      */
@@ -496,7 +495,7 @@ EOD;
         $record = (array)$record;
 
         if (empty($record['courseid'])) {
-            throw new coding_exception('courseid must be present in phpunit_util::create_grouping() $record');
+            throw new coding_exception('courseid must be present in testing_data_generator::create_grouping() $record');
         }
 
         if (!isset($record['name'])) {
@@ -602,3 +601,24 @@ EOD;
         return true;
     }
 }
+
+/**
+ * Deprecated in favour of testing_data_generator
+ *
+ * @deprecated since Moodle 2.5 MDL-37457 - please do not use this function any more.
+ * @todo       MDL-37517 This will be deleted in Moodle 2.7
+ * @see        testing_data_generator
+ * @package    core
+ * @category   test
+ * @copyright  2012 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class phpunit_data_generator extends testing_data_generator {
+
+    /**
+     * Dumb constructor to throw the deprecated notification
+     */
+    public function __construct() {
+        debugging('Class phpunit_data_generator is deprecated, please use class testing_module_generator instead', DEBUG_DEVELOPER);
+    }
+}
diff --git a/lib/testing/generator/lib.php b/lib/testing/generator/lib.php
new file mode 100644 (file)
index 0000000..3afbff2
--- /dev/null
@@ -0,0 +1,31 @@
+<?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/>.
+
+/**
+ * Adds data generator support
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// NOTE: MOODLE_INTERNAL is not verified here because we load this before setup.php!
+
+require_once(__DIR__.'/data_generator.php');
+require_once(__DIR__.'/module_generator.php');
+require_once(__DIR__.'/block_generator.php');
+
similarity index 74%
rename from lib/phpunit/classes/module_generator.php
rename to lib/testing/generator/module_generator.php
index ffa20f9..c499f96 100644 (file)
@@ -18,7 +18,7 @@
  * Module generator base class.
  *
  * @package    core
- * @category   phpunit
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
  * Extend in mod/xxxx/tests/generator/lib.php as class mod_xxxx_generator.
  *
  * @package    core
- * @category   phpunit
+ * @category   test
  * @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  */
+abstract class testing_module_generator {
+
+    /**
+     * @var testing_data_generator
+     */
     protected $datagenerator;
 
-    /** @var number of created instances */
+    /**
+     * @var number of created instances
+     */
     protected $instancecount = 0;
 
-    public function __construct(phpunit_data_generator $datagenerator) {
+    /**
+     * Dumb constructor to throw the deprecated notification
+     * @param testing_data_generator $datagenerator
+     */
+    public function __construct(testing_data_generator $datagenerator) {
         $this->datagenerator = $datagenerator;
     }
 
@@ -73,9 +82,9 @@ abstract class phpunit_module_generator {
 
     /**
      * Create course module and link it to course
-     * @param int $courseid
-     * @param array $options: section, visible
-     * @return int $cm instance id
+     * @param integer $courseid
+     * @param array $options section, visible
+     * @return integer $cm instance id
      */
     protected function precreate_course_module($courseid, array $options) {
         global $DB, $CFG;
@@ -94,7 +103,7 @@ abstract class phpunit_module_generator {
         $cm->added              = time();
 
         $columns = $DB->get_columns('course_modules');
-        foreach ($options as $key=>$value) {
+        foreach ($options as $key => $value) {
             if ($key === 'id' or !isset($columns[$key])) {
                 continue;
             }
@@ -140,3 +149,26 @@ abstract class phpunit_module_generator {
      */
     abstract public function create_instance($record = null, array $options = null);
 }
+
+/**
+ * Deprecated in favour of testing_module_generator
+ *
+ * @deprecated since Moodle 2.5 MDL-37457 - please do not use this function any more.
+ * @todo       MDL-37517 This will be deleted in Moodle 2.7
+ * @see        testing_module_generator
+ * @package    core
+ * @category   test
+ * @copyright  2012 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class phpunit_module_generator extends testing_module_generator {
+
+    /**
+     * Dumb constructor to throw the deprecated notification
+     * @param testing_data_generator $datagenerator
+     */
+    public function __construct(testing_data_generator $datagenerator) {
+        debugging('Class phpunit_module_generator is deprecated, please use class testing_module_generator instead', DEBUG_DEVELOPER);
+        parent::__construct($datagenerator);
+    }
+}
diff --git a/lib/testing/lib.php b/lib/testing/lib.php
new file mode 100644 (file)
index 0000000..0713d6b
--- /dev/null
@@ -0,0 +1,130 @@
+<?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/>.
+
+/**
+ * Testing general functions
+ *
+ * Note: these functions must be self contained and must not rely on any library or include
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Returns relative path against current working directory,
+ * to be used for shell execution hints.
+ * @param string $moodlepath starting with "/", ex: "/admin/tool/cli/init.php"
+ * @return string path relative to current directory or absolute path
+ */
+function testing_cli_argument_path($moodlepath) {
+    global $CFG;
+
+    if (isset($CFG->admin) and $CFG->admin !== 'admin') {
+        $moodlepath = preg_replace('|^/admin/|', "/$CFG->admin/", $moodlepath);
+    }
+
+    $cwd = getcwd();
+    if (substr($cwd, -1) !== DIRECTORY_SEPARATOR) {
+        $cwd .= DIRECTORY_SEPARATOR;
+    }
+    $path = realpath($CFG->dirroot.$moodlepath);
+
+    if (strpos($path, $cwd) === 0) {
+        $path = substr($path, strlen($cwd));
+    }
+
+    if (testing_is_cygwin()) {
+        $path = str_replace('\\', '/', $path);
+    }
+
+    return $path;
+}
+
+/**
+ * Try to change permissions to $CFG->dirroot or $CFG->dataroot if possible
+ * @param string $file
+ * @return bool success
+ */
+function testing_fix_file_permissions($file) {
+    global $CFG;
+
+    $permissions = fileperms($file);
+    if ($permissions & $CFG->filepermissions != $CFG->filepermissions) {
+        $permissions = $permissions | $CFG->filepermissions;
+        return chmod($file, $permissions);
+    }
+
+    return true;
+}
+
+/**
+ * Find out if running under Cygwin on Windows.
+ * @return bool
+ */
+function testing_is_cygwin() {
+    if (empty($_SERVER['OS']) or $_SERVER['OS'] !== 'Windows_NT') {
+        return false;
+
+    } else if (!empty($_SERVER['SHELL']) and $_SERVER['SHELL'] === '/bin/bash') {
+        return true;
+
+    } else if (!empty($_SERVER['TERM']) and $_SERVER['TERM'] === 'cygwin') {
+        return true;
+
+    } else {
+        return false;
+    }
+}
+
+/**
+ * Mark empty dataroot to be used for testing.
+ * @param string $dataroot  The dataroot directory
+ * @param string $framework The test framework
+ * @return void
+ */
+function testing_initdataroot($dataroot, $framework) {
+    global $CFG;
+
+    $filename = $dataroot . '/' . $framework . 'testdir.txt';
+
+    umask(0);
+    if (!file_exists($filename)) {
+        file_put_contents($filename, 'Contents of this directory are used during tests only, do not delete this file!');
+    }
+    testing_fix_file_permissions($filename);
+
+    $varname = $framework . '_dataroot';
+    $datarootdir = $CFG->{$varname} . '/' . $framework;
+    if (!file_exists($datarootdir)) {
+        mkdir($datarootdir, $CFG->directorypermissions);
+    }
+}
+
+/**
+ * Prints an error and stops execution
+ *
+ * @param integer $errorcode
+ * @param string $text
+ * @return void exits
+ */
+function testing_error($errorcode, $text = '') {
+
+    // do not write to error stream because we need the error message in PHP exec result from web ui
+    echo($text."\n");
+    exit($errorcode);
+}
similarity index 98%
rename from lib/phpunit/tests/generator_test.php
rename to lib/testing/tests/generator_test.php
index 0213875..19acca6 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * PHPUnit integration tests
+ * Data generators tests
  *
  * @package    core
- * @category   phpunit
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -30,11 +30,11 @@ defined('MOODLE_INTERNAL') || die();
  * Test data generator
  *
  * @package    core
- * @category   phpunit
+ * @category   test
  * @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 {
+class core_test_generator_testcase extends advanced_testcase {
     public function test_create() {
         global $DB;
 
index 915e0bb..dec5fdd 100644 (file)
@@ -872,7 +872,26 @@ class moodlelib_testcase extends advanced_testcase {
     function test_clean_param_file() {
         $this->assertEquals(clean_param('correctfile.txt', PARAM_FILE), 'correctfile.txt');
         $this->assertEquals(clean_param('b\'a<d`\\/fi:l>e.t"x|t', PARAM_FILE), 'badfile.txt');
-        $this->assertEquals(clean_param('../parentdirfile.txt', PARAM_FILE), 'parentdirfile.txt');
+        $this->assertEquals(clean_param('../parentdirfile.txt', PARAM_FILE), '..parentdirfile.txt');
+        $this->assertEquals(clean_param('../../grandparentdirfile.txt', PARAM_FILE), '....grandparentdirfile.txt');
+        $this->assertEquals(clean_param('..\winparentdirfile.txt', PARAM_FILE), '..winparentdirfile.txt');
+        $this->assertEquals(clean_param('../../wingrandparentdir.txt', PARAM_FILE), '....wingrandparentdir.txt');
+        $this->assertEquals(clean_param('myfile.a.b.txt', PARAM_FILE), 'myfile.a.b.txt');
+        $this->assertEquals(clean_param('myfile..a..b.txt', PARAM_FILE), 'myfile..a..b.txt');
+        $this->assertEquals(clean_param('myfile.a..b...txt', PARAM_FILE), 'myfile.a..b...txt');
+        $this->assertEquals(clean_param('myfile.a.txt', PARAM_FILE), 'myfile.a.txt');
+        $this->assertEquals(clean_param('myfile...txt', PARAM_FILE), 'myfile...txt');
+        $this->assertEquals(clean_param('...jpg', PARAM_FILE), '...jpg');
+        $this->assertEquals(clean_param('.a.b.', PARAM_FILE), '.a.b.');
+        $this->assertEquals(clean_param('.', PARAM_FILE), '');
+        $this->assertEquals(clean_param('..', PARAM_FILE), '');
+        $this->assertEquals(clean_param('...', PARAM_FILE), '...');
+        $this->assertEquals(clean_param('. . . .', PARAM_FILE), '. . . .');
+        $this->assertEquals(clean_param('dontrtrim.me. .. .. . ', PARAM_FILE), 'dontrtrim.me. .. .. . ');
+        $this->assertEquals(clean_param(' . .dontltrim.me', PARAM_FILE), ' . .dontltrim.me');
+        $this->assertEquals(clean_param("here is a tab\t.txt", PARAM_FILE), 'here is a tab.txt');
+        $this->assertEquals(clean_param("here is a line\r\nbreak.txt", PARAM_FILE), 'here is a linebreak.txt');
+
         //The following behaviours have been maintained although they seem a little odd
         $this->assertEquals(clean_param('funny:thing', PARAM_FILE), 'funnything');
         $this->assertEquals(clean_param('./currentdirfile.txt', PARAM_FILE), '.currentdirfile.txt');
@@ -881,6 +900,26 @@ class moodlelib_testcase extends advanced_testcase {
         $this->assertEquals(clean_param('~/myfile.txt', PARAM_FILE), '~myfile.txt');
     }
 
+    function test_clean_param_path() {
+        $this->assertEquals(clean_param('correctfile.txt', PARAM_PATH), 'correctfile.txt');
+        $this->assertEquals(clean_param('b\'a<d`\\/fi:l>e.t"x|t', PARAM_PATH), 'bad/file.txt');
+        $this->assertEquals(clean_param('../parentdirfile.txt', PARAM_PATH), '/parentdirfile.txt');
+        $this->assertEquals(clean_param('../../grandparentdirfile.txt', PARAM_PATH), '/grandparentdirfile.txt');
+        $this->assertEquals(clean_param('..\winparentdirfile.txt', PARAM_PATH), '/winparentdirfile.txt');
+        $this->assertEquals(clean_param('../../wingrandparentdir.txt', PARAM_PATH), '/wingrandparentdir.txt');
+        $this->assertEquals(clean_param('funny:thing', PARAM_PATH), 'funnything');
+        $this->assertEquals(clean_param('./././here', PARAM_PATH), './here');
+        $this->assertEquals(clean_param('./currentdirfile.txt', PARAM_PATH), './currentdirfile.txt');
+        $this->assertEquals(clean_param('c:\temp\windowsfile.txt', PARAM_PATH), 'c/temp/windowsfile.txt');
+        $this->assertEquals(clean_param('/home/user/linuxfile.txt', PARAM_PATH), '/home/user/linuxfile.txt');
+        $this->assertEquals(clean_param('/home../user ./.linuxfile.txt', PARAM_PATH), '/home../user ./.linuxfile.txt');
+        $this->assertEquals(clean_param('~/myfile.txt', PARAM_PATH), '~/myfile.txt');
+        $this->assertEquals(clean_param('~/../myfile.txt', PARAM_PATH), '~/myfile.txt');
+        $this->assertEquals(clean_param('/..b../.../myfile.txt', PARAM_PATH), '/..b../.../myfile.txt');
+        $this->assertEquals(clean_param('..b../.../myfile.txt', PARAM_PATH), '..b../.../myfile.txt');
+        $this->assertEquals(clean_param('/super//slashes///', PARAM_PATH), '/super/slashes/');
+    }
+
     function test_clean_param_username() {
         global $CFG;
         $currentstatus =  $CFG->extendedusernamechars;
index f7f2fca..5e29d5d 100644 (file)
@@ -179,8 +179,23 @@ class web_testcase extends advanced_testcase {
     }
 
     function test_out_as_local_url() {
+        global $CFG;
+        // Test http url.
         $url1 = new moodle_url('/lib/tests/weblib_test.php');
         $this->assertEquals('/lib/tests/weblib_test.php', $url1->out_as_local_url());
+
+        // Test https url.
+        $httpswwwroot = str_replace("http://", "https://", $CFG->wwwroot);
+        $url2 = new moodle_url($httpswwwroot.'/login/profile.php');
+        $this->assertEquals('/login/profile.php', $url2->out_as_local_url());
+
+        // Test http url matching wwwroot.
+        $url3 = new moodle_url($CFG->wwwroot);
+        $this->assertEquals('', $url3->out_as_local_url());
+
+        // Test http url matching wwwroot ending with slash (/).
+        $url3 = new moodle_url($CFG->wwwroot.'/');
+        $this->assertEquals('/', $url3->out_as_local_url());
     }
 
     /**
@@ -192,6 +207,31 @@ class web_testcase extends advanced_testcase {
         $url2->out_as_local_url();
     }
 
+    /**
+     * You should get error with modified url
+     *
+     * @expectedException coding_exception
+     * @return void
+     */
+    public function test_modified_url_out_as_local_url_error() {
+        global $CFG;
+
+        $modifiedurl = $CFG->wwwroot.'1';
+        $url3 = new moodle_url($modifiedurl.'/login/profile.php');
+        $url3->out_as_local_url();
+    }
+
+    /**
+     * Try get local url from external https url and you should get error
+     *
+     * @expectedException coding_exception
+     * @return void
+     */
+    public function test_https_out_as_local_url_error() {
+        $url4 = new moodle_url('https://www.google.com/lib/tests/weblib_test.php');
+        $url4->out_as_local_url();
+    }
+
     public function test_clean_text() {
         $text = "lala <applet>xx</applet>";
         $this->assertEquals($text, clean_text($text, FORMAT_PLAIN));
index 9b93a3a..4ee520b 100644 (file)
@@ -747,12 +747,18 @@ class moodle_url {
         global $CFG;
 
         $url = $this->out($escaped, $overrideparams);
-
-        if (strpos($url, $CFG->wwwroot) !== 0) {
+        $httpswwwroot = str_replace("http://", "https://", $CFG->wwwroot);
+
+        // $url should be equal to wwwroot or httpswwwroot. If not then throw exception.
+        if (($url === $CFG->wwwroot) || (strpos($url, $CFG->wwwroot.'/') === 0)) {
+            $localurl = substr($url, strlen($CFG->wwwroot));
+            return !empty($localurl) ? $localurl : '';
+        } else if (($url === $httpswwwroot) || (strpos($url, $httpswwwroot.'/') === 0)) {
+            $localurl = substr($url, strlen($httpswwwroot));
+            return !empty($localurl) ? $localurl : '';
+        } else {
             throw new coding_exception('out_as_local_url called on a non-local URL');
         }
-
-        return str_replace($CFG->wwwroot, '', $url);
     }
 
     /**
index edb655f..1c4ba83 100644 (file)
@@ -10,7 +10,9 @@ YUI.add('moodle-core-blocks', function(Y) {
         LIGHTBOX : 'lightbox',
         REGIONCONTENT : 'region-content',
         SKIPBLOCK : 'skip-block',
-        SKIPBLOCKTO : 'skip-block-to'
+        SKIPBLOCKTO : 'skip-block-to',
+        MYINDEX : 'page-my-index',
+        REGIONMAIN : 'region-main'
     }
 
     var DRAGBLOCK = function() {
@@ -26,6 +28,15 @@ YUI.add('moodle-core-blocks', function(Y) {
             this.samenodeclass = CSS.BLOCK;
             this.parentnodeclass = CSS.REGIONCONTENT;
 
+            // Add relevant classes and ID to 'content' block region on My Home page.
+            var myhomecontent = Y.Node.all('body#'+CSS.MYINDEX+' #'+CSS.REGIONMAIN+' > .'+CSS.REGIONCONTENT);
+            if (myhomecontent.size() > 0) {
+                var contentregion = myhomecontent.item(0);
+                contentregion.addClass(CSS.BLOCKREGION);
+                contentregion.set('id', CSS.REGIONCONTENT);
+                contentregion.one('div').addClass(CSS.REGIONCONTENT);
+            }
+
             // Initialise blocks dragging
             // Find all block regions on the page
             var blockregionlist = Y.Node.all('div.'+CSS.BLOCKREGION);
index cd3b558..fde60a4 100644 (file)
@@ -341,7 +341,9 @@ if (isloggedin() and !isguestuser()) {
     echo $OUTPUT->box_end();
 } else {
     include("index_form.html");
-    if (!empty($CFG->loginpageautofocus)) {
+    if ($errormsg) {
+        $PAGE->requires->js_init_call('M.util.focus_login_error', null, true);
+    } else if (!empty($CFG->loginpageautofocus)) {
         //focus username or password
         $PAGE->requires->js_init_call('M.util.focus_login_form', null, true);
     }
index 5c9f34d..c5f75e8 100644 (file)
@@ -30,9 +30,10 @@ if (!empty($CFG->loginpasswordautocomplete)) {
         </div>
         <?php
           if (!empty($errormsg)) {
-              echo '<div class="loginerrors">';
+              echo html_writer::start_tag('div', array('class' => 'loginerrors'));
+              echo html_writer::link('#', $errormsg, array('id' => 'loginerrormessage', 'class' => 'accesshide'));
               echo $OUTPUT->error_text($errormsg);
-              echo '</div>';
+              echo html_writer::end_tag('div');
           }
         ?>
         <form action="<?php echo $CFG->httpswwwroot; ?>/login/index.php" method="post" id="login" <?php echo $autocomplete; ?> >
index 2c56aa7..9ed302c 100644 (file)
@@ -203,7 +203,9 @@ class assign_feedback_comments extends assign_feedback_plugin {
             }
         }
 
-        $mform->addElement('editor', 'assignfeedbackcomments_editor', '', null, null);
+        $mform->addElement('editor', 'assignfeedbackcomments_editor', html_writer::tag('span', $this->get_name(),
+            array('class' => 'accesshide')), null, null);
+
         return true;
     }
 
index 63fd674..d79053a 100644 (file)
@@ -143,8 +143,8 @@ class assign_feedback_file extends assign_feedback_plugin {
                                                   'assignfeedback_file',
                                                   ASSIGNFEEDBACK_FILE_FILEAREA,
                                                   $gradeid);
-
-        $mform->addElement('filemanager', $elementname . '_filemanager', '', null, $fileoptions);
+        $mform->addElement('filemanager', $elementname . '_filemanager', html_writer::tag('span', $this->get_name(),
+            array('class' => 'accesshide')), null, $fileoptions);
 
         return true;
     }
index b933f0a..07e04c2 100644 (file)
@@ -358,7 +358,7 @@ function assign_print_overview($courses, &$htmlarray) {
     $rs->close();
 
     // Get all user submissions, indexed by assignment id.
-    $dbparams = array_merge(array($USER->id, $USER->id), $assignmentidparams);
+    $dbparams = array($USER->id, $USER->id);
     $mysubmissions = $DB->get_records_sql('SELECT
                                                a.id AS assignment,
                                                a.nosubmissions AS nosubmissions,
@@ -372,8 +372,8 @@ function assign_print_overview($courses, &$htmlarray) {
                                                g.userid = ?
                                            LEFT JOIN {assign_submission} s ON
                                                s.assignment = a.id AND
-                                               s.userid = ? AND
-                                               a.id ' . $sqlassignmentids, $dbparams);
+                                               s.userid = ?
+                                           WHERE a.id ' . $sqlassignmentids, $dbparams);
 
     foreach ($assignments as $assignment) {
         // Do not show assignments that are not open.
index 16364e2..33f97be 100644 (file)
@@ -110,7 +110,12 @@ class assign_submission_comments extends assign_submission_plugin {
      * @return bool was it a success? (false will trigger a rollback)
      */
     public function upgrade_settings(context $oldcontext, stdClass $oldassignment, & $log) {
-        // No settings to upgrade.
+        if ($oldassignment->assignmenttype == 'upload') {
+            // Disable if allow notes was not enabled.
+            if (!$oldassignment->var2) {
+                $this->disable();
+            }
+        }
         return true;
     }
 
index 39c3561..f6e1ba2 100644 (file)
@@ -166,7 +166,9 @@ class assign_submission_file extends assign_submission_plugin {
                                                   'assignsubmission_file',
                                                   ASSIGNSUBMISSION_FILE_FILEAREA,
                                                   $submissionid);
-        $mform->addElement('filemanager', 'files_filemanager', '', null, $fileoptions);
+        $mform->addElement('filemanager', 'files_filemanager', html_writer::tag('span', $this->get_name(),
+            array('class' => 'accesshide')), null, $fileoptions);
+
         return true;
     }
 
@@ -342,13 +344,31 @@ class assign_submission_file extends assign_submission_plugin {
      * @return bool Was it a success? (false will trigger rollback)
      */
     public function upgrade_settings(context $oldcontext, stdClass $oldassignment, & $log) {
+        global $DB;
+
         if ($oldassignment->assignmenttype == 'uploadsingle') {
             $this->set_config('maxfilesubmissions', 1);
             $this->set_config('maxsubmissionsizebytes', $oldassignment->maxbytes);
             return true;
-        } else {
+        } else if ($oldassignment->assignmenttype == 'upload') {
             $this->set_config('maxfilesubmissions', $oldassignment->var1);
             $this->set_config('maxsubmissionsizebytes', $oldassignment->maxbytes);
+
+            // Advanced file upload uses a different setting to do the same thing.
+            $DB->set_field('assign',
+                           'submissiondrafts',
+                           $oldassignment->var4,
+                           array('id'=>$this->assignment->get_instance()->id));
+
+            // Convert advanced file upload "hide description before due date" setting.
+            $alwaysshow = 0;
+            if (!$oldassignment->var3) {
+                $alwaysshow = 1;
+            }
+            $DB->set_field('assign',
+                           'alwaysshowdescription',
+                           $alwaysshow,
+                           array('id'=>$this->assignment->get_instance()->id));
             return true;
         }
     }
index edbe85a..57e51ab 100644 (file)
@@ -95,7 +95,9 @@ class assign_submission_onlinetext extends assign_submission_plugin {
                                              'assignsubmission_onlinetext',
                                              ASSIGNSUBMISSION_ONLINETEXT_FILEAREA,
                                              $submissionid);
-        $mform->addElement('editor', 'onlinetext_editor', '', null, $editoroptions);
+        $mform->addElement('editor', 'onlinetext_editor', html_writer::tag('span', $this->get_name(),
+            array('class' => 'accesshide')), null, $editoroptions);
+
         return true;
     }
 
index 43eb7d0..9e8e8ed 100644 (file)
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * assign module PHPUnit data generator class
+ * assign module data generator class
  *
  * @package mod_assign
- * @category phpunit
+ * @category test
  * @copyright 2012 Paul Charsley
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class mod_assign_generator extends phpunit_module_generator {
+class mod_assign_generator extends testing_module_generator {
 
     /**
      * Create new assign module instance
index 8454229..51fb6eb 100644 (file)
@@ -136,7 +136,7 @@ class mod_assign_upgradelib_testcase extends advanced_testcase {
         $plugin = $assign->get_submission_plugin_by_type('onlinetext');
         $this->assertEmpty($plugin->is_enabled());
         $plugin = $assign->get_submission_plugin_by_type('comments');
-        $this->assertNotEmpty($plugin->is_enabled());
+        $this->assertEmpty($plugin->is_enabled());
         $plugin = $assign->get_submission_plugin_by_type('file');
         $this->assertNotEmpty($plugin->is_enabled());
         $plugin = $assign->get_feedback_plugin_by_type('comments');
index 055457b..abffb4e 100644 (file)
@@ -173,6 +173,24 @@ class assign_upgrade_manager {
                                                        array('areaid'=>$gradingarea->id));
             }
 
+            // Upgrade availability data.
+            $DB->set_field('course_modules_avail_fields',
+                           'coursemoduleid',
+                           $newcoursemodule->id,
+                           array('coursemoduleid'=>$oldcoursemodule->id));
+            $DB->set_field('course_modules_availability',
+                           'coursemoduleid',
+                           $newcoursemodule->id,
+                           array('coursemoduleid'=>$oldcoursemodule->id));
+            $DB->set_field('course_modules_availability',
+                           'sourcecmid',
+                           $newcoursemodule->id,
+                           array('sourcecmid'=>$oldcoursemodule->id));
+            $DB->set_field('course_sections_availability',
+                           'sourcecmid',
+                           $newcoursemodule->id,
+                           array('sourcecmid'=>$oldcoursemodule->id));
+
             // Upgrade completion data.
             $DB->set_field('course_modules_completion',
                            'coursemoduleid',
index ea07132..9bd8b22 100644 (file)
@@ -18,7 +18,7 @@
  * mod_assignment data generator
  *
  * @package    mod_assignment
- * @category   phpunit
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -27,14 +27,14 @@ defined('MOODLE_INTERNAL') || die();
 
 
 /**
- * Assignment module PHPUnit data generator class
+ * Assignment module data generator class
  *
  * @package    mod_assignment
- * @category   phpunit
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class mod_assignment_generator extends phpunit_module_generator {
+class mod_assignment_generator extends testing_module_generator {
 
     /**
      * Create new assignment module instance
index 4ff538d..389769d 100644 (file)
@@ -18,7 +18,7 @@
  * mod_data data generator
  *
  * @package    mod_data
- * @category   phpunit
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -27,14 +27,14 @@ defined('MOODLE_INTERNAL') || die();
 
 
 /**
- * Page module PHPUnit data generator class
+ * Database module data generator class
  *
  * @package    mod_data
- * @category   phpunit
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class mod_data_generator extends phpunit_module_generator {
+class mod_data_generator extends testing_module_generator {
 
     /**
      * Create new data module instance
index ea00ab2..903c9a1 100644 (file)
@@ -112,6 +112,7 @@ class restore_forum_activity_task extends restore_activity_task {
         $rules[] = new restore_log_rule('forum', 'delete discussion', 'view.php?id={course_module}', '{forum}');
         $rules[] = new restore_log_rule('forum', 'add post', 'discuss.php?d={forum_discussion}&parent={forum_post}', '{forum_post}');
         $rules[] = new restore_log_rule('forum', 'update post', 'discuss.php?d={forum_discussion}#p{forum_post}&parent={forum_post}', '{forum_post}');
+        $rules[] = new restore_log_rule('forum', 'update post', 'discuss.php?d={forum_discussion}&parent={forum_post}', '{forum_post}');
         $rules[] = new restore_log_rule('forum', 'prune post', 'discuss.php?d={forum_discussion}', '{forum_post}');
         $rules[] = new restore_log_rule('forum', 'delete post', 'discuss.php?d={forum_discussion}', '[post]');
 
index 369b593..7f98768 100644 (file)
@@ -18,7 +18,7 @@
  * mod_forum data generator
  *
  * @package    mod_forum
- * @category   phpunit
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -27,14 +27,14 @@ defined('MOODLE_INTERNAL') || die();
 
 
 /**
- * Assignment module PHPUnit data generator class
+ * Forum module data generator class
  *
  * @package    mod_forum
- * @category   phpunit
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class mod_forum_generator extends phpunit_module_generator {
+class mod_forum_generator extends testing_module_generator {
 
     /**
      * Create new forum module instance
index f4addcc..10b8541 100644 (file)
@@ -2309,9 +2309,14 @@ abstract class lesson_page extends lesson_base {
         }
         if (count($this->answers)>0) {
             $count = 0;
+            $qtype = $properties->qtype;
             foreach ($this->answers as $answer) {
-                $properties->{'answer_editor['.$count.']'} = array('text'=>$answer->answer, 'format'=>$answer->answerformat);
-                $properties->{'response_editor['.$count.']'} = array('text'=>$answer->response, 'format'=>$answer->responseformat);
+                $properties->{'answer_editor['.$count.']'} = array('text' => $answer->answer, 'format' => $answer->answerformat);
+                if ($qtype != LESSON_PAGE_MATCHING) {
+                    $properties->{'response_editor['.$count.']'} = array('text' => $answer->response, 'format' => $answer->responseformat);
+                } else {
+                    $properties->{'response_editor['.$count.']'} = $answer->response;
+                }
                 $properties->{'jumpto['.$count.']'} = $answer->jumpto;
                 $properties->{'score['.$count.']'} = $answer->score;
                 $count++;
index 82769f6..00b0dcb 100644 (file)
@@ -72,16 +72,17 @@ class lesson_page_type_matching extends lesson_page {
         foreach ($answers as $answer) {
             // get all the response
             if ($answer->response != NULL) {
-                $responses[] = trim($answer->response);
+                $responses[$answer->id] = trim($answer->response);
             }
         }
 
         $responseoptions = array(''=>get_string('choosedots'));
         if (!empty($responses)) {
-            shuffle($responses);
-            $responses = array_unique($responses);
-            foreach ($responses as $response) {
-                $responseoptions[htmlspecialchars(trim($response))] = $response;
+            $shuffleresponses = $responses;
+            shuffle($shuffleresponses);
+            foreach ($shuffleresponses as  $response) {
+                $key = array_search($response, $responses);
+                $responseoptions[$key] = $response;
             }
         }
         if (isset($USER->modattempts[$this->lesson->id]) && !empty($attempt->useranswer)) {
@@ -115,9 +116,9 @@ class lesson_page_type_matching extends lesson_page {
                 $answer->answer = $properties->answer_editor[$i]['text'];
                 $answer->answerformat = $properties->answer_editor[$i]['format'];
             }
-            if (!empty($properties->response_editor[$i]) && is_array($properties->response_editor[$i])) {
-                $answer->response = $properties->response_editor[$i]['text'];
-                $answer->responseformat = $properties->response_editor[$i]['format'];
+            if (!empty($properties->response_editor[$i])) {
+                $answer->response = $properties->response_editor[$i];
+                $answer->responseformat = 0;
             }
 
             if (isset($properties->jumpto[$i])) {
@@ -171,27 +172,26 @@ class lesson_page_type_matching extends lesson_page {
         $wrong   = array_shift($answers);
 
         foreach ($answers as $key=>$answer) {
-            if ($answer->answer === '' or $answer->response === '') {
-                // incomplete option!
-                unset($answers[$key]);
+            if ($answer->answer !== '' or $answer->response !== '') {
+                $answers[$answer->id] = $answer;
             }
+            unset($answers[$key]);
         }
         // get he users exact responses for record keeping
         $hits = 0;
         $userresponse = array();
-        foreach ($response as $key => $value) {
-            foreach($answers as $answer) {
-                if ($value === $answer->response) {
-                    $userresponse[] = $answer->id;
-                }
-                if ((int)$answer->id === (int)$key) {
-                    $result->studentanswer .= '<br />'.format_text($answer->answer, $answer->answerformat, $formattextdefoptions).' = '.$value;
-                }
-                if ((int)$answer->id === (int)$key and $value === $answer->response) {
+        foreach ($response as $id => $value) {
+            $userresponse[] = $value;
+            // Make sure the user's answer is exist in question's answer
+            if (array_key_exists($id, $answers)) {
+                $answer = $answers[$id];
+                $result->studentanswer .= '<br />'.format_text($answer->answer, $answer->answerformat, $formattextdefoptions).' = '.$answers[$value]->response;
+                if ($id == $value) {
                     $hits++;
                 }
             }
         }
+
         $result->userresponse = implode(",", $userresponse);
 
         if ($hits == count($answers)) {
@@ -315,9 +315,9 @@ class lesson_page_type_matching extends lesson_page {
                 $this->answers[$i]->answer = $properties->answer_editor[$i]['text'];
                 $this->answers[$i]->answerformat = $properties->answer_editor[$i]['format'];
             }
-            if (!empty($properties->response_editor[$i]) && is_array($properties->response_editor[$i])) {
-                $this->answers[$i]->response = $properties->response_editor[$i]['text'];
-                $this->answers[$i]->responseformat = $properties->response_editor[$i]['format'];
+            if (!empty($properties->response_editor[$i])) {
+                $this->answers[$i]->response = $properties->response_editor[$i];
+                $this->answers[$i]->responseformat = 0;
             }
 
             if (isset($properties->jumpto[$i])) {
@@ -473,12 +473,18 @@ class lesson_add_page_form_matching extends lesson_add_page_form_base {
         for ($i = 2; $i < $this->_customdata['lesson']->maxanswers+2; $i++) {
             $this->_form->addElement('header', 'matchingpair'.($i-1), get_string('matchingpair', 'lesson', $i-1));
             $this->add_answer($i, NULL, ($i < 4));
-            $this->add_response($i, get_string('matchesanswer','lesson'), ($i < 4));
+            $required = ($i < 4);
+            $label = get_string('matchesanswer','lesson');
+            $count = $i;
+            $this->_form->addElement('text', 'response_editor['.$count.']', $label, array('size'=>'50'));
+            $this->_form->setDefault('response_editor['.$count.']', '');
+            if ($required) {
+                $this->_form->addRule('response_editor['.$count.']', get_string('required'), 'required', null, 'client');
+            }
         }
     }
 }
 
-
 class lesson_display_answer_form_matching extends moodleform {
 
     public function definition() {
index b3d3480..5fc7a09 100644 (file)
@@ -18,7 +18,7 @@
  * mod_lti data generator
  *
  * @package    mod_lti
- * @category   phpunit
+ * @category   test
  * @copyright  Copyright (c) 2012 Moodlerooms Inc. (http://www.moodlerooms.com)
  * @author     Mark Nielsen
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Assignment module PHPUnit data generator class
+ * LTI module data generator class
  *
  * @package    mod_lti
- * @category   phpunit
+ * @category   test
  * @copyright  Copyright (c) 2012 Moodlerooms Inc. (http://www.moodlerooms.com)
  * @author     Mark Nielsen
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class mod_lti_generator extends phpunit_module_generator {
+class mod_lti_generator extends testing_module_generator {
 
     /**
      * Create new lti module instance
@@ -98,4 +98,4 @@ class mod_lti_generator extends phpunit_module_generator {
         $id = lti_add_instance($record, null);
         return $this->post_add_instance($id, $record->coursemodule);
     }
-}
\ No newline at end of file
+}
index d2ae2fa..533c9ff 100644 (file)
@@ -18,7 +18,7 @@
  * mod_page data generator
  *
  * @package    mod_page
- * @category   phpunit
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -27,14 +27,14 @@ defined('MOODLE_INTERNAL') || die();
 
 
 /**
- * Page module PHPUnit data generator class
+ * Page module data generator class
  *
  * @package    mod_page
- * @category   phpunit
+ * @category   test
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class mod_page_generator extends phpunit_module_generator {
+class mod_page_generator extends testing_module_generator {
 
     /**
      * Create new page module instance
index c8776de..175e5c6 100644 (file)
@@ -100,7 +100,8 @@ class mod_quiz_overdue_attempt_updater {
 
 
         // SQL to compute timeclose and timelimit for each attempt:
-        $quizausersql = quiz_get_attempt_usertime_sql();
+        $quizausersql = quiz_get_attempt_usertime_sql(
+                "iquiza.state IN ('inprogress', 'overdue') AND iquiza.timecheckstate <= :iprocessto");
 
         // This query should have all the quiz_attempts columns.
         return $DB->get_recordset_sql("
@@ -116,6 +117,6 @@ class mod_quiz_overdue_attempt_updater {
             AND quiza.timecheckstate <= :processto
        ORDER BY quiz.course, quiza.quiz",
 
-                array('processto' => $processto));
+                array('processto' => $processto, 'iprocessto' => $processto));
     }
 }
index 8bacae7..a366a84 100644 (file)
@@ -783,34 +783,48 @@ function quiz_update_open_attempts(array $conditions) {
     }
 
     $params = array();
-    $coursecond = '';
-    $usercond = '';
-    $quizcond = '';
-    $groupcond = '';
+    $wheres = array("quiza.state IN ('inprogress', 'overdue')");
+    $iwheres = array("iquiza.state IN ('inprogress', 'overdue')");
 
     if (isset($conditions['courseid'])) {
         list ($incond, $inparams) = $DB->get_in_or_equal($conditions['courseid'], SQL_PARAMS_NAMED, 'cid');
         $params = array_merge($params, $inparams);
-        $coursecond = "AND quiza.quiz IN (SELECT q.id FROM {quiz} q WHERE q.course $incond)";
+        $wheres[] = "quiza.quiz IN (SELECT q.id FROM {quiz} q WHERE q.course $incond)";
+        list ($incond, $inparams) = $DB->get_in_or_equal($conditions['courseid'], SQL_PARAMS_NAMED, 'icid');
+        $params = array_merge($params, $inparams);
+        $iwheres[] = "iquiza.quiz IN (SELECT q.id FROM {quiz} q WHERE q.course $incond)";
     }
+
     if (isset($conditions['userid'])) {
         list ($incond, $inparams) = $DB->get_in_or_equal($conditions['userid'], SQL_PARAMS_NAMED, 'uid');
         $params = array_merge($params, $inparams);
-        $usercond = "AND quiza.userid $incond";
+        $wheres[] = "quiza.userid $incond";
+        list ($incond, $inparams) = $DB->get_in_or_equal($conditions['userid'], SQL_PARAMS_NAMED, 'iuid');
+        $params = array_merge($params, $inparams);
+        $iwheres[] = "iquiza.userid $incond";
     }
+
     if (isset($conditions['quizid'])) {
         list ($incond, $inparams) = $DB->get_in_or_equal($conditions['quizid'], SQL_PARAMS_NAMED, 'qid');
         $params = array_merge($params, $inparams);
-        $quizcond = "AND quiza.quiz $incond";
+        $wheres[] = "quiza.quiz $incond";
+        list ($incond, $inparams) = $DB->get_in_or_equal($conditions['quizid'], SQL_PARAMS_NAMED, 'iqid');
+        $params = array_merge($params, $inparams);
+        $iwheres[] = "iquiza.quiz $incond";
     }
+
     if (isset($conditions['groupid'])) {
         list ($incond, $inparams) = $DB->get_in_or_equal($conditions['groupid'], SQL_PARAMS_NAMED, 'gid');
         $params = array_merge($params, $inparams);
-        $groupcond = "AND quiza.quiz IN (SELECT qo.quiz FROM {quiz_overrides} qo WHERE qo.groupid $incond)";
+        $wheres[] = "quiza.quiz IN (SELECT qo.quiz FROM {quiz_overrides} qo WHERE qo.groupid $incond)";
+        list ($incond, $inparams) = $DB->get_in_or_equal($conditions['groupid'], SQL_PARAMS_NAMED, 'igid');
+        $params = array_merge($params, $inparams);
+        $iwheres[] = "iquiza.quiz IN (SELECT qo.quiz FROM {quiz_overrides} qo WHERE qo.groupid $incond)";
     }
 
     // SQL to compute timeclose and timelimit for each attempt:
-    $quizausersql = quiz_get_attempt_usertime_sql();
+    $quizausersql = quiz_get_attempt_usertime_sql(
+            implode("\n                AND ", $iwheres));
 
     // SQL to compute the new timecheckstate
     $timecheckstatesql = "
@@ -822,11 +836,7 @@ function quiz_update_open_attempts(array $conditions) {
           CASE WHEN quiza.state = 'overdue' THEN quiz.graceperiod ELSE 0 END";
 
     // SQL to select which attempts to process
-    $attemptselect = " quiza.state IN ('inprogress', 'overdue')
-                       $coursecond
-                       $usercond
-                       $quizcond
-                       $groupcond";
+    $attemptselect = implode("\n                         AND ", $wheres);
 
    /*
     * Each database handles updates with inner joins differently:
@@ -876,9 +886,14 @@ function quiz_update_open_attempts(array $conditions) {
 /**
  * Returns SQL to compute timeclose and timelimit for every attempt, taking into account user and group overrides.
  *
- * @return string         SQL select with columns attempt.id, usertimeclose, usertimelimit
+ * @param string $redundantwhereclauses extra where clauses to add to the subquery
+ *      for performance. These can use the table alias iquiza for the quiz attempts table.
+ * @return string SQL select with columns attempt.id, usertimeclose, usertimelimit.
  */
-function quiz_get_attempt_usertime_sql() {
+function quiz_get_attempt_usertime_sql($redundantwhereclauses = '') {
+    if ($redundantwhereclauses) {
+        $redundantwhereclauses = 'WHERE ' . $redundantwhereclauses;
+    }
     // The multiple qgo JOINS are necessary because we want timeclose/timelimit = 0 (unlimited) to supercede
     // any other group override
     $quizausersql = "
@@ -894,6 +909,7 @@ function quiz_get_attempt_usertime_sql() {
       LEFT JOIN {quiz_overrides} qgo2 ON qgo2.quiz = iquiza.quiz AND qgo2.groupid = gm.groupid AND qgo2.timeclose > 0
       LEFT JOIN {quiz_overrides} qgo3 ON qgo3.quiz = iquiza.quiz AND qgo3.groupid = gm.groupid AND qgo3.timelimit = 0
       LEFT JOIN {quiz_overrides} qgo4 ON qgo4.quiz = iquiza.quiz AND qgo4.groupid = gm.groupid AND qgo4.timelimit > 0
+          $redundantwhereclauses
        GROUP BY iquiza.id, iquiz.id, iquiz.timeclose, iquiz.timelimit";
     return $quizausersql;
 }
index b889d3d..fa2e1db 100644 (file)
@@ -23,7 +23,7 @@ defined('MOODLE_INTERNAL') || die();
  * @copyright 2012 The Open University
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class mod_quiz_generator extends phpunit_module_generator {
+class mod_quiz_generator extends testing_module_generator {
 
     /**
      * Create new quiz module instance.
index e5e4fbc..7c0a814 100644 (file)
@@ -834,7 +834,7 @@ function scorm_course_format_display($user, $course) {
 }
 
 function scorm_view_display ($user, $scorm, $action, $cm) {
-    global $CFG, $DB, $PAGE, $OUTPUT;
+    global $CFG, $DB, $PAGE, $OUTPUT, $COURSE;
 
     if ($scorm->scormtype != SCORM_TYPE_LOCAL && $scorm->updatefreq == SCORM_UPDATE_EVERYTIME) {
         scorm_parse($scorm, false);
@@ -913,7 +913,7 @@ function scorm_view_display ($user, $scorm, $action, $cm) {
                       <label for="a"><?php print_string('newattempt', 'scorm') ?></label>
             <?php
         }
-        if (!empty($scorm->popup)) {
+        if ($COURSE->format != 'scorm' && !empty($scorm->popup)) {
             echo '<input type="hidden" name="display" value="popup" />'."\n";
         }
         ?>
index 5325606..9b23f51 100644 (file)
@@ -27,6 +27,9 @@
         <testsuite name="core_phpunit">
             <directory suffix="_test.php">lib/phpunit/tests</directory>
         </testsuite>
+        <testsuite name="core_test">
+            <directory suffix="_test.php">lib/testing/tests</directory>
+        </testsuite>
         <testsuite name="core_db">
             <directory suffix="_test.php">lib/ddl/tests</directory>
             <directory suffix="_test.php">lib/dml/tests</directory>
index 69ecb56..6f3a66f 100644 (file)
@@ -107,6 +107,15 @@ function xmldb_qtype_essay_upgrade($oldversion) {
     // Moodle v2.4.0 release upgrade line
     // Put any upgrade step following this
 
+    if ($oldversion < 2013011800) {
+        // Then we delete the old question_answers rows for essay questions.
+        $DB->delete_records_select('qtype_essay_options', "NOT EXISTS (
+                SELECT 1 FROM {question} WHERE qtype = 'essay' AND
+                    {question}.id = {qtype_essay_options}.questionid)");
+
+        // Essay savepoint reached.
+        upgrade_plugin_savepoint(true, 2013011800, 'qtype', 'essay');
+    }
 
     return true;
 }
index dac965e..10a1346 100644 (file)
@@ -80,6 +80,13 @@ class qtype_essay extends question_type {
         $question->graderinfoformat = $questiondata->options->graderinfoformat;
     }
 
+    public function delete_question($questionid, $contextid) {
+        global $DB;
+
+        $DB->delete_records('qtype_essay_options', array('questionid' => $questionid));
+        parent::delete_question($questionid, $contextid);
+    }
+
     /**
      * @return array the different response formats that the question type supports.
      * internal name => human-readable name.
index 997941c..67a0d8e 100644 (file)
@@ -26,7 +26,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $plugin->component = 'qtype_essay';
-$plugin->version   = 2012112900;
+$plugin->version   = 2013011800;
 
 $plugin->requires  = 2012112900;
 
index d02b181..fd9262d 100644 (file)
@@ -150,7 +150,7 @@ class wikimedia {
         $this->_param['gsrlimit'] = WIKIMEDIA_THUMBS_PER_PAGE;
         $this->_param['gsroffset'] = $page * WIKIMEDIA_THUMBS_PER_PAGE;
         $this->_param['prop']   = 'imageinfo';
-        $this->_param['iiprop'] = 'url|dimensions|mime';
+        $this->_param['iiprop'] = 'url|dimensions|mime|timestamp|size|user';
         $this->_param['iiurlwidth'] = WIKIMEDIA_IMAGE_SIDE_LENGTH;
         $this->_param['iiurlheight'] = WIKIMEDIA_IMAGE_SIDE_LENGTH;
         //didn't work with POST
@@ -162,26 +162,45 @@ class wikimedia {
                 $file_type = $page['imageinfo'][0]['mime'];
                 $image_types = array('image/jpeg', 'image/png', 'image/gif', 'image/svg+xml');
                 if (in_array($file_type, $image_types)) {  //is image
-                    $thumbnail = $this->get_thumb_url($page['imageinfo'][0]['url'], $page['imageinfo'][0]['width'], $page['imageinfo'][0]['height']);
-                    $source = $page['imageinfo'][0]['thumburl'];        //upload scaled down image
                     $extension = pathinfo($title, PATHINFO_EXTENSION);
                     if (strcmp($extension, 'svg') == 0) {               //upload png version of svg-s
                         $title .= '.png';
                     }
-                } else {                                   //other file types
-                    $thumbnail = '';
-                    $source = $page['imageinfo'][0]['url'];
+                    if ($page['imageinfo'][0]['thumbwidth'] < $page['imageinfo'][0]['width']) {
+                        $attrs = array(
+                            //upload scaled down image
+                            'source' => $page['imageinfo'][0]['thumburl'],
+                            'image_width' => $page['imageinfo'][0]['thumbwidth'],
+                            'image_height' => $page['imageinfo'][0]['thumbheight']
+                        );
+                    } else {
+                        $attrs = array(
+                            //upload full size image
+                            'source' => $page['imageinfo'][0]['url'],
+                            'image_width' => $page['imageinfo'][0]['width'],
+                            'image_height' => $page['imageinfo'][0]['height'],
+                            'size' => $page['imageinfo'][0]['size']
+                        );
+                    }
+                    $attrs += array(
+                        'thumbnail' => $this->get_thumb_url($page['imageinfo'][0]['url'], $page['imageinfo'][0]['width'], $page['imageinfo'][0]['height'], 120),
+                        'icon' => $this->get_thumb_url($page['imageinfo'][0]['url'], $page['imageinfo'][0]['width'], $page['imageinfo'][0]['height'], 24),
+                        'author' => $page['imageinfo'][0]['user'],
+                        'datemodified' => strtotime($page['imageinfo'][0]['timestamp']),
+                        );
+                } else {  // other file types
+                    $attrs = array(
+                        $thumbnail = '',
+                        $source = $page['imageinfo'][0]['url']);
                 }
                 $files_array[] = array(
                     'title'=>substr($title, 5),         //chop off 'File:'
-                    'thumbnail'=>$thumbnail,
                     'thumbnail_width'=>120,
                     'thumbnail_height'=>120,
-                    // plugin-dependent unique path to the file (id, url, path, etc.)
-                    'source'=>$source,
+                    'license' => 'cc-sa',
                     // the accessible url of the file
                     'url'=>$page['imageinfo'][0]['descriptionurl']
-                );
+                ) + $attrs;
             }
         }
         return $files_array;
index abd131c..45d9fd6 100644 (file)
@@ -120,7 +120,7 @@ class webservice_rest_server extends webservice_base_server {
             } else {
                 $response = '<?xml version="1.0" encoding="UTF-8" ?>'."\n";
                 $response .= '<RESPONSE>'."\n";
-                $response .= self::xmlize_result($this->returns, $this->function->returns_desc);
+                $response .= self::xmlize_result($validatedvalues, $this->function->returns_desc);
                 $response .= '</RESPONSE>'."\n";
             }
         }
@@ -248,4 +248,4 @@ class webservice_rest_test_client implements webservice_test_client_interface {
     public function simpletest($serverurl, $function, $params) {
         return download_file_content($serverurl.'&wsfunction='.$function, null, $params);
     }
-}
\ No newline at end of file
+}