Merge branch 'MDL-37506' of git://github.com/timhunt/moodle
authorDan Poltawski <dan@moodle.com>
Wed, 23 Jan 2013 00:54:32 +0000 (08:54 +0800)
committerDan Poltawski <dan@moodle.com>
Wed, 23 Jan 2013 00:54:32 +0000 (08:54 +0800)
143 files changed:
admin/mnet/index.php
admin/settings/appearance.php
admin/tool/customlang/filter_form.php
admin/tool/phpunit/cli/init.php
admin/tool/phpunit/cli/util.php
admin/tool/uploaduser/index.php
admin/tool/uploaduser/lang/en/tool_uploaduser.php
admin/tool/uploaduser/version.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
admin/user.php
admin/webservice/forms.php
auth/cas/CAS/CAS.php
auth/cas/auth.php
blocks/calendar_month/block_calendar_month.php
blocks/online_users/tests/generator/lib.php
blog/locallib.php
calendar/lib.php
calendar/managesubscriptions.php
calendar/managesubscriptions_form.php
calendar/renderer.php
calendar/upgrade.txt
course/category.php
course/dnduploadlib.php
course/edit.php
course/editcategory.php
course/index.php
course/lib.php
course/mod.php
course/modedit.php
course/rest.php
course/yui/dragdrop/dragdrop.js
course/yui/toolboxes/toolboxes.js
enrol/locallib.php
files/renderer.php
grade/report/grader/lib.php
grade/report/grader/styles.css
group/lib.php
lang/en/admin.php
lang/en/cache.php
lang/en/calendar.php
lang/en/question.php
lib/adminlib.php
lib/ajax/blocks.php
lib/completionlib.php
lib/db/caches.php
lib/db/events.php
lib/db/log.php
lib/deprecatedlib.php
lib/dml/moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/editor/tinymce/lib.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/pluginlib.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
lib/yui/formchangechecker/formchangechecker.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/lib_test.php [new file with mode: 0644]
mod/assign/tests/upgradelib_test.php
mod/assign/upgradelib.php
mod/assignment/tests/generator/lib.php
mod/book/tool/print/index.php
mod/book/tool/print/print.css
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/essay.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/report/statistics/report.php
mod/quiz/tests/generator/lib.php
mod/scorm/locallib.php
phpunit.xml.dist
question/format.php
question/format/gift/tests/giftformat_test.php
question/type/essay/db/upgrade.php
question/type/essay/questiontype.php
question/type/essay/version.php
question/type/multianswer/questiontype.php
question/type/shortanswer/backup/moodle2/backup_qtype_shortanswer_plugin.class.php
question/type/shortanswer/backup/moodle2/restore_qtype_shortanswer_plugin.class.php
question/type/shortanswer/db/install.xml
question/type/shortanswer/db/upgrade.php [new file with mode: 0755]
question/type/shortanswer/questiontype.php
question/type/shortanswer/tests/upgradelibnewqe_test.php [moved from question/type/shortanswer/tests/tupgradelibnewqe_test.php with 100% similarity]
question/type/shortanswer/version.php
report/outline/index.php
repository/wikimedia/lang/en/repository_wikimedia.php
repository/wikimedia/lib.php
repository/wikimedia/wikimedia.php
theme/base/style/calendar.css
user/profile/lib.php
version.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 6b527ec..da078d7 100644 (file)
@@ -81,6 +81,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configselect('calendar_exportlookahead', new lang_string('configexportlookahead','admin'), new lang_string('helpexportlookahead', 'admin'), 365, $days));
     $temp->add(new admin_setting_configselect('calendar_exportlookback', new lang_string('configexportlookback','admin'), new lang_string('helpexportlookback', 'admin'), 5, $days));
     $temp->add(new admin_setting_configtext('calendar_exportsalt', new lang_string('calendarexportsalt','admin'), new lang_string('configcalendarexportsalt', 'admin'), random_string(60)));
+    $temp->add(new admin_setting_configcheckbox('calendar_showicalsource', new lang_string('configshowicalsource', 'admin'), new lang_string('helpshowicalsource','admin'), 1));
     $ADMIN->add('appearance', $temp);
 
     // blog
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 384a900..f249f1d 100644 (file)
@@ -94,6 +94,7 @@ $STD_FIELDS = array('id', 'firstname', 'lastname', 'username', 'email',
         'oldusername', // use when renaming users - this is the original username
         'suspended',   // 1 means suspend user account, 0 means activate user account, nothing means keep as is for existing users
         'deleted',     // 1 means delete user
+        'mnethostid',  // Can not be used for adding, updating or deleting of users - only for enrolments, groups, cohorts and suspending.
     );
 
 $PRF_FIELDS = array();
@@ -285,19 +286,58 @@ if ($formdata = $mform2->is_cancelled()) {
             $userserrors++;
             continue;
         }
+
         if ($user->username !== clean_param($user->username, PARAM_USERNAME)) {
             $upt->track('status', get_string('invalidusername', 'error', 'username'), 'error');
             $upt->track('username', $errorstr, 'error');
             $userserrors++;
         }
-        if ($existinguser = $DB->get_record('user', array('username'=>$user->username, 'mnethostid'=>$CFG->mnet_localhost_id))) {
+
+        if (empty($user->mnethostid)) {
+            $user->mnethostid = $CFG->mnet_localhost_id;
+        }
+
+        if ($existinguser = $DB->get_record('user', array('username'=>$user->username, 'mnethostid'=>$user->mnethostid))) {
             $upt->track('id', $existinguser->id, 'normal', false);
         }
 
-        // find out in username incrementing required
-        if ($existinguser and $optype == UU_USER_ADDINC) {
-            $user->username = uu_increment_username($user->username);
-            $existinguser = false;
+        if ($user->mnethostid == $CFG->mnet_localhost_id) {
+            $remoteuser = false;
+
+            // Find out if username incrementing required.
+            if ($existinguser and $optype == UU_USER_ADDINC) {
+                $user->username = uu_increment_username($user->username);
+                $existinguser = false;
+            }
+
+        } else {
+            if (!$existinguser or $optype == UU_USER_ADDINC) {
+                $upt->track('status', get_string('errormnetadd', 'tool_uploaduser'), 'error');
+                $userserrors++;
+                continue;
+            }
+
+            $remoteuser = true;
+
+            // Make sure there are no changes of existing fields except the suspended status.
+            foreach ((array)$existinguser as $k => $v) {
+                if ($k === 'suspended') {
+                    continue;
+                }
+                if (property_exists($user, $k)) {
+                    $user->$k = $v;
+                }
+                if (in_array($k, $upt->columns)) {
+                    if ($k === 'password' or $k === 'oldusername' or $k === 'deleted') {
+                        $upt->track($k, '', 'normal', false);
+                    } else {
+                        $upt->track($k, s($v), 'normal', false);
+                    }
+                }
+            }
+            unset($user->oldusername);
+            unset($user->password);
+            $user->auth = $existinguser->auth;
         }
 
         // notify about nay username changes
@@ -337,7 +377,7 @@ if ($formdata = $mform2->is_cancelled()) {
 
         // delete user
         if (!empty($user->deleted)) {
-            if (!$allowdeletes) {
+            if (!$allowdeletes or $remoteuser) {
                 $usersskipped++;
                 $upt->track('status', $strusernotdeletedoff, 'warning');
                 continue;
@@ -475,7 +515,7 @@ if ($formdata = $mform2->is_cancelled()) {
             $doupdate = false;
             $dologout = false;
 
-            if ($updatetype != UU_UPDATE_NOCHANGES) {
+            if ($updatetype != UU_UPDATE_NOCHANGES and !$remoteuser) {
                 if (!empty($user->auth) and $user->auth !== $existinguser->auth) {
                     $upt->track('auth', s($existinguser->auth).'-->'.s($user->auth), 'info', false);
                     $existinguser->auth = $user->auth;
@@ -573,7 +613,11 @@ if ($formdata = $mform2->is_cancelled()) {
             // changing of passwords is a special case
             // do not force password changes for external auth plugins!
             $oldpw = $existinguser->password;
-            if (!$isinternalauth) {
+
+            if ($remoteuser) {
+                // Do not mess with passwords of remote users.
+
+            } else if (!$isinternalauth) {
                 $existinguser->password = 'not cached';
                 $upt->track('password', '-', 'normal', false);
                 // clean up prefs
@@ -609,10 +653,13 @@ if ($formdata = $mform2->is_cancelled()) {
 
                 $upt->track('status', $struserupdated);
                 $usersupdated++;
-                // pre-process custom profile menu fields data from csv file
-                $existinguser = uu_pre_process_custom_profile_data($existinguser);
-                // save custom profile fields data from csv file
-                profile_save_data($existinguser);
+
+                if (!$remoteuser) {
+                    // pre-process custom profile menu fields data from csv file
+                    $existinguser = uu_pre_process_custom_profile_data($existinguser);
+                    // save custom profile fields data from csv file
+                    profile_save_data($existinguser);
+                }
 
                 events_trigger('user_updated', $existinguser);
 
index 74bb55e..13e5992 100644 (file)
@@ -30,6 +30,7 @@ $string['csvdelimiter'] = 'CSV delimiter';
 $string['defaultvalues'] = 'Default values';
 $string['deleteerrors'] = 'Delete errors';
 $string['encoding'] = 'Encoding';
+$string['errormnetadd'] = 'Can not add remote users';
 $string['errors'] = 'Errors';
 $string['nochanges'] = 'No changes';
 $string['pluginname'] = 'User upload';
index 25e0749..8645f1c 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012112900; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2012120700; // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2012112900; // Requires this Moodle version
 $plugin->component = 'tool_uploaduser'; // Full name of the plugin (used for diagnostics)
 
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 6f6bc92..a52db6c 100644 (file)
         $$column = "<a href=\"user.php?sort=$column&amp;dir=$columndir\">".$string[$column]."</a>$columnicon";
     }
 
-    if ($sort == "name") {
-        $sort = "firstname";
+    $override = new stdClass();
+    $override->firstname = 'firstname';
+    $override->lastname = 'lastname';
+    $fullnamelanguage = get_string('fullnamedisplay', '', $override);
+    if (($CFG->fullnamedisplay == 'firstname lastname') or
+        ($CFG->fullnamedisplay == 'firstname') or
+        ($CFG->fullnamedisplay == 'language' and $fullnamelanguage == 'firstname lastname' )) {
+        $fullnamedisplay = "$firstname / $lastname";
+        if ($sort == "name") { // If sort has already been set to something else then ignore.
+            $sort = "firstname";
+        }
+    } else { // ($CFG->fullnamedisplay == 'language' and $fullnamelanguage == 'lastname firstname').
+        $fullnamedisplay = "$lastname / $firstname";
+        if ($sort == "name") { // This should give the desired sorting based on fullnamedisplay.
+            $sort = "lastname";
+        }
     }
 
     list($extrasql, $params) = $ufiltering->get_sql_filter();
             $users = $nusers;
         }
 
-        $override = new stdClass();
-        $override->firstname = 'firstname';
-        $override->lastname = 'lastname';
-        $fullnamelanguage = get_string('fullnamedisplay', '', $override);
-        if (($CFG->fullnamedisplay == 'firstname lastname') or
-            ($CFG->fullnamedisplay == 'firstname') or
-            ($CFG->fullnamedisplay == 'language' and $fullnamelanguage == 'firstname lastname' )) {
-            $fullnamedisplay = "$firstname / $lastname";
-        } else { // ($CFG->fullnamedisplay == 'language' and $fullnamelanguage == 'lastname firstname')
-            $fullnamedisplay = "$lastname / $firstname";
-        }
-
         $table = new html_table();
         $table->head = array ();
         $table->colclasses = array();
index 289dcc4..745e9e1 100644 (file)
@@ -163,6 +163,7 @@ class external_service_functions_form extends moodleform {
 
         $mform->addElement('searchableselector', 'fids', get_string('name'),
                 $functions, array('multiple'));
+        $mform->addRule('fids', get_string('required'), 'required', null, 'client');
 
         $mform->addElement('hidden', 'id');
         $mform->setType('id', PARAM_INT);
index e6bae3f..975c0e5 100644 (file)
@@ -339,7 +339,7 @@ class phpCAS {
         *\r
         * @return a newly created CASClient object\r
         */\r
-       function client($server_version, $server_hostname, $server_port, $server_uri, $start_session = true) {\r
+       public static function client($server_version, $server_hostname, $server_port, $server_uri, $start_session = true) {\r
                global $PHPCAS_CLIENT, $PHPCAS_INIT_CALL;\r
 \r
                phpCAS :: traceBegin();\r
@@ -388,7 +388,7 @@ class phpCAS {
         *\r
         * @return a newly created CASClient object\r
         */\r
-       function proxy($server_version, $server_hostname, $server_port, $server_uri, $start_session = true) {\r
+       public static function proxy($server_version, $server_hostname, $server_port, $server_uri, $start_session = true) {\r
                global $PHPCAS_CLIENT, $PHPCAS_INIT_CALL;\r
 \r
                phpCAS :: traceBegin();\r
@@ -438,7 +438,7 @@ class phpCAS {
         *\r
         * @param $filename the name of the file used for logging, or FALSE to stop debugging.\r
         */\r
-       function setDebug($filename = '') {\r
+       public static function setDebug($filename = '') {\r
                global $PHPCAS_DEBUG;\r
 \r
                if ($filename != FALSE && gettype($filename) != 'string') {\r
@@ -480,7 +480,7 @@ class phpCAS {
         * This method is a wrapper for debug_backtrace() that is not available \r
         * in all PHP versions (>= 4.3.0 only)\r
         */\r
-       function backtrace() {\r
+       public static function backtrace() {\r
                if (function_exists('debug_backtrace')) {\r
                        return debug_backtrace();\r
                } else {\r
@@ -496,7 +496,7 @@ class phpCAS {
         *\r
         * @private\r
         */\r
-       function log($str) {\r
+       public static function log($str) {\r
                $indent_str = ".";\r
                global $PHPCAS_DEBUG;\r
 \r
@@ -517,7 +517,7 @@ class phpCAS {
         *\r
         * @private\r
         */\r
-       function error($msg) {\r
+       public static function error($msg) {\r
                $dbg = phpCAS :: backtrace();\r
                $function = '?';\r
                $file = '?';\r
@@ -542,7 +542,7 @@ class phpCAS {
        /**\r
         * This method is used to log something in debug mode.\r
         */\r
-       function trace($str) {\r
+       public static function trace($str) {\r
                $dbg = phpCAS :: backtrace();\r
                phpCAS :: log($str . ' [' . basename($dbg[1]['file']) . ':' . $dbg[1]['line'] . ']');\r
        }\r
@@ -550,7 +550,7 @@ class phpCAS {
        /**\r
         * This method is used to indicate the start of the execution of a function in debug mode.\r
         */\r
-       function traceBegin() {\r
+       public static function traceBegin() {\r
                global $PHPCAS_DEBUG;\r
 \r
                $dbg = phpCAS :: backtrace();\r
@@ -577,7 +577,7 @@ class phpCAS {
         *\r
         * @param $res the result of the function\r
         */\r
-       function traceEnd($res = '') {\r
+       public static function traceEnd($res = '') {\r
                global $PHPCAS_DEBUG;\r
 \r
                $PHPCAS_DEBUG['indent']--;\r
@@ -590,7 +590,7 @@ class phpCAS {
        /**\r
         * This method is used to indicate the end of the execution of the program\r
         */\r
-       function traceExit() {\r
+       public static function traceExit() {\r
                global $PHPCAS_DEBUG;\r
 \r
                phpCAS :: log('exit()');\r
@@ -617,7 +617,7 @@ class phpCAS {
         *\r
         * @sa PHPCAS_LANG_FRENCH, PHPCAS_LANG_ENGLISH\r
         */\r
-       function setLang($lang) {\r
+       public static function setLang($lang) {\r
                global $PHPCAS_CLIENT;\r
                if (!is_object($PHPCAS_CLIENT)) {\r
                        phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');\r
@@ -642,7 +642,7 @@ class phpCAS {
         *\r
         * @return the phpCAS version.\r
         */\r
-       function getVersion() {\r
+       public static function getVersion() {\r
                return PHPCAS_VERSION;\r
        }\r
 \r
@@ -660,7 +660,7 @@ class phpCAS {
         *\r
         * @param $header the HTML header.\r
         */\r
-       function setHTMLHeader($header) {\r
+       public static function setHTMLHeader($header) {\r
                global $PHPCAS_CLIENT;\r
                if (!is_object($PHPCAS_CLIENT)) {\r
                        phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');\r
@@ -676,7 +676,7 @@ class phpCAS {
         *\r
         * @param $footer the HTML footer.\r
         */\r
-       function setHTMLFooter($footer) {\r
+       public static function setHTMLFooter($footer) {\r
                global $PHPCAS_CLIENT;\r
                if (!is_object($PHPCAS_CLIENT)) {\r
                        phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');\r
@@ -703,7 +703,7 @@ class phpCAS {
         * @param $format the format used to store the PGT's (`plain' and `xml' allowed)\r
         * @param $path the path where the PGT's should be stored\r
         */\r
-       function setPGTStorageFile($format = '', $path = '') {\r
+       public static function setPGTStorageFile($format = '', $path = '') {\r
                global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;\r
 \r
                phpCAS :: traceBegin();\r
@@ -749,7 +749,7 @@ class phpCAS {
         * @return TRUE on success, FALSE otherwise (in this later case, $err_code\r
         * gives the reason why it failed and $output contains an error message).\r
         */\r
-       function serviceWeb($url, & $err_code, & $output) {\r
+       public static function serviceWeb($url, & $err_code, & $output) {\r
                global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;\r
 \r
                phpCAS :: traceBegin();\r
@@ -792,7 +792,7 @@ class phpCAS {
         * @return an IMAP stream on success, FALSE otherwise (in this later case, $err_code\r
         * gives the reason why it failed and $err_msg contains an error message).\r
         */\r
-       function serviceMail($url, $service, $flags, & $err_code, & $err_msg, & $pt) {\r
+       public static function serviceMail($url, $service, $flags, & $err_code, & $err_msg, & $pt) {\r
                global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;\r
 \r
                phpCAS :: traceBegin();\r
@@ -839,7 +839,7 @@ class phpCAS {
         *\r
         * @param $n an integer.\r
         */\r
-       function setCacheTimesForAuthRecheck($n) {\r
+       public static function setCacheTimesForAuthRecheck($n) {\r
                global $PHPCAS_CLIENT;\r
                if (!is_object($PHPCAS_CLIENT)) {\r
                        phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');\r
@@ -854,7 +854,7 @@ class phpCAS {
         * This method is called to check if the user is authenticated (use the gateway feature).\r
         * @return TRUE when the user is authenticated; otherwise FALSE.\r
         */\r
-       function checkAuthentication() {\r
+       public static function checkAuthentication() {\r
                global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;\r
 \r
                phpCAS :: traceBegin();\r
@@ -882,7 +882,7 @@ class phpCAS {
         * authenticated. If the user is not authenticated, halt by redirecting to \r
         * the CAS server.\r
         */\r
-       function forceAuthentication() {\r
+       public static function forceAuthentication() {\r
                global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;\r
 \r
                phpCAS :: traceBegin();\r
@@ -916,7 +916,7 @@ class phpCAS {
        /**\r
         * This method is called to renew the authentication.\r
         **/\r
-       function renewAuthentication() {\r
+       public static function renewAuthentication() {\r
                global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;\r
 \r
                phpCAS :: traceBegin();\r
@@ -941,7 +941,7 @@ class phpCAS {
        /**\r
         * This method has been left from version 0.4.1 for compatibility reasons.\r
         */\r
-       function authenticate() {\r
+       public static function authenticate() {\r
                phpCAS :: error('this method is deprecated. You should use ' . __CLASS__ . '::forceAuthentication() instead');\r
        }\r
 \r
@@ -951,7 +951,7 @@ class phpCAS {
         *\r
         * @return TRUE when the user is authenticated.\r
         */\r
-       function isAuthenticated() {\r
+       public static function isAuthenticated() {\r
                global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;\r
 \r
                phpCAS :: traceBegin();\r
@@ -981,7 +981,7 @@ class phpCAS {
         * @return true if authenticated, false otherwise.\r
         * @since 0.4.22 by Brendan Arnold\r
         */\r
-       function isSessionAuthenticated() {\r
+       public static function isSessionAuthenticated() {\r
                global $PHPCAS_CLIENT;\r
                if (!is_object($PHPCAS_CLIENT)) {\r
                        phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');\r
@@ -996,7 +996,7 @@ class phpCAS {
         *\r
         * @return the login name of the authenticated user\r
         */\r
-       function getUser() {\r
+       public static function getUser() {\r
                global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;\r
                if (!is_object($PHPCAS_CLIENT)) {\r
                        phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');\r
@@ -1017,7 +1017,7 @@ class phpCAS {
         *\r
         * @return the login name of the authenticated user\r
         */\r
-       function getAttributes() {\r
+       public static function getAttributes() {\r
                global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL;\r
                if (!is_object($PHPCAS_CLIENT)) {\r
                        phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');\r
@@ -1033,7 +1033,7 @@ class phpCAS {
        /**\r
         * Handle logout requests.\r
         */\r
-       function handleLogoutRequests($check_client = true, $allowed_clients = false) {\r
+       public static function handleLogoutRequests($check_client = true, $allowed_clients = false) {\r
                global $PHPCAS_CLIENT;\r
                if (!is_object($PHPCAS_CLIENT)) {\r
                        phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');\r
@@ -1047,7 +1047,7 @@ class phpCAS {
         *\r
         * @return the login name of the authenticated user\r
         */\r
-       function getServerLoginURL() {\r
+       public static function getServerLoginURL() {\r
                global $PHPCAS_CLIENT;\r
                if (!is_object($PHPCAS_CLIENT)) {\r
                        phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');\r
@@ -1060,7 +1060,7 @@ class phpCAS {
         * @param $url the login URL\r
         * @since 0.4.21 by Wyman Chan\r
         */\r
-       function setServerLoginURL($url = '') {\r
+       public static function setServerLoginURL($url = '') {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1081,7 +1081,7 @@ class phpCAS {
         * @param $url the serviceValidate URL\r
         * @since 1.1.0 by Joachim Fritschi\r
         */\r
-       function setServerServiceValidateURL($url = '') {\r
+       public static function setServerServiceValidateURL($url = '') {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1102,7 +1102,7 @@ class phpCAS {
         * @param $url the proxyValidate URL\r
         * @since 1.1.0 by Joachim Fritschi\r
         */\r
-       function setServerProxyValidateURL($url = '') {\r
+       public static function setServerProxyValidateURL($url = '') {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1122,7 +1122,7 @@ class phpCAS {
         * @param $url the samlValidate URL\r
         * @since 1.1.0 by Joachim Fritschi\r
         */\r
-       function setServerSamlValidateURL($url = '') {\r
+       public static function setServerSamlValidateURL($url = '') {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1143,7 +1143,7 @@ class phpCAS {
         *\r
         * @return the login name of the authenticated user\r
         */\r
-       function getServerLogoutURL() {\r
+       public static function getServerLogoutURL() {\r
                global $PHPCAS_CLIENT;\r
                if (!is_object($PHPCAS_CLIENT)) {\r
                        phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');\r
@@ -1156,7 +1156,7 @@ class phpCAS {
         * @param $url the logout URL\r
         * @since 0.4.21 by Wyman Chan\r
         */\r
-       function setServerLogoutURL($url = '') {\r
+       public static function setServerLogoutURL($url = '') {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1176,7 +1176,7 @@ class phpCAS {
         * @params $params an array that contains the optional url and service parameters that will be passed to the CAS server\r
         * @public\r
         */\r
-       function logout($params = "") {\r
+       public static function logout($params = "") {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1206,7 +1206,7 @@ class phpCAS {
         * This method is used to logout from CAS. Halts by redirecting to the CAS server.\r
         * @param $service a URL that will be transmitted to the CAS server\r
         */\r
-       function logoutWithRedirectService($service) {\r
+       public static function logoutWithRedirectService($service) {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1226,7 +1226,7 @@ class phpCAS {
         * This method is used to logout from CAS. Halts by redirecting to the CAS server.\r
         * @param $url a URL that will be transmitted to the CAS server\r
         */\r
-       function logoutWithUrl($url) {\r
+       public static function logoutWithUrl($url) {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1247,7 +1247,7 @@ class phpCAS {
         * @param $service a URL that will be transmitted to the CAS server\r
         * @param $url a URL that will be transmitted to the CAS server\r
         */\r
-       function logoutWithRedirectServiceAndUrl($service, $url) {\r
+       public static function logoutWithRedirectServiceAndUrl($service, $url) {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1273,7 +1273,7 @@ class phpCAS {
         *\r
         * @param $url the URL\r
         */\r
-       function setFixedCallbackURL($url = '') {\r
+       public static function setFixedCallbackURL($url = '') {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1295,7 +1295,7 @@ class phpCAS {
         *\r
         * @param $url the URL\r
         */\r
-       function setFixedServiceURL($url) {\r
+       public static function setFixedServiceURL($url) {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1311,7 +1311,7 @@ class phpCAS {
        /**\r
         * Get the URL that is set as the CAS service parameter.\r
         */\r
-       function getServiceURL() {\r
+       public static function getServiceURL() {\r
                global $PHPCAS_CLIENT;\r
                if (!is_object($PHPCAS_CLIENT)) {\r
                        phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');\r
@@ -1322,7 +1322,7 @@ class phpCAS {
        /**\r
         * Retrieve a Proxy Ticket from the CAS server.\r
         */\r
-       function retrievePT($target_service, & $err_code, & $err_msg) {\r
+       public static function retrievePT($target_service, & $err_code, & $err_msg) {\r
                global $PHPCAS_CLIENT;\r
                if (!is_object($PHPCAS_CLIENT)) {\r
                        phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');\r
@@ -1338,7 +1338,7 @@ class phpCAS {
         *\r
         * @param $cert the PEM certificate\r
         */\r
-       function setCasServerCert($cert) {\r
+       public static function setCasServerCert($cert) {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1356,7 +1356,7 @@ class phpCAS {
         *\r
         * @param $cert the CA certificate\r
         */\r
-       function setCasServerCACert($cert) {\r
+       public static function setCasServerCACert($cert) {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1372,7 +1372,7 @@ class phpCAS {
        /**\r
         * Set no SSL validation for the CAS server.\r
         */\r
-       function setNoCasServerValidation() {\r
+       public static function setNoCasServerValidation() {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
@@ -1390,7 +1390,7 @@ class phpCAS {
         * @param $key the option key\r
         * @param $value the value to set\r
         */\r
-       function setExtraCurlOption($key, $value) {\r
+       public static function setExtraCurlOption($key, $value) {\r
                global $PHPCAS_CLIENT;\r
                phpCAS :: traceBegin();\r
                if (!is_object($PHPCAS_CLIENT)) {\r
index c0bcd50..ccd1021 100644 (file)
@@ -104,12 +104,14 @@ class auth_plugin_cas extends auth_plugin_ldap {
         $this->connectCAS();
 
         if (phpCAS::checkAuthentication()) {
+            $frm = new stdClass();
             $frm->username = phpCAS::getUser();
             $frm->password = 'passwdCas';
             return;
         }
 
         if (isset($_GET['loginguest']) && ($_GET['loginguest'] == true)) {
+            $frm = new stdClass();
             $frm->username = 'guest';
             $frm->password = 'guest';
             return;
index 2e395ce..abe2808 100644 (file)
@@ -42,13 +42,11 @@ class block_calendar_month extends block_base {
         list($courses, $group, $user) = calendar_set_filters($filtercourse);
         if ($issite) {
             // For the front page
-            $this->content->text .= calendar_top_controls('frontpage', array('id' => $courseid, 'm' => $cal_m, 'y' => $cal_y));
-            $this->content->text .= calendar_get_mini($courses, $group, $user, $cal_m, $cal_y);
+            $this->content->text .= calendar_get_mini($courses, $group, $user, $cal_m, $cal_y, 'frontpage', $courseid);
             // No filters for now
         } else {
             // For any other course
-            $this->content->text .= calendar_top_controls('course', array('id' => $courseid, 'm' => $cal_m, 'y' => $cal_y));
-            $this->content->text .= calendar_get_mini($courses, $group, $user, $cal_m, $cal_y);
+            $this->content->text .= calendar_get_mini($courses, $group, $user, $cal_m, $cal_y, 'course', $courseid);
             $this->content->text .= '<h3 class="eventskey">'.get_string('eventskey', 'calendar').'</h3>';
             $this->content->text .= '<div class="filters calendar_filters">'.calendar_filter_controls($this->page->url).'</div>';
         }
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 304b3da..67fa66d 100644 (file)
@@ -76,7 +76,7 @@ class blog_entry implements renderable {
      * @param mixed $idorparams A blog entry id if INT, or data for a new entry if array
      */
     public function __construct($id=null, $params=null, $form=null) {
-        global $DB, $PAGE;
+        global $DB, $PAGE, $CFG;
 
         if (!empty($id)) {
             $object = $DB->get_record('post', array('id' => $id));
@@ -89,6 +89,18 @@ class blog_entry implements renderable {
             }
         }
 
+        if (!empty($CFG->useblogassociations)) {
+            $associations = $DB->get_records('blog_association', array('blogid' => $this->id));
+            foreach ($associations as $association) {
+                $context = context::instance_by_id($association->contextid);
+                if ($context->contextlevel == CONTEXT_COURSE) {
+                    $this->courseassoc = $association->contextid;
+                } else if ($context->contextlevel == CONTEXT_MODULE) {
+                    $this->modassoc = $association->contextid;
+                }
+            }
+        }
+
         $this->form = $form;
     }
 
@@ -104,44 +116,44 @@ class blog_entry implements renderable {
 
         $this->renderable->user = $DB->get_record('user', array('id'=>$this->userid));
 
-        // Entry comments.\r
-        if (!empty($CFG->usecomments) and $CFG->blogusecomments) {\r
+        // Entry comments.
+        if (!empty($CFG->usecomments) and $CFG->blogusecomments) {
             require_once($CFG->dirroot . '/comment/lib.php');
-\r
-            $cmt = new stdClass();\r
-            $cmt->context = context_user::instance($this->userid);\r
-            $cmt->courseid = $PAGE->course->id;\r
-            $cmt->area = 'format_blog';\r
-            $cmt->itemid = $this->id;\r
-            $cmt->showcount = $CFG->blogshowcommentscount;\r
-            $cmt->component = 'blog';\r
-            $this->renderable->comment = new comment($cmt);\r
+
+            $cmt = new stdClass();
+            $cmt->context = context_user::instance($this->userid);
+            $cmt->courseid = $PAGE->course->id;
+            $cmt->area = 'format_blog';
+            $cmt->itemid = $this->id;
+            $cmt->showcount = $CFG->blogshowcommentscount;
+            $cmt->component = 'blog';
+            $this->renderable->comment = new comment($cmt);
         }
-\r
-        $this->summary = file_rewrite_pluginfile_urls($this->summary, 'pluginfile.php', SYSCONTEXTID, 'blog', 'post', $this->id);\r
 
-        // External blog link.\r
-        if ($this->uniquehash && $this->content) {\r
-            if ($externalblog = $DB->get_record('blog_external', array('id' => $this->content))) {\r
-                $urlparts = parse_url($externalblog->url);\r
+        $this->summary = file_rewrite_pluginfile_urls($this->summary, 'pluginfile.php', SYSCONTEXTID, 'blog', 'post', $this->id);
+
+        // External blog link.
+        if ($this->uniquehash && $this->content) {
+            if ($externalblog = $DB->get_record('blog_external', array('id' => $this->content))) {
+                $urlparts = parse_url($externalblog->url);
                 $this->renderable->externalblogtext = get_string('retrievedfrom', 'blog') . get_string('labelsep', 'langconfig');
-                $this->renderable->externalblogtext .= html_writer::link($urlparts['scheme'] . '://'.$urlparts['host'], $externalblog->name);\r
-            }\r
+                $this->renderable->externalblogtext .= html_writer::link($urlparts['scheme'] . '://'.$urlparts['host'], $externalblog->name);
+            }
         }
 
         // Retrieve associations
         $this->renderable->unassociatedentry = false;
         if (!empty($CFG->useblogassociations)) {
-\r
+
             // Adding the entry associations data.
             if ($associations = $associations = $DB->get_records('blog_association', array('blogid' => $this->id))) {
-\r
-                // Check to see if the entry is unassociated with group/course level access.\r
-                if ($this->publishstate == 'group' || $this->publishstate == 'course') {\r
-                    $this->renderable->unassociatedentry = true;\r
+
+                // Check to see if the entry is unassociated with group/course level access.
+                if ($this->publishstate == 'group' || $this->publishstate == 'course') {
+                    $this->renderable->unassociatedentry = true;
                 }
-\r
-                foreach ($associations as $key => $assocrec) {\r
+
+                foreach ($associations as $key => $assocrec) {
 
                     if (!$context = context::instance_by_id($assocrec->contextid, IGNORE_MISSING)) {
                         unset($associations[$key]);
@@ -150,14 +162,14 @@ class blog_entry implements renderable {
 
                     // The renderer will need the contextlevel of the association.
                     $associations[$key]->contextlevel = $context->contextlevel;
-\r
-                    // Course associations.\r
+
+                    // Course associations.
                     if ($context->contextlevel ==  CONTEXT_COURSE) {
                         $instancename = $DB->get_field('course', 'shortname', array('id' => $context->instanceid)); //TODO: performance!!!!
 
                         $associations[$key]->url = $assocurl = new moodle_url('/course/view.php', array('id' => $context->instanceid));
                         $associations[$key]->text = $instancename;
-                        $associations[$key]->icon = new pix_icon('i/course', $associations[$key]->text);\r
+                        $associations[$key]->icon = new pix_icon('i/course', $associations[$key]->text);
                     }
 
                     // Mod associations.
@@ -174,11 +186,11 @@ class blog_entry implements renderable {
                         $associations[$key]->url = new moodle_url('/mod/' . $modinfo->name . '/view.php', array('id' => $context->instanceid));
                         $associations[$key]->text = $instancename;
                         $associations[$key]->icon = new pix_icon('icon', $associations[$key]->text, $modinfo->name);
-                    }\r
+                    }
                 }
             }
-            $this->renderable->blogassociations = $associations;\r
-        }\r
+            $this->renderable->blogassociations = $associations;
+        }
 
         // Entry attachments.
         $this->renderable->attachments = $this->get_attachments();
@@ -193,23 +205,23 @@ class blog_entry implements renderable {
      */
     function get_attachments() {
 
-        global $CFG;\r
-\r
-        require_once($CFG->libdir.'/filelib.php');\r
-\r
+        global $CFG;
+
+        require_once($CFG->libdir.'/filelib.php');
+
         $syscontext = context_system::instance();
-\r
+
         $fs = get_file_storage();
-        $files = $fs->get_area_files($syscontext->id, 'blog', 'attachment', $this->id);\r
+        $files = $fs->get_area_files($syscontext->id, 'blog', 'attachment', $this->id);
 
         // Adding a blog_entry_attachment for each non-directory file.
-        $attachments = array();\r
-        foreach ($files as $file) {\r
-            if ($file->is_directory()) {\r
-                continue;\r
+        $attachments = array();
+        foreach ($files as $file) {
+            if ($file->is_directory()) {
+                continue;
             }
-            $attachments[] = new blog_entry_attachment($file, $this->id);\r
-        }\r
+            $attachments[] = new blog_entry_attachment($file, $this->id);
+        }
 
         return $attachments;
     }
@@ -251,6 +263,7 @@ class blog_entry implements renderable {
         }
 
         tag_set('post', $this->id, $this->tags);
+        events_trigger('blog_entry_added', $this);
     }
 
     /**
@@ -284,6 +297,7 @@ class blog_entry implements renderable {
         tag_set('post', $entry->id, $entry->tags);
 
         add_to_log(SITEID, 'blog', 'update', 'index.php?userid='.$USER->id.'&entryid='.$entry->id, $entry->subject);
+        events_trigger('blog_entry_edited', $entry);
     }
 
     /**
@@ -301,6 +315,7 @@ class blog_entry implements renderable {
         tag_set('post', $this->id, array());
 
         add_to_log(SITEID, 'blog', 'delete', 'index.php?userid='. $this->userid, 'deleted blog entry with entry id# '. $this->id);
+        events_trigger('blog_entry_deleted', $this);
     }
 
     /**
@@ -636,7 +651,7 @@ class blog_listing {
         global $CFG, $USER, $DB, $OUTPUT, $PAGE;
         $sitecontext = context_system::instance();
 
-        // Blog renderer\r
+        // Blog renderer
         $output = $PAGE->get_renderer('blog');
 
         $page  = optional_param('blogpage', 0, PARAM_INT);
@@ -1030,8 +1045,8 @@ class blog_entry_attachment implements renderable {
         global $CFG;
 
         $this->file = $file;
-        $this->filename = $file->get_filename();\r
-        $this->url = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.SYSCONTEXTID.'/blog/attachment/'.$entryid.'/'.$this->filename);\r
+        $this->filename = $file->get_filename();
+        $this->url = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.SYSCONTEXTID.'/blog/attachment/'.$entryid.'/'.$this->filename);
     }
 
 }
index dc56f91..03fb441 100644 (file)
@@ -119,6 +119,27 @@ function calendar_get_days() {
     return array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
 }
 
+/**
+ * Get the subscription from a given id
+ *
+ * @since Moodle 2.5
+ * @param int $id id of the subscription
+ * @return stdClass Subscription record from DB
+ * @throws moodle_exception for an invalid id
+ */
+function calendar_get_subscription($id) {
+    global $DB;
+
+    $cache = cache::make('core', 'calendar_subscriptions');
+    $subscription = $cache->get($id);
+    if (empty($subscription)) {
+        $subscription = $DB->get_record('event_subscriptions', array('id' => $id), '*', MUST_EXIST);
+        // cache the data.
+        $cache->set($id, $subscription);
+    }
+    return $subscription;
+}
+
 /**
  * Gets the first day of the week
  *
@@ -145,14 +166,16 @@ function calendar_get_starting_weekday() {
 /**
  * Generates the HTML for a miniature calendar
  *
- * @param array $courses list of course
+ * @param array $courses list of course to list events from
  * @param array $groups list of group
  * @param array $users user's info
  * @param int $cal_month calendar month in numeric, default is set to false
  * @param int $cal_year calendar month in numeric, default is set to false
+ * @param string $placement the place/page the calendar is set to appear - passed on the the controls function
+ * @param int $courseid id of the course the calendar is displayed on - passed on the the controls function
  * @return string $content return html table for mini calendar
  */
-function calendar_get_mini($courses, $groups, $users, $cal_month = false, $cal_year = false) {
+function calendar_get_mini($courses, $groups, $users, $cal_month = false, $cal_year = false, $placement = false, $courseid = false ) {
     global $CFG, $USER, $OUTPUT;
 
     $display = new stdClass;
@@ -259,6 +282,9 @@ function calendar_get_mini($courses, $groups, $users, $cal_month = false, $cal_y
 
     $summary = get_string('calendarheading', 'calendar', userdate(make_timestamp($y, $m), get_string('strftimemonthyear')));
     $content .= '<table class="minicalendar calendartable" summary="'.$summary.'">'; // Begin table
+    if (($placement !== false) && ($courseid  !== false)) {
+        $content .= '<caption>'. calendar_top_controls($placement, array('id' => $courseid, 'm' => $m, 'y' => $y)) .'</caption>';
+    }
     $content .= '<tr class="weekdays">'; // Header row: day names
 
     // Print out the names of the weekdays
@@ -314,7 +340,7 @@ function calendar_get_mini($courses, $groups, $users, $cal_month = false, $cal_y
                 if (!isset($events[$eventid])) {
                     continue;
                 }
-                $event = $events[$eventid];
+                $event = new calendar_event($events[$eventid]);
                 $popupalt  = '';
                 $component = 'moodle';
                 if(!empty($event->modulename)) {
@@ -335,7 +361,15 @@ function calendar_get_mini($courses, $groups, $users, $cal_month = false, $cal_y
 
                 $popupcontent .= html_writer::start_tag('div');
                 $popupcontent .= $OUTPUT->pix_icon($popupicon, $popupalt, $component);
-                $popupcontent .= html_writer::link($dayhref, format_string($event->name, true));
+                $name = format_string($event->name, true);
+                // Show ical source if needed.
+                if (!empty($event->subscription) && $CFG->calendar_showicalsource) {
+                    $a = new stdClass();
+                    $a->name = $name;
+                    $a->source = $event->subscription->name;
+                    $name = get_string('namewithsource', 'calendar', $a);
+                }
+                $popupcontent .= html_writer::link($dayhref, $name);
                 $popupcontent .= html_writer::end_tag('div');
             }
 
@@ -1910,6 +1944,10 @@ class calendar_event {
             $data->id = null;
         }
 
+        if (!empty($data->subscriptionid)) {
+            $data->subscription = calendar_get_subscription($data->subscriptionid);
+        }
+
         // Default to a user event
         if (empty($data->eventtype)) {
             $data->eventtype = 'user';
@@ -2743,12 +2781,18 @@ function calendar_add_subscription($sub) {
         $sub->pollinterval = 0;
     }
 
+    $cache = cache::make('core', 'calendar_subscriptions');
+
     if (!empty($sub->name)) {
         if (empty($sub->id)) {
             $id = $DB->insert_record('event_subscriptions', $sub);
+            // we cannot cache the data here because $sub is not complete.
             return $id;
         } else {
+            // Why are we doing an update here?
             $DB->update_record('event_subscriptions', $sub);
+            // update cache.
+            $cache->set($sub->id, $sub);
             return $sub->id;
         }
     } else {
@@ -2762,9 +2806,10 @@ function calendar_add_subscription($sub) {
  * @param object $event The RFC-2445 iCalendar event
  * @param int $courseid The course ID
  * @param int $subscriptionid The iCalendar subscription ID
+ * @throws dml_exception A DML specific exception is thrown for invalid subscriptionids.
  * @return int Code: 1=updated, 2=inserted, 0=error
  */
-function calendar_add_icalendar_event($event, $courseid, $subscriptionid = null) {
+function calendar_add_icalendar_event($event, $courseid, $subscriptionid) {
     global $DB, $USER;
 
     // Probably an unsupported X-MICROSOFT-CDO-BUSYSTATUS event.
@@ -2805,16 +2850,13 @@ function calendar_add_icalendar_event($event, $courseid, $subscriptionid = null)
     $eventrecord->timemodified = time();
 
     // Add the iCal subscription details if required.
-    if ($sub = $DB->get_record('event_subscriptions', array('id' => $subscriptionid))) {
-        $eventrecord->subscriptionid = $subscriptionid;
-        $eventrecord->userid = $sub->userid;
-        $eventrecord->groupid = $sub->groupid;
-        $eventrecord->courseid = $sub->courseid;
-        $eventrecord->eventtype = $sub->eventtype;
-    } else {
-        // We should never do anything with an event without a subscription reference.
-        return 0;
-    }
+    // We should never do anything with an event without a subscription reference.
+    $sub = calendar_get_subscription($subscriptionid);
+    $eventrecord->subscriptionid = $subscriptionid;
+    $eventrecord->userid = $sub->userid;
+    $eventrecord->groupid = $sub->groupid;
+    $eventrecord->courseid = $sub->courseid;
+    $eventrecord->eventtype = $sub->eventtype;
 
     if ($updaterecord = $DB->get_record('event', array('uuid' => $eventrecord->uuid))) {
         $eventrecord->id = $updaterecord->id;
@@ -2838,20 +2880,18 @@ function calendar_add_icalendar_event($event, $courseid, $subscriptionid = null)
  * @param int $subscriptionid The ID of the subscription we are acting upon.
  * @param int $pollinterval The poll interval to use.
  * @param int $action The action to be performed. One of update or remove.
+ * @throws dml_exception if invalid subscriptionid is provided
  * @return string A log of the import progress, including errors
  */
 function calendar_process_subscription_row($subscriptionid, $pollinterval, $action) {
     global $DB;
 
-    if (empty($subscriptionid)) {
-        return '';
-    }
-
     // Fetch the subscription from the database making sure it exists.
-    $sub = $DB->get_record('event_subscriptions', array('id' => $subscriptionid), '*', MUST_EXIST);
+    $sub = calendar_get_subscription($subscriptionid);
 
     $strupdate = get_string('update');
     $strremove = get_string('remove');
+
     // Update or remove the subscription, based on action.
     switch ($action) {
         case $strupdate:
@@ -2862,12 +2902,15 @@ function calendar_process_subscription_row($subscriptionid, $pollinterval, $acti
             $sub->pollinterval = $pollinterval;
             $DB->update_record('event_subscriptions', $sub);
 
+            // update the cache.
+            $cache = cache::make('core', 'calendar_subscriptions');
+            $cache->set($sub->id, $sub);
+
             // Update the events.
             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 +2920,22 @@ 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));
+    cache_helper::invalidate_by_definition('core', 'calendar_subscriptions', array(), array($subscription));
+}
 /**
  * From a URL, fetch the calendar and return an iCalendar object.
  *
@@ -2962,10 +3021,7 @@ function calendar_import_icalendar_events($ical, $courseid, $subscriptionid = nu
 function calendar_update_subscription_events($subscriptionid) {
     global $DB;
 
-    $sub = $DB->get_record('event_subscriptions', array('id' => $subscriptionid));
-    if (empty($sub)) {
-        print_error('errorbadsubscription', 'calendar');
-    }
+    $sub = calendar_get_subscription($subscriptionid);
     // Don't update a file subscription. TODO: Update from a new uploaded file.
     if (empty($sub->url)) {
         return 'File subscription not updated.';
@@ -2974,6 +3030,9 @@ function calendar_update_subscription_events($subscriptionid) {
     $return = calendar_import_icalendar_events($ical, $sub->courseid, $subscriptionid);
     $sub->lastupdated = time();
     $DB->update_record('event_subscriptions', $sub);
+    // Update the cache.
+    $cache = cache::make('core', 'calendar_subscriptions');
+    $cache->set($subscriptionid, $sub);
     return $return;
 }
 
@@ -2991,7 +3050,7 @@ function calendar_can_edit_subscription($subscriptionorid) {
     } else if (is_object($subscriptionorid)) {
         $subscription = $subscriptionorid;
     } else {
-        $subscription = $DB->get_record('event_subscriptions', array('id' => $subscriptionorid), '*', MUST_EXIST);
+        $subscription = calendar_get_subscription($subscriptionorid);
     }
     $allowed = new stdClass;
     $courseid = $subscription->courseid;
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 f5e6885..49ca5fe 100644 (file)
@@ -171,16 +171,13 @@ class core_calendar_renderer extends plugin_renderer_base {
         list($nextmon, $nextyr) = calendar_add_month($calendar->month, $calendar->year);
 
         $content  = html_writer::start_tag('div', array('class'=>'minicalendarblock'));
-        $content .= calendar_top_controls('display', array('id' => $calendar->courseid, 'm' => $prevmon, 'y' => $prevyr));
-        $content .= calendar_get_mini($calendar->courses, $calendar->groups, $calendar->users, $prevmon, $prevyr);
+        $content .= calendar_get_mini( $calendar->courses, $calendar->groups, $calendar->users, $prevmon, $prevyr, 'display', $calendar->courseid);
         $content .= html_writer::end_tag('div');
         $content .= html_writer::start_tag('div', array('class'=>'minicalendarblock'));
-        $content .= calendar_top_controls('display', array('id' => $calendar->courseid, 'm' => $calendar->month, 'y' => $calendar->year));
-        $content .= calendar_get_mini($calendar->courses, $calendar->groups, $calendar->users, $calendar->month, $calendar->year);
+        $content .= calendar_get_mini($calendar->courses, $calendar->groups, $calendar->users, $calendar->month, $calendar->year, 'display', $calendar->courseid);
         $content .= html_writer::end_tag('div');
         $content .= html_writer::start_tag('div', array('class'=>'minicalendarblock'));
-        $content .= calendar_top_controls('display', array('id' => $calendar->courseid, 'm' => $nextmon, 'y' => $nextyr));
-        $content .= calendar_get_mini($calendar->courses, $calendar->groups, $calendar->users, $nextmon, $nextyr);
+        $content .= calendar_get_mini($calendar->courses, $calendar->groups, $calendar->users, $nextmon, $nextyr, 'display', $calendar->courseid);
         $content .= html_writer::end_tag('div');
         return $content;
     }
@@ -292,6 +289,8 @@ class core_calendar_renderer extends plugin_renderer_base {
      * @return string
      */
     public function event(calendar_event $event, $showactions=true) {
+        global $CFG;
+
         $event = calendar_add_event_metadata($event);
 
         $anchor  = html_writer::tag('a', '', array('name'=>'event_'.$event->id));
@@ -320,6 +319,16 @@ class core_calendar_renderer extends plugin_renderer_base {
         if (!empty($event->courselink)) {
             $table->data[0]->cells[1]->text .= html_writer::tag('div', $event->courselink, array('class'=>'course'));
         }
+        // Show subscription source if needed.
+        if (!empty($event->subscription) && $CFG->calendar_showicalsource) {
+            if (!empty($event->subscription->url)) {
+                $source = html_writer::link($event->subscription->url, get_string('subsource', 'calendar', $event->subscription));
+            } else {
+                // File based ical.
+                $source = get_string('subsource', 'calendar', $event->subscription);
+            }
+            $table->data[0]->cells[1]->text .= html_writer::tag('div', $source, array('class' => 'subscription'));
+        }
         if (!empty($event->time)) {
             $table->data[0]->cells[1]->text .= html_writer::tag('span', $event->time, array('class'=>'date'));
         } else {
index ff3607c..54262b0 100644 (file)
@@ -1,6 +1,12 @@
 This files describes API changes in /calendar/* ,
 information provided here is intended especially for developers.
 
+=== 2.5 ===
+required changes in code:
+* calendar_add_icalendar_event() now requires a valid subscriptionid
+* calendar_process_subscription_row() throws exception for invalid subscriptionid
+* calendar_update_subscription_events() now throws a dml_exception instead of moodle_exception for bad subscriptions
+* calendar_get_mini() function now has optional $placement and $courseid paramaters.
 
 === 2.4 ===
 
index 37138b5..71ea312 100644 (file)
@@ -144,6 +144,7 @@ if ($editingon && $sesskeyprovided) {
             require_capability('moodle/course:visibility', $coursecontext);
             // Set the visibility of the course. we set the old flag when user manually changes visibility of course.
             $DB->update_record('course', array('id' => $course->id, 'visible' => $visible, 'visibleold' => $visible, 'timemodified' => time()));
+            add_to_log($course->id, "course", ($visible ? 'show' : 'hide'), "edit.php?id=$course->id", $course->id);
         }
     }
 
@@ -172,6 +173,7 @@ if ($editingon && $sesskeyprovided) {
             }
             $DB->set_field('course', 'sortorder', $swapcourse->sortorder, array('id' => $movecourse->id));
             $DB->set_field('course', 'sortorder', $movecourse->sortorder, array('id' => $swapcourse->id));
+            add_to_log($movecourse->id, "course", "move", "edit.php?id=$movecourse->id", $movecourse->id);
         }
     }
 
index ecb519b..73c8a79 100644 (file)
@@ -616,7 +616,7 @@ class dndupload_ajax_processor {
 
         if (!$instanceid) {
             // Something has gone wrong - undo everything we can.
-            delete_course_module($this->cm->id);
+            course_delete_module($this->cm->id);
             throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
         }
 
@@ -639,7 +639,7 @@ class dndupload_ajax_processor {
         $info = get_fast_modinfo($this->course);
         if (!isset($info->cms[$this->cm->id])) {
             // The course module has not been properly created in the course - undo everything.
-            delete_course_module($this->cm->id);
+            course_delete_module($this->cm->id);
             throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
         }
         $mod = $info->get_cm($this->cm->id);
index 22bdd57..91f28ed 100644 (file)
@@ -135,16 +135,8 @@ if ($editform->is_cancelled()) {
         update_course($data, $editoroptions);
     }
 
-    switch ($returnto) {
-        case 'category':
-        case 'topcat': //redirecting to where the new course was created by default.
-            $url = new moodle_url($CFG->wwwroot.'/course/category.php', array('id'=>$categoryid));
-            break;
-        default:
-            $url = new moodle_url($CFG->wwwroot.'/course/view.php', array('id'=>$course->id));
-            break;
-    }
-    redirect($url);
+    // Redirect user to newly created/updated course.
+    redirect(new moodle_url('/course/view.php', array('id' => $course->id)));
 }
 
 
index f6d47e8..fab0f52 100644 (file)
@@ -102,6 +102,7 @@ if ($mform->is_cancelled()) {
         $newcategory->theme = $data->theme;
     }
 
+    $logaction = 'update';
     if ($id) {
         // Update an existing category.
         $newcategory->id = $category->id;
@@ -119,10 +120,12 @@ if ($mform->is_cancelled()) {
         $category = create_course_category($newcategory);
         $newcategory->id = $category->id;
         $categorycontext = $category->context;
+        $logaction = 'add';
     }
 
     $newcategory = file_postupdate_standard_editor($newcategory, 'description', $editoroptions, $categorycontext, 'coursecat', 'description', 0);
     $DB->update_record('course_categories', $newcategory);
+    add_to_log(SITEID, "category", $logaction, "editcategory.php?id=$newcategory->id", $newcategory->id);
     fix_course_sortorder();
 
     redirect('category.php?id='.$newcategory->id.'&categoryedit=on');
index f67ac5b..aa871c4 100644 (file)
@@ -230,6 +230,7 @@ if ((!empty($moveup) or !empty($movedown)) and confirm_sesskey()) {
     if ($swapcategory and $movecategory) {
         $DB->set_field('course_categories', 'sortorder', $swapcategory->sortorder, array('id'=>$movecategory->id));
         $DB->set_field('course_categories', 'sortorder', $movecategory->sortorder, array('id'=>$swapcategory->id));
+        add_to_log(SITEID, "category", "move", "editcategory.php?id=$movecategory->id", $movecategory->id);
     }
 
     // finally reorder courses
index 92268fd..0240760 100644 (file)
@@ -68,6 +68,7 @@ function make_log_url($module, $url) {
         case 'lib':
         case 'admin':
         case 'calendar':
+        case 'category':
         case 'mnet course':
             if (strpos($url, '../') === 0) {
                 $url = ltrim($url, '.');
@@ -2149,6 +2150,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 +2170,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) {
@@ -2183,46 +2191,102 @@ function set_coursemodule_visible($id, $visible) {
 }
 
 /**
- * Delete a course module and any associated data at the course level (events)
- * Until 1.5 this function simply marked a deleted flag ... now it
- * deletes it completely.
+ * This function will handles the whole deletion process of a module. This includes calling
+ * the modules delete_instance function, deleting files, events, grades, conditional data,
+ * the data in the course_module and course_sections table and adding a module deletion
+ * event to the DB.
  *
+ * @param int $cmid the course module id
+ * @since 2.5
  */
-function delete_course_module($id) {
-    global $CFG, $DB;
+function course_delete_module($cmid) {
+    global $CFG, $DB, $USER;
+
     require_once($CFG->libdir.'/gradelib.php');
     require_once($CFG->dirroot.'/blog/lib.php');
 
-    if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
+    // Get the course module.
+    if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) {
         return true;
     }
-    $modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module));
-    //delete events from calendar
-    if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
+
+    // Get the module context.
+    $modcontext = context_module::instance($cm->id);
+
+    // Get the course module name.
+    $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module), MUST_EXIST);
+
+    // Get the file location of the delete_instance function for this module.
+    $modlib = "$CFG->dirroot/mod/$modulename/lib.php";
+
+    // Include the file required to call the delete_instance function for this module.
+    if (file_exists($modlib)) {
+        require_once($modlib);
+    } else {
+        throw new moodle_exception("This module is missing mod/$modulename/lib.php", '', '',
+            null, 'failedtodeletemodulemissinglibfile');
+    }
+
+    $deleteinstancefunction = $modulename . "_delete_instance";
+
+    if (!$deleteinstancefunction($cm->instance)) {
+        throw new moodle_exception("Could not delete the $modulename (instance)", '', '',
+            null, 'failedtodeletemoduleinstance');
+    }
+
+    // Remove all module files in case modules forget to do that.
+    $fs = get_file_storage();
+    $fs->delete_area_files($modcontext->id);
+
+    // Delete events from calendar.
+    if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename))) {
         foreach($events as $event) {
             delete_event($event->id);
         }
     }
-    //delete grade items, outcome items and grades attached to modules
-    if ($grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename,
-                                                   'iteminstance'=>$cm->instance, 'courseid'=>$cm->course))) {
+
+    // Delete grade items, outcome items and grades attached to modules.
+    if ($grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $modulename,
+                                                   'iteminstance' => $cm->instance, 'courseid' => $cm->course))) {
         foreach ($grade_items as $grade_item) {
             $grade_item->delete('moddelete');
         }
     }
+
     // Delete completion and availability data; it is better to do this even if the
     // features are not turned on, in case they were turned on previously (these will be
-    // very quick on an empty table)
+    // very quick on an empty table).
     $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
     $DB->delete_records('course_modules_availability', array('coursemoduleid'=> $cm->id));
     $DB->delete_records('course_modules_avail_fields', array('coursemoduleid' => $cm->id));
     $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
                                                             'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
 
+    // Delete the context.
     delete_context(CONTEXT_MODULE, $cm->id);
-    $DB->delete_records('course_modules', array('id'=>$cm->id));
+
+    // Delete the module from the course_modules table.
+    $DB->delete_records('course_modules', array('id' => $cm->id));
+
+    // Delete module from that section.
+    if (!delete_mod_from_section($cm->id, $cm->section)) {
+        throw new moodle_exception("Could not delete the $modulename from section", '', '',
+            null, 'failedtodeletemodulefromsection');
+    }
+
+    // Trigger a mod_deleted event with information about this module.
+    $eventdata = new stdClass();
+    $eventdata->modulename = $modulename;
+    $eventdata->cmid       = $cm->id;
+    $eventdata->courseid   = $cm->course;
+    $eventdata->userid     = $USER->id;
+    events_trigger('mod_deleted', $eventdata);
+
+    add_to_log($cm->course, 'course', "delete mod",
+               "view.php?id=$cm->course",
+               "$modulename $cm->instance", $cm->id);
+
     rebuild_course_cache($cm->course, true);
-    return true;
 }
 
 function delete_mod_from_section($modid, $sectionid) {
@@ -2418,7 +2482,7 @@ function reorder_sections($sections, $origin_position, $target_position) {
  * All parameters are objects
  */
 function moveto_module($mod, $section, $beforemod=NULL) {
-    global $OUTPUT;
+    global $OUTPUT, $DB;
 
 /// Remove original module from original section
     if (! delete_mod_from_section($mod->id, $mod->section)) {
@@ -2427,7 +2491,16 @@ function moveto_module($mod, $section, $beforemod=NULL) {
 
     // if moving to a hidden section then hide module
     if (!$section->visible && $mod->visible) {
+        // Set this in the object because it is sent as a response to ajax calls.
+        $mod->visible = 0;
         set_coursemodule_visible($mod->id, 0);
+        // Set visibleold to 1 so module will be visible when section is made visible.
+        $DB->set_field('course_modules', 'visibleold', 1, array('id' => $mod->id));
+    }
+    if ($section->visible && !$mod->visible) {
+        set_coursemodule_visible($mod->id, $mod->visibleold);
+        // Set this in the object because it is sent as a response to ajax calls.
+        $mod->visible = $mod->visibleold;
     }
 
 /// Add the module into the new section
@@ -2722,6 +2795,7 @@ function category_delete_full($category, $showfeedback=true) {
     // finally delete the category and it's context
     $DB->delete_records('course_categories', array('id'=>$category->id));
     delete_context(CONTEXT_COURSECAT, $category->id);
+    add_to_log(SITEID, "category", "delete", "index.php", "$category->name (ID $category->id)");
 
     events_trigger('course_category_deleted', $category);
 
@@ -2777,6 +2851,7 @@ function category_delete_move($category, $newparentid, $showfeedback=true) {
     // finally delete the category and it's context
     $DB->delete_records('course_categories', array('id'=>$category->id));
     delete_context(CONTEXT_COURSECAT, $category->id);
+    add_to_log(SITEID, "category", "delete", "index.php", "$category->name (ID $category->id)");
 
     events_trigger('course_category_deleted', $category);
 
@@ -2823,6 +2898,7 @@ function move_courses($courseids, $categoryid) {
             }
 
             $DB->update_record('course', $course);
+            add_to_log($course->id, "course", "move", "edit.php?id=$course->id", $course->id);
 
             $context   = context_course::instance($course->id);
             context_moved($context, $newparent);
@@ -2855,6 +2931,7 @@ function course_category_hide($category) {
             $DB->set_field('course', 'visible', 0, array('category' => $cat->id));
         }
     }
+    add_to_log(SITEID, "category", "hide", "editcategory.php?id=$category->id", $category->id);
 }
 
 /**
@@ -2878,6 +2955,7 @@ function course_category_show($category) {
             $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($cat->id));
         }
     }
+    add_to_log(SITEID, "category", "show", "editcategory.php?id=$category->id", $category->id);
 }
 
 /**
@@ -2891,12 +2969,10 @@ function move_category($category, $newparentcat) {
 
     $hidecat = false;
     if (empty($newparentcat->id)) {
-        $DB->set_field('course_categories', 'parent', 0, array('id'=>$category->id));
-
+        $DB->set_field('course_categories', 'parent', 0, array('id' => $category->id));
         $newparent = context_system::instance();
-
     } else {
-        $DB->set_field('course_categories', 'parent', $newparentcat->id, array('id'=>$category->id));
+        $DB->set_field('course_categories', 'parent', $newparentcat->id, array('id' => $category->id));
         $newparent = context_coursecat::instance($newparentcat->id);
 
         if (!$newparentcat->visible and $category->visible) {
@@ -2910,6 +2986,9 @@ function move_category($category, $newparentcat) {
     // now make it last in new category
     $DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('id'=>$category->id));
 
+    // Log action.
+    add_to_log(SITEID, "category", "move", "editcategory.php?id=$category->id", $category->id);
+
     // and fix the sortorders
     fix_course_sortorder();
 
@@ -3753,16 +3832,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 b1ed617..1ea8b78 100644 (file)
@@ -120,7 +120,6 @@ if (!empty($add)) {
     $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
 
     require_login($course, false, $cm);
-    $coursecontext = context_course::instance($course->id);
     $modcontext = context_module::instance($cm->id);
     require_capability('moodle/course:manageactivities', $modcontext);
 
@@ -138,9 +137,8 @@ if (!empty($add)) {
         $PAGE->set_title($strdeletecheck);
         $PAGE->set_heading($course->fullname);
         $PAGE->navbar->add($strdeletecheck);
-        echo $OUTPUT->header();
 
-        // print_simple_box_start('center', '60%', '#FFAAAA', 20, 'noticebox');
+        echo $OUTPUT->header();
         echo $OUTPUT->box_start('noticebox');
         $formcontinue = new single_button(new moodle_url("$CFG->wwwroot/course/mod.php", $optionsyes), get_string('yes'));
         $formcancel = new single_button($return, get_string('no'), 'get');
@@ -151,42 +149,8 @@ if (!empty($add)) {
         exit;
     }
 
-    $modlib = "$CFG->dirroot/mod/$cm->modname/lib.php";
-
-    if (file_exists($modlib)) {
-        require_once($modlib);
-    } else {
-        print_error('modulemissingcode', '', '', $modlib);
-    }
-
-    $deleteinstancefunction = $cm->modname."_delete_instance";
-
-    if (!$deleteinstancefunction($cm->instance)) {
-        echo $OUTPUT->notification("Could not delete the $cm->modname (instance)");
-    }
-
-    // remove all module files in case modules forget to do that
-    $fs = get_file_storage();
-    $fs->delete_area_files($modcontext->id);
-
-    if (!delete_course_module($cm->id)) {
-        echo $OUTPUT->notification("Could not delete the $cm->modname (coursemodule)");
-    }
-    if (!delete_mod_from_section($cm->id, $cm->section)) {
-        echo $OUTPUT->notification("Could not delete the $cm->modname from that section");
-    }
-
-    // Trigger a mod_deleted event with information about this module.
-    $eventdata = new stdClass();
-    $eventdata->modulename = $cm->modname;
-    $eventdata->cmid       = $cm->id;
-    $eventdata->courseid   = $course->id;
-    $eventdata->userid     = $USER->id;
-    events_trigger('mod_deleted', $eventdata);
-
-    add_to_log($course->id, 'course', "delete mod",
-               "view.php?id=$cm->course",
-               "$cm->modname $cm->instance", $cm->id);
+    // Delete the module.
+    course_delete_module($cm->id);
 
     redirect($return);
 }
index 56bf28a..c08108a 100644 (file)
@@ -480,6 +480,7 @@ if ($mform->is_cancelled()) {
         // make sure visibility is set correctly (in particular in calendar)
         // note: allow them to set it even without moodle/course:activityvisibility
         set_coursemodule_visible($fromform->coursemodule, $fromform->visible);
+        $DB->set_field('course_modules', 'visibleold', 1, array('id' => $fromform->coursemodule));
 
         if (isset($fromform->cmidnumber)) { //label
             // set cm idnumber - uniqueness is already verified by form validation
index 8056c57..4bae0b0 100644 (file)
@@ -134,6 +134,7 @@ switch($requestmethod) {
                         }
 
                         moveto_module($cm, $section, $beforemod);
+                        echo json_encode(array('visible' => $cm->visible));
                         break;
                     case 'gettitle':
                         require_capability('moodle/course:manageactivities', $modcontext);
@@ -194,44 +195,7 @@ switch($requestmethod) {
         switch ($class) {
             case 'resource':
                 require_capability('moodle/course:manageactivities', $modcontext);
-                $modlib = "$CFG->dirroot/mod/$cm->modname/lib.php";
-
-                if (file_exists($modlib)) {
-                    include_once($modlib);
-                } else {
-                    throw new moodle_exception("Ajax rest.php: This module is missing mod/$cm->modname/lib.php");
-                }
-                $deleteinstancefunction = $cm->modname."_delete_instance";
-
-                // Run the module's cleanup funtion.
-                if (!$deleteinstancefunction($cm->instance)) {
-                    throw new moodle_exception("Ajax rest.php: Could not delete the $cm->modname $cm->name (instance)");
-                    die;
-                }
-
-                // remove all module files in case modules forget to do that
-                $fs = get_file_storage();
-                $fs->delete_area_files($modcontext->id);
-
-                if (!delete_course_module($cm->id)) {
-                    throw new moodle_exception("Ajax rest.php: Could not delete the $cm->modname $cm->name (coursemodule)");
-                }
-                // Remove the course_modules entry.
-                if (!delete_mod_from_section($cm->id, $cm->section)) {
-                    throw new moodle_exception("Ajax rest.php: Could not delete the $cm->modname $cm->name from section");
-                }
-
-                // Trigger a mod_deleted event with information about this module.
-                $eventdata = new stdClass();
-                $eventdata->modulename = $cm->modname;
-                $eventdata->cmid       = $cm->id;
-                $eventdata->courseid   = $course->id;
-                $eventdata->userid     = $USER->id;
-                events_trigger('mod_deleted', $eventdata);
-
-                add_to_log($courseid, "course", "delete mod",
-                           "view.php?id=$courseid",
-                           "$cm->modname $cm->instance", $cm->id);
+                course_delete_module($cm->id);
                 break;
         }
         break;
index 96b8f1b..6872ece 100644 (file)
@@ -385,6 +385,9 @@ YUI.add('moodle-course-dragdrop', function(Y) {
                         spinner.show();
                     },
                     success: function(tid, response) {
+                        var responsetext = Y.JSON.parse(response.responseText);
+                        var params = {element: dragnode, visible: responsetext.visible};
+                        M.course.coursebase.invoke_function('set_visibility_resource_ui', params);
                         this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
                         window.setTimeout(function(e) {
                             spinner.hide();
index 23a61a3..8c652e0 100644 (file)
@@ -592,6 +592,26 @@ YUI.add('moodle-course-toolboxes', function(Y) {
                 }
             }, this);
             listenevents.push(thisevent);
+        },
+        /**
+         * Set the visibility of the current resource (identified by the element)
+         * to match the hidden parameter (this is not a toggle).
+         * Only changes the visibility in the browser (no ajax update).
+         * @param args An object with 'element' being the A node containing the resource
+         *             and 'visible' being the state that the visiblity should be set to.
+         * @return void
+         */
+        set_visibility_resource_ui: function(args) {
+            var element = args.element;
+            var shouldbevisible = args.visible;
+            var buttonnode = element.one(CSS.SHOW);
+            var visible = (buttonnode === null);
+            if (visible) {
+                buttonnode = element.one(CSS.HIDE);
+            }
+            if (visible != shouldbevisible) {
+                this.toggle_hide_resource_ui(buttonnode);
+            }
         }
     }, {
         NAME : 'course-resource-toolbox',
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 fcbdfcc..bd0da5c 100644 (file)
@@ -895,7 +895,7 @@ function groups_calculate_role_people($rs, $context) {
                 $roles[$roledata->id] = $roledata;
             }
             // Record that user has role
-            $users[$rec->userid]->roles[] = $roles[$rec->roleid];
+            $users[$rec->userid]->roles[$rec->roleid] = $roles[$rec->roleid];
         }
     }
     $rs->close();
@@ -925,7 +925,8 @@ function groups_calculate_role_people($rs, $context) {
         } else if($rolecount > 1) {
             $roleid = '*';
         } else {
-            $roleid = $userdata->roles[0]->id;
+            $userrole = reset($userdata->roles);
+            $roleid = $userrole->id;
         }
         $roles[$roleid]->users[$userid] = $userdata;
     }
index 8178322..df948f3 100644 (file)
@@ -310,6 +310,7 @@ $string['configsessioncookie'] = 'This setting customises the name of the cookie
 $string['configsessioncookiedomain'] = 'This allows you to change the domain that the Moodle cookies are available from. This is useful for Moodle customisations (e.g. authentication or enrolment plugins) that need to share Moodle session information with a web application on another subdomain. <strong>WARNING: it is strongly recommended to leave this setting at the default (empty) - an incorrect value will prevent all logins to the site.</strong>';
 $string['configsessioncookiepath'] = 'If you need to change where browsers send the Moodle cookies, you can change this setting to specify a subdirectory of your web site.  Otherwise the default \'/\' should be fine.';
 $string['configsessiontimeout'] = 'If people logged in to this site are idle for a long time (without loading pages) then they are automatically logged out (their session is ended).  This variable specifies how long this time should be.';
+$string['configshowicalsource'] = 'Show source information for ical events.';
 $string['configshowcommentscount'] = 'Show comments count, it will cost one more query when display comments link';
 $string['configshowsiteparticipantslist'] = 'All of these site students and site teachers will be listed on the site participants list. Who shall be allowed to see this site participants list?';
 $string['configsitedefaultlicense'] = 'Default site licence';
@@ -568,6 +569,7 @@ $string['helpcalendarcustomexport'] = 'Enable custom date range export option in
 $string['helpexportlookahead'] = 'How many days in the future does the calendar look for events during export for the custom export option?';
 $string['helpexportlookback'] = 'How many days in the past does the calendar look for events during export for the custom export option?';
 $string['helpforcetimezone'] = 'You can allow users to individually select their timezone, or force a timezone for everyone.';
+$string['helpshowicalsource'] = 'Enable this setting if you want to show the ical subscription name and link for ical imported events.';
 $string['helpsitemaintenance'] = 'For upgrades and other work';
 $string['helpstartofweek'] = 'Which day starts the week in the calendar?';
 $string['helpupcominglookahead'] = 'How many days in the future does the calendar look for upcoming events by default?';
index e2ee36b..208e9a4 100644 (file)
@@ -34,6 +34,7 @@ $string['area'] = 'Area';
 $string['caching'] = 'Caching';
 $string['cacheadmin'] = 'Cache administration';
 $string['cacheconfig'] = 'Configuration';
+$string['cachedef_calendar_subscriptions'] = 'Calendar subscriptions';
 $string['cachedef_config'] = 'Config settings';
 $string['cachedef_databasemeta'] = 'Database meta information';
 $string['cachedef_eventinvalidation'] = 'Event invalidation';
index 78c2fb9..59ea201 100644 (file)
@@ -126,6 +126,7 @@ $string['monthly'] = 'Monthly';
 $string['monthlyview'] = 'Monthly view';
 $string['monthnext'] = 'Next month';
 $string['monththis'] = 'This month';
+$string['namewithsource'] = '{$a->name}({$a->source})';
 $string['never'] = 'Never';
 $string['newevent'] = 'New event';
 $string['notitle'] = 'no title';
@@ -168,6 +169,7 @@ $string['subscriptions'] = 'Subscriptions';
 $string['subscriptionname'] = 'Calendar name';
 $string['subscriptionremoved'] = 'Calendar subscription {$a} removed';
 $string['subscriptionupdated'] = 'Calendar subscription {$a} updated';
+$string['subsource'] = 'Event source: {$a->name}';
 $string['sun'] = 'Sun';
 $string['sunday'] = 'Sunday';
 $string['thu'] = 'Thu';
index 135c3fb..3dea4dd 100644 (file)
@@ -183,13 +183,13 @@ $string['invalidcategoryidforparent'] = 'Invalid category id for parent!';
 $string['invalidcategoryidtomove'] = 'Invalid category id to move!';
 $string['invalidconfirm'] = 'Confirmation string was incorrect';
 $string['invalidcontextinhasanyquestions'] = 'Invalid context passed to question_context_has_any_questions.';
+$string['invalidgrade'] = 'Grades ({$a}) do not match grade options - question skipped.';
 $string['invalidpenalty'] = 'Invalid penalty';
 $string['invalidwizardpage'] = 'Incorrect or no wizard page specified!';
 $string['lastmodifiedby'] = 'Last modified by';
 $string['linkedfiledoesntexist'] = 'Linked file {$a} doesn\'t exist';
 $string['makechildof'] = 'Make child of \'{$a}\'';
 $string['maketoplevelitem'] = 'Move to top level';
-$string['matcherror'] = 'Grades do not match grade options - question skipped';
 $string['matchgrades'] = 'Match grades';
 $string['matchgradeserror'] = 'Error if grade not listed';
 $string['matchgradesnearest'] = 'Nearest grade if not listed';
index ba994c1..17f22fc 100644 (file)
@@ -5012,9 +5012,9 @@ class admin_setting_manageenrols extends admin_setting {
      * @return string
      */
     public function output_html($data, $query='') {
-        global $CFG, $OUTPUT, $DB;
+        global $CFG, $OUTPUT, $DB, $PAGE;
 
-        // display strings
+        // Display strings.
         $strup        = get_string('up');
         $strdown      = get_string('down');
         $strsettings  = get_string('settings');
@@ -5022,6 +5022,9 @@ class admin_setting_manageenrols extends admin_setting {
         $strdisable   = get_string('disable');
         $struninstall = get_string('uninstallplugin', 'admin');
         $strusage     = get_string('enrolusage', 'enrol');
+        $strversion   = get_string('version');
+
+        $pluginmanager = plugin_manager::instance();
 
         $enrols_available = enrol_get_plugins(false);
         $active_enrols    = enrol_get_plugins(true);
@@ -5033,8 +5036,7 @@ class admin_setting_manageenrols extends admin_setting {
         foreach ($enrols_available as $key=>$enrol) {
             $allenrols[$key] = true;
         }
-        // now find all borked plugins and at least allow then to uninstall
-        $borked = array();
+        // Now find all borked plugins and at least allow then to uninstall.
         $condidates = $DB->get_fieldset_sql("SELECT DISTINCT enrol FROM {enrol}");
         foreach ($condidates as $candidate) {
             if (empty($allenrols[$candidate])) {
@@ -5046,29 +5048,35 @@ class admin_setting_manageenrols extends admin_setting {
         $return .= $OUTPUT->box_start('generalbox enrolsui');
 
         $table = new html_table();
-        $table->head  = array(get_string('name'), $strusage, $strenable, $strup.'/'.$strdown, $strsettings, $struninstall);
-        $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
+        $table->head  = array(get_string('name'), $strusage, $strversion, $strenable, $strup.'/'.$strdown, $strsettings, $struninstall);
+        $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
         $table->id = 'courseenrolmentplugins';
         $table->attributes['class'] = 'admintable generaltable';
         $table->data  = array();
 
-        // iterate through enrol plugins and add to the display table
+        // Iterate through enrol plugins and add to the display table.
         $updowncount = 1;
         $enrolcount = count($active_enrols);
         $url = new moodle_url('/admin/enrol.php', array('sesskey'=>sesskey()));
         $printed = array();
         foreach($allenrols as $enrol => $unused) {
+            $plugininfo = $pluginmanager->get_plugin_info('enrol_'.$enrol);
+            $version = get_config('enrol_'.$enrol, 'version');
+            if ($version === false) {
+                $version = '';
+            }
+
             if (get_string_manager()->string_exists('pluginname', 'enrol_'.$enrol)) {
                 $name = get_string('pluginname', 'enrol_'.$enrol);
             } else {
                 $name = $enrol;
             }
-            //usage
+            // Usage.
             $ci = $DB->count_records('enrol', array('enrol'=>$enrol));
             $cp = $DB->count_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($enrol));
             $usage = "$ci / $cp";
 
-            // hide/show link
+            // Hide/show links.
             if (isset($active_enrols[$enrol])) {
                 $aurl = new moodle_url($url, array('action'=>'disable', 'enrol'=>$enrol));
                 $hideshow = "<a href=\"$aurl\">";
@@ -5086,8 +5094,13 @@ class admin_setting_manageenrols extends admin_setting {
                 $enabled = false;
                 $displayname = '<span class="notifyproblem">'.$name.'</span>';
             }
+            if ($PAGE->theme->resolve_image_location('icon', 'enrol_' . $name, false)) {
+                $icon = $OUTPUT->pix_icon('icon', '', 'enrol_' . $name, array('class' => 'icon pluginicon'));
+            } else {
+                $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
+            }
 
-            // up/down link (only if enrol is enabled)
+            // Up/down link (only if enrol is enabled).
             $updown = '';
             if ($enabled) {
                 if ($updowncount > 1) {
@@ -5107,20 +5120,25 @@ class admin_setting_manageenrols extends admin_setting {
                 ++$updowncount;
             }
 
-            // settings link
-            if (isset($active_enrols[$enrol]) or file_exists($CFG->dirroot.'/enrol/'.$enrol.'/settings.php')) {
-                $surl = new moodle_url('/admin/settings.php', array('section'=>'enrolsettings'.$enrol));
-                $settings = "<a href=\"$surl\">$strsettings</a>";
+            // Add settings link.
+            if (!$version) {
+                $settings = '';
+            } else if ($url = $plugininfo->get_settings_url()) {
+                $settings = html_writer::link($url, $strsettings);
             } else {
                 $settings = '';
             }
 
-            // uninstall
-            $aurl = new moodle_url($url, array('action'=>'uninstall', 'enrol'=>$enrol));
-            $uninstall = "<a href=\"$aurl\">$struninstall</a>";
+            // Add uninstall info.
+            if ($version) {
+                $url = new moodle_url($plugininfo->get_uninstall_url(), array('return'=>'settings'));
+                $uninstall = html_writer::link($url, $struninstall);
+            } else {
+                $uninstall = '';
+            }
 
-            // add a row to the table
-            $table->data[] = array($displayname, $usage, $hideshow, $updown, $settings, $uninstall);
+            // Add a row to the table.
+            $table->data[] = array($icon.$displayname, $usage, $version, $hideshow, $updown, $settings, $uninstall);
 
             $printed[$enrol] = true;
         }
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 4da22fa..235c4be 100644 (file)
@@ -1088,7 +1088,7 @@ class completion_info {
      * @return bool
      */
     public function is_tracked_user($userid) {
-        return is_enrolled(context_course::instance($this->course->id), $userid, '', true);
+        return is_enrolled(context_course::instance($this->course->id), $userid, 'moodle/course:isincompletionreports', true);
     }
 
     /**
@@ -1105,7 +1105,7 @@ class completion_info {
         global $DB;
 
         list($enrolledsql, $enrolledparams) = get_enrolled_sql(
-                context_course::instance($this->course->id), '', $groupid, true);
+                context_course::instance($this->course->id), 'moodle/course:isincompletionreports', $groupid, true);
         $sql  = 'SELECT COUNT(eu.id) FROM (' . $enrolledsql . ') eu JOIN {user} u ON u.id = eu.id';
         if ($where) {
             $sql .= " WHERE $where";
index 09a00fb..6299390 100644 (file)
@@ -104,5 +104,12 @@ $definitions = array(
         'simpledata' => true, // Array of stdClass objects containing only strings.
         'persist' => true, // Likely there will be a couple of calls to this.
         'persistmaxsize' => 2, // The original cache used 1, we've increased that to two.
+    ),
+     // Used to cache calendar subscriptions.
+    'calendar_subscriptions' => array(
+        'mode' => cache_store::MODE_APPLICATION,
+        'simplekeys' => true,
+        'simpledata' => true,
+        'persistent' => true,
     )
 );
index 51a8958..769ad0b 100644 (file)
@@ -121,4 +121,10 @@ mod_deleted - int courseid, int cmid, text modulename - happens when a module is
 mod_created - int courseid, int cmid, text modulename - happens when a module is created
 mod_updated - int courseid, int cmid, text modulename - happens when a module is updated
 
+=== blog events
+
+blog_entry_added - blog post object
+blog_entry_edited - blog post object
+blog_entry_deleteded - blog post object
+
 */
index 94abc81..8d62b39 100644 (file)
@@ -40,6 +40,9 @@ $logs = array(
     array('module'=>'course', 'action'=>'view', 'mtable'=>'course', 'field'=>'fullname'),
     array('module'=>'course', 'action'=>'view section', 'mtable'=>'course_sections', 'field'=>'name'),
     array('module'=>'course', 'action'=>'update', 'mtable'=>'course', 'field'=>'fullname'),
+    array('module'=>'course', 'action'=>'hide', 'mtable'=>'course', 'field'=>'fullname'),
+    array('module'=>'course', 'action'=>'show', 'mtable'=>'course', 'field'=>'fullname'),
+    array('module'=>'course', 'action'=>'move', 'mtable'=>'course', 'field'=>'fullname'),
     array('module'=>'course', 'action'=>'enrol', 'mtable'=>'course', 'field'=>'fullname'), // there should be some way to store user id of the enrolled user!
     array('module'=>'course', 'action'=>'unenrol', 'mtable'=>'course', 'field'=>'fullname'), // there should be some way to store user id of the enrolled user!
     array('module'=>'course', 'action'=>'report log', 'mtable'=>'course', 'field'=>'fullname'),
@@ -47,6 +50,11 @@ $logs = array(
     array('module'=>'course', 'action'=>'report outline', 'mtable'=>'course', 'field'=>'fullname'),
     array('module'=>'course', 'action'=>'report participation', 'mtable'=>'course', 'field'=>'fullname'),
     array('module'=>'course', 'action'=>'report stats', 'mtable'=>'course', 'field'=>'fullname'),
+    array('module'=>'category', 'action'=>'add', 'mtable'=>'course_categories', 'field'=>'name'),
+    array('module'=>'category', 'action'=>'hide', 'mtable'=>'course_categories', 'field'=>'name'),
+    array('module'=>'category', 'action'=>'move', 'mtable'=>'course_categories', 'field'=>'name'),
+    array('module'=>'category', 'action'=>'show', 'mtable'=>'course_categories', 'field'=>'name'),
+    array('module'=>'category', 'action'=>'update', 'mtable'=>'course_categories', 'field'=>'name'),
     array('module'=>'message', 'action'=>'write', 'mtable'=>'user', 'field'=>$DB->sql_concat('firstname', "' '" , 'lastname')),
     array('module'=>'message', 'action'=>'read', 'mtable'=>'user', 'field'=>$DB->sql_concat('firstname', "' '" , 'lastname')),
     array('module'=>'message', 'action'=>'add contact', 'mtable'=>'user', 'field'=>$DB->sql_concat('firstname', "' '" , 'lastname')),
index 327b03d..2108770 100644 (file)
@@ -3437,3 +3437,50 @@ function print_recent_activity($course) {
         echo '<p class="message">'.get_string('nothingnew').'</p>';
     }
 }
+
+/**
+ * Delete a course module and any associated data at the course level (events)
+ * Until 1.5 this function simply marked a deleted flag ... now it
+ * deletes it completely.
+ *
+ * @deprecated since 2.5
+ *
+ * @param int $id the course module id
+ * @return boolean true on success, false on failure
+ */
+function delete_course_module($id) {
+    debugging('Function delete_course_module() is deprecated. Please use course_delete_module() instead.', DEBUG_DEVELOPER);
+
+    global $CFG, $DB;
+
+    require_once($CFG->libdir.'/gradelib.php');
+    require_once($CFG->dirroot.'/blog/lib.php');
+
+    if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
+        return true;
+    }
+    $modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module));
+    //delete events from calendar
+    if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
+        foreach($events as $event) {
+            delete_event($event->id);
+        }
+    }
+    //delete grade items, outcome items and grades attached to modules
+    if ($grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename,
+                                                   'iteminstance'=>$cm->instance, 'courseid'=>$cm->course))) {
+        foreach ($grade_items as $grade_item) {
+            $grade_item->delete('moddelete');
+        }
+    }
+    // Delete completion and availability data; it is better to do this even if the
+    // features are not turned on, in case they were turned on previously (these will be
+    // very quick on an empty table)
+    $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
+    $DB->delete_records('course_modules_availability', array('coursemoduleid'=> $cm->id));
+    $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
+                                                            'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
+
+    delete_context(CONTEXT_MODULE, $cm->id);
+    return $DB->delete_records('course_modules', array('id'=>$cm->id));
+}
index 6ffbcbe..3dc9c55 100644 (file)
@@ -408,6 +408,7 @@ abstract class moodle_database {
             // free memory
             $this->last_sql    = null;
             $this->last_params = null;
+            $this->print_debug_time();
             return;
         }
 
@@ -415,7 +416,6 @@ abstract class moodle_database {
         $type   = $this->last_type;
         $sql    = $this->last_sql;
         $params = $this->last_params;
-        $time   = microtime(true) - $this->last_time;
         $error  = $this->get_last_error();
 
         $this->query_log($error);
@@ -520,6 +520,25 @@ abstract class moodle_database {
         }
     }
 
+    /**
+     * Prints the time a query took to run.
+     * @return void
+     */
+    protected function print_debug_time() {
+        if (!$this->get_debug()) {
+            return;
+        }
+        $time = microtime(true) - $this->last_time;
+        $message = "Query took: {$time} seconds.\n";
+        if (CLI_SCRIPT) {
+            echo $message;
+            echo "--------------------------------\n";
+        } else {
+            echo s($message);
+            echo "<hr />\n";
+        }
+    }
+
     /**
      * Returns the SQL WHERE conditions.
      * @param string $table The table name that these conditions will be validated against.
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 0c4569e..ede90a5 100644 (file)
@@ -95,12 +95,12 @@ class tinymce_texteditor extends texteditor {
      * @param null $fpoptions
      */
     public function use_editor($elementid, array $options=null, $fpoptions=null) {
-        global $PAGE;
-        // Note: use full moodle_url instance to prevent standard JS loader.
+        global $PAGE, $CFG;
+        // Note: use full moodle_url instance to prevent standard JS loader, make sure we are using https on profile page if required.
         if (debugging('', DEBUG_DEVELOPER)) {
-            $PAGE->requires->js(new moodle_url('/lib/editor/tinymce/tiny_mce/'.$this->version.'/tiny_mce_src.js'));
+            $PAGE->requires->js(new moodle_url($CFG->httpswwwroot.'/lib/editor/tinymce/tiny_mce/'.$this->version.'/tiny_mce_src.js'));
         } else {
-            $PAGE->requires->js(new moodle_url('/lib/editor/tinymce/tiny_mce/'.$this->version.'/tiny_mce.js'));
+            $PAGE->requires->js(new moodle_url($CFG->httpswwwroot.'/lib/editor/tinymce/tiny_mce/'.$this->version.'/tiny_mce.js'));
         }
         $PAGE->requires->js_init_call('M.editor_tinymce.init_editor', array($elementid, $this->get_init_params($elementid, $options)), true);
         if ($fpoptions) {
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 8f9f1d5..e1971d1 100644 (file)
@@ -3063,21 +3063,28 @@ class plugininfo_enrol extends plugininfo_base {
     }
 
     public function get_settings_section_name() {
-        return 'enrolsettings' . $this->name;
+        if (file_exists($this->full_path('settings.php'))) {
+            return 'enrolsettings' . $this->name;
+        } else {
+            return null;
+        }
     }
 
     public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
         global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
+
+        if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) {
+            return;
+        }
+        $section = $this->get_settings_section_name();
+
         $ADMIN = $adminroot; // may be used in settings.php
         $enrol = $this; // also can be used inside settings.php
-        $section = $this->get_settings_section_name();
+        $settings = new admin_settingpage($section, $this->displayname,
+                'moodle/site:config', $this->is_enabled() === false);
+
+        include($this->full_path('settings.php')); // This may also set $settings to null!
 
-        $settings = null;
-        if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
-            $settings = new admin_settingpage($section, $this->displayname,
-                    'moodle/site:config', $this->is_enabled() === false);
-            include($this->full_path('settings.php')); // this may also set $settings to null
-        }
         if ($settings) {
             $ADMIN->add($parentnodename, $settings);
         }
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/