Merge branch 'MDL-35622-master' of git://github.com/FMCorz/moodle
authorDan Poltawski <dan@moodle.com>
Wed, 20 Feb 2013 06:27:27 +0000 (14:27 +0800)
committerDan Poltawski <dan@moodle.com>
Wed, 20 Feb 2013 06:27:27 +0000 (14:27 +0800)
293 files changed:
.jshintrc
README.txt
admin/tool/behat/cli/util.php
admin/tool/behat/tests/behat/basic_actions.feature [new file with mode: 0644]
admin/tool/behat/tests/behat/data_generators.feature
admin/tool/behat/tests/behat/manipulate_forms.feature [new file with mode: 0644]
admin/tool/behat/tests/behat/nasty_strings.feature [new file with mode: 0644]
admin/tool/uploaduser/index.php
admin/tool/xmldb/actions/XMLDBAction.class.php
admin/tool/xmldb/actions/view_structure_php/view_structure_php.class.php
admin/tool/xmldb/actions/view_table_php/view_table_php.class.php
auth/db/auth.php
auth/email/auth.php
auth/ldap/auth.php
auth/manual/auth.php
auth/none/auth.php
auth/webservice/auth.php
backup/converter/moodle1/lib.php
backup/converter/moodle1/tests/lib_test.php
backup/import.php
backup/util/dbops/restore_dbops.class.php
backup/util/ui/base_moodleform.class.php
backup/util/xml/parser/processors/grouped_parser_processor.class.php
backup/util/xml/parser/tests/fixtures/test6.xml [new file with mode: 0644]
backup/util/xml/parser/tests/parser_test.php
blocks/community/communitycourse.php
blocks/community/forms.php
blocks/community/lang/en/block_community.php
blocks/community/styles.css
blocks/dock.js
blocks/glossary_random/block_glossary_random.php
blocks/glossary_random/edit_form.php
blocks/glossary_random/version.php
blocks/html/backup/moodle1/lib.php
blocks/navigation/version.php
blocks/navigation/yui/navigation/navigation.js
blocks/recent_activity/block_recent_activity.php
blocks/rss_client/backup/moodle1/lib.php
blocks/settings/lang/en/block_settings.php
blog/edit_form.php
cache/classes/config.php
cache/classes/definition.php
cache/classes/factory.php
cache/classes/helper.php
cache/locallib.php
cache/tests/cache_test.php
cache/tests/fixtures/lib.php
comment/comment_ajax.php
comment/lib.php
config-dist.php
course/dndupload.js
course/dnduploadlib.php
course/format/formatlegacy.php
course/format/topics/format.js
course/format/topics/lib.php
course/format/weeks/format.js
course/format/weeks/lib.php
course/lib.php
course/manage.php
course/tests/behat/add_activities.feature
course/tests/behat/behat_course.php
course/tests/courselib_test.php
course/yui/toolboxes/toolboxes.js
enrol/cohort/lib.php
enrol/imsenterprise/lib.php
enrol/imsenterprise/locallib.php
enrol/locallib.php
enrol/manual/ajax.php
enrol/manual/lib.php
enrol/manual/locallib.php
enrol/manual/yui/quickenrolment/quickenrolment.js
enrol/paypal/lib.php
enrol/self/lib.php
filter/activitynames/filter.php
grade/edit/outcome/index.php
grade/report/grader/ajax_callbacks.php
grade/report/grader/lib.php
grade/report/grader/module.js
grade/tests/reportgrader_test.php [new file with mode: 0644]
group/externallib.php
group/groupings.php
group/tests/externallib_test.php
help.php
install/lang/et/error.php
install/lang/kmr/langconfig.php [new file with mode: 0644]
install/lang/pt_br/install.php
lang/en/admin.php
lang/en/block.php
lang/en/countries.php
lang/en/form.php
lang/en/grading.php
lang/en/moodle.php
lang/en/rating.php
lib/accesslib.php
lib/behat/behat_base.php
lib/behat/classes/behat_config_manager.php
lib/behat/form_field/behat_form_editor.php
lib/cronlib.php
lib/csslib.php
lib/datalib.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/filelib.php
lib/filterlib.php
lib/form/form.js
lib/form/listing.php [new file with mode: 0644]
lib/form/yui/listing/listing.js [new file with mode: 0644]
lib/form/yui/shortforms/shortforms.js [new file with mode: 0644]
lib/form/yui/showadvanced/showadvanced.js [new file with mode: 0644]
lib/formslib.php
lib/google/Google_Client.php [new file with mode: 0644]
lib/google/LICENSE [new file with mode: 0755]
lib/google/NOTICE [new file with mode: 0755]
lib/google/README [new file with mode: 0755]
lib/google/auth/Google_AssertionCredentials.php [new file with mode: 0644]
lib/google/auth/Google_Auth.php [new file with mode: 0644]
lib/google/auth/Google_AuthNone.php [new file with mode: 0644]
lib/google/auth/Google_LoginTicket.php [new file with mode: 0644]
lib/google/auth/Google_OAuth2.php [new file with mode: 0644]
lib/google/auth/Google_P12Signer.php [new file with mode: 0644]
lib/google/auth/Google_PemVerifier.php [new file with mode: 0644]
lib/google/auth/Google_Signer.php [new file with mode: 0644]
lib/google/auth/Google_Verifier.php [new file with mode: 0644]
lib/google/cache/Google_ApcCache.php [new file with mode: 0644]
lib/google/cache/Google_Cache.php [new file with mode: 0644]
lib/google/cache/Google_FileCache.php [new file with mode: 0644]
lib/google/cache/Google_MemcacheCache.php [new file with mode: 0644]
lib/google/config.php [new file with mode: 0755]
lib/google/contrib/Google_AdexchangebuyerService.php [new file with mode: 0644]
lib/google/contrib/Google_AdsenseService.php [new file with mode: 0644]
lib/google/contrib/Google_AdsensehostService.php [new file with mode: 0644]
lib/google/contrib/Google_AnalyticsService.php [new file with mode: 0644]
lib/google/contrib/Google_BigqueryService.php [new file with mode: 0644]
lib/google/contrib/Google_BloggerService.php [new file with mode: 0644]
lib/google/contrib/Google_BooksService.php [new file with mode: 0644]
lib/google/contrib/Google_CalendarService.php [new file with mode: 0644]
lib/google/contrib/Google_ComputeService.php [new file with mode: 0644]
lib/google/contrib/Google_CustomsearchService.php [new file with mode: 0644]
lib/google/contrib/Google_DriveService.php [new file with mode: 0644]
lib/google/contrib/Google_FreebaseService.php [new file with mode: 0644]
lib/google/contrib/Google_FusiontablesService.php [new file with mode: 0644]
lib/google/contrib/Google_GanService.php [new file with mode: 0644]
lib/google/contrib/Google_LatitudeService.php [new file with mode: 0644]
lib/google/contrib/Google_LicensingService.php [new file with mode: 0644]
lib/google/contrib/Google_ModeratorService.php [new file with mode: 0644]
lib/google/contrib/Google_Oauth2Service.php [new file with mode: 0644]
lib/google/contrib/Google_OrkutService.php [new file with mode: 0644]
lib/google/contrib/Google_PagespeedonlineService.php [new file with mode: 0644]
lib/google/contrib/Google_PlusMomentsService.php [new file with mode: 0644]
lib/google/contrib/Google_PlusService.php [new file with mode: 0644]
lib/google/contrib/Google_PredictionService.php [new file with mode: 0644]
lib/google/contrib/Google_ShoppingService.php [new file with mode: 0644]
lib/google/contrib/Google_SiteVerificationService.php [new file with mode: 0644]
lib/google/contrib/Google_StorageService.php [new file with mode: 0644]
lib/google/contrib/Google_TaskqueueService.php [new file with mode: 0644]
lib/google/contrib/Google_TasksService.php [new file with mode: 0644]
lib/google/contrib/Google_TranslateService.php [new file with mode: 0644]
lib/google/contrib/Google_UrlshortenerService.php [new file with mode: 0644]
lib/google/contrib/Google_WebfontsService.php [new file with mode: 0644]
lib/google/contrib/Google_YoutubeService.php [new file with mode: 0644]
lib/google/curlio.php [new file with mode: 0644]
lib/google/external/URITemplateParser.php [new file with mode: 0644]
lib/google/io/Google_CacheParser.php [new file with mode: 0644]
lib/google/io/Google_CurlIO.php [new file with mode: 0644]
lib/google/io/Google_HttpRequest.php [new file with mode: 0644]
lib/google/io/Google_IO.php [new file with mode: 0644]
lib/google/io/Google_REST.php [new file with mode: 0644]
lib/google/io/cacerts.pem [new file with mode: 0644]
lib/google/local_config.php [new file with mode: 0644]
lib/google/readme_moodle.txt [new file with mode: 0644]
lib/google/service/Google_BatchRequest.php [new file with mode: 0644]
lib/google/service/Google_MediaFileUpload.php [new file with mode: 0644]
lib/google/service/Google_Model.php [new file with mode: 0644]
lib/google/service/Google_Service.php [new file with mode: 0644]
lib/google/service/Google_ServiceResource.php [new file with mode: 0644]
lib/google/service/Google_Utils.php [new file with mode: 0644]
lib/grade/tests/fixtures/lib.php
lib/installlib.php
lib/javascript-static.js
lib/modinfolib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputlib.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/password_compat/lib/password.php [new file with mode: 0644]
lib/password_compat/readme_moodle.txt [new file with mode: 0644]
lib/password_compat/tests/PasswordGetInfoTest.php [new file with mode: 0644]
lib/password_compat/tests/PasswordHashTest.php [new file with mode: 0644]
lib/password_compat/tests/PasswordNeedsRehashTest.php [new file with mode: 0644]
lib/password_compat/tests/PasswordVerifyTest.php [new file with mode: 0644]
lib/phpunit/bootstrap.php
lib/phpunit/classes/util.php
lib/setuplib.php
lib/statslib.php
lib/testing/classes/nasty_strings.php [new file with mode: 0644]
lib/testing/classes/util.php
lib/testing/generator/data_generator.php
lib/tests/behat/behat_data_generators.php
lib/tests/behat/behat_forms.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_hooks.php
lib/tests/behat/behat_navigation.php
lib/tests/behat/behat_transformations.php [new file with mode: 0644]
lib/tests/moodlelib_test.php
lib/thirdpartylibs.xml
lib/upgrade.txt
lib/upgradelib.php
lib/yui/popuphelp/popuphelp.js [new file with mode: 0644]
lib/yui/tooltip/tooltip.js [new file with mode: 0644]
message/index.php
message/lib.php
message/tests/externallib_test.php
mod/assign/externallib.php
mod/assign/feedback/offline/importgradesform.php
mod/assign/feedback/offline/locallib.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/locallib.php
mod/assign/mod_form.php
mod/assign/module.js
mod/assign/submission/file/locallib.php
mod/assign/tests/lib_test.php
mod/assign/tests/locallib_test.php
mod/assignment/type/upload/assignment.class.php
mod/assignment/type/uploadsingle/assignment.class.php
mod/choice/mod_form.php
mod/data/renderer.php
mod/folder/backup/moodle1/lib.php
mod/folder/mod_form.php
mod/forum/backup/moodle2/backup_forum_stepslib.php
mod/forum/db/access.php
mod/forum/db/install.xml
mod/forum/db/upgrade.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/mod_form.php
mod/forum/styles.css
mod/forum/upgrade.txt [new file with mode: 0644]
mod/forum/version.php
mod/label/db/upgrade.php
mod/label/lang/en/label.php
mod/label/lib.php
mod/label/settings.php [new file with mode: 0644]
mod/label/version.php
mod/lti/locallib.php
mod/quiz/accessrule/openclosedate/rule.php
mod/quiz/accessrule/openclosedate/tests/rule_test.php
mod/quiz/lib.php
mod/quiz/mod_form.php
mod/quiz/settings.php
mod/quiz/version.php
mod/resource/mod_form.php
mod/scorm/lang/en/scorm.php
mod/scorm/mod_form.php
mod/scorm/report/basic/report.php
mod/scorm/report/graphs/graph.php
mod/scorm/report/interactions/report.php
mod/workshop/mod_form.php
mod/workshop/settings.php
phpunit.xml.dist
question/engine/questionattempt.php
question/format/blackboard/tests/fixtures/sample_blackboard.dat
question/format/blackboard_six/formatbase.php
question/format/blackboard_six/tests/fixtures/sample_blackboard_pool.dat
question/format/xml/format.php
question/format/xml/tests/fixtures/truefalse.xml
question/type/essay/question.php
report/courseoverview/index.php
report/courseoverview/settings.php
report/log/locallib.php
report/security/lang/en/report_security.php
report/security/locallib.php
repository/filepicker.js
repository/googledocs/lang/en/repository_googledocs.php
repository/googledocs/lib.php
repository/googledocs/version.php
tag/coursetagslib.php
tag/index.php
theme/anomaly/style/general.css
theme/base/style/core.css
theme/canvas/style/core.css
theme/formal_white/style/formal_white.css
theme/splash/style/core.css
theme/standard/style/modules.css
theme/yui_combo.php
theme/yui_image.php
user/externallib.php
user/filters/user_filter_forms.php
user/tests/externallib_test.php
version.php
webservice/lib.php

index 9b833d9..06de646 100644 (file)
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,6 +1,5 @@
 {
     "browser":      true,
-    "node":         true,
     "yui":          true,
     "bitwise":      true,
     "curly":        true,
index 245848b..b1017df 100644 (file)
@@ -8,7 +8,7 @@ a few minutes:
 1) Move the Moodle files into your web directory.
 
 2) Create a single database for Moodle to store all
-   it's tables in (or choose an existing database).
+   its tables in (or choose an existing database).
 
 3) Visit your Moodle site with a browser, you should
    be taken to the install.php script, which will lead
index 4fd147f..8cba367 100644 (file)
@@ -84,6 +84,7 @@ error_reporting(E_ALL | E_STRICT);
 ini_set('display_errors', '1');
 ini_set('log_errors', '1');
 
+// Getting $CFG data.
 require_once(__DIR__ . '/../../../../config.php');
 
 // CFG->behat_prefix must be set and with value different than CFG->prefix and phpunit_prefix.
@@ -141,6 +142,10 @@ foreach ($vars as $var) {
 $CFG->noemailever = true;
 $CFG->passwordsaltmain = 'moodle';
 
+// Unset cache and temp directories to reset them again with the new $CFG->dataroot.
+unset($CFG->cachedir);
+unset($CFG->tempdir);
+
 // Continues setup.
 define('ABORT_AFTER_CONFIG_CANCEL', true);
 require("$CFG->dirroot/lib/setup.php");
@@ -150,6 +155,7 @@ require_once($CFG->libdir.'/upgradelib.php');
 require_once($CFG->libdir.'/clilib.php');
 require_once($CFG->libdir.'/pluginlib.php');
 require_once($CFG->libdir.'/installlib.php');
+require_once($CFG->libdir.'/testing/classes/test_lock.php');
 
 if ($unrecognized) {
     $unrecognized = implode("\n  ", $unrecognized);
@@ -165,6 +171,8 @@ if ($options['install']) {
     behat_util::install_site();
     mtrace("Acceptance tests site installed");
 } else if ($options['drop']) {
+    // Ensure no tests are running.
+    test_lock::acquire('behat');
     behat_util::drop_site();
     mtrace("Acceptance tests site dropped");
 } else if ($options['enable']) {
diff --git a/admin/tool/behat/tests/behat/basic_actions.feature b/admin/tool/behat/tests/behat/basic_actions.feature
new file mode 100644 (file)
index 0000000..8adc991
--- /dev/null
@@ -0,0 +1,29 @@
+@tool_behat
+Feature: Page contents assertions
+  In order to write good tests
+  As a tests writer
+  I need to check the page contents
+
+  @javascript
+  Scenario: Basic contents assertions
+    Given I log in as "admin"
+    And I am on homepage
+    And I expand "Users" node
+    And I follow "Groups"
+    And I press "Create group"
+    And I fill the moodle form with:
+      | Group name | I'm the name |
+      | Group description | I'm the description |
+    And I press "Save changes"
+    When I follow "Overview"
+    And I wait until the page is ready
+    And I wait "2" seconds
+    And I hover ".region-content .generaltable td span"
+    Then I should see "I'm the description"
+    And I should see "Filter groups by"
+    And I should not see "Filter groupssss by"
+    And I should see "Group members" in the ".region-content table th.c1" element
+    And I should not see "Group membersssss" in the ".region-content table th.c1" element
+    And I follow "Groups"
+    And the element "#groupeditform #showcreateorphangroupform" should be enabled
+    And the element "#groupeditform #showeditgroupsettingsform" should be disabled
index 19aeb51..ef5a993 100644 (file)
@@ -102,8 +102,8 @@ Feature: Set up contextual data for tests
     Then the "groups" select box should contain "Group 1 (1)"
     And the "groups" select box should contain "Group 2 (1)"
     And I select "Group 1 (1)" from "groups"
-    And I wait "1" seconds
+    And I wait "5" seconds
     And the "members" select box should contain "Student 1"
     And I select "Group 2 (1)" from "groups"
-    And I wait "1" seconds
+    And I wait "5" seconds
     And the "members" select box should contain "Student 2"
diff --git a/admin/tool/behat/tests/behat/manipulate_forms.feature b/admin/tool/behat/tests/behat/manipulate_forms.feature
new file mode 100644 (file)
index 0000000..9db0740
--- /dev/null
@@ -0,0 +1,20 @@
+@tool_behat @core_form
+Feature: Forms manipulation
+  In order to interact with Moodle
+  As a user
+  I need to set forms values
+
+  @javascript
+  Scenario: Basic forms manipulation
+    Given I log in as "admin"
+    And I follow "Admin User"
+    And I follow "Edit profile"
+    When I fill in "First name" with "Field value"
+    And I select "Use standard web forms" from "When editing text"
+    And I check "Unmask"
+    Then the "First name" field should match "Field value" value
+    And the "When editing text" select box should contain "Use standard web forms"
+    And the "Unmask" checkbox should be checked
+    And I uncheck "Unmask"
+    And the "Unmask" checkbox should not be checked
+    And I press "Update profile"
diff --git a/admin/tool/behat/tests/behat/nasty_strings.feature b/admin/tool/behat/tests/behat/nasty_strings.feature
new file mode 100644 (file)
index 0000000..9135798
--- /dev/null
@@ -0,0 +1,60 @@
+@tool_behat
+Feature: Transform steps arguments
+  In order to write tests with complex nasty arguments
+  As a tests writer
+  I need to apply some transformations to the steps arguments
+
+  Background:
+    Given I am on homepage
+    And the following "courses" exists:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And I log in as "admin"
+    And I follow "Admin User"
+    And I follow "Edit profile"
+
+  Scenario: Use nasty strings on steps arguments
+    When I fill in "Surname" with "$NASTYSTRING1"
+    And I fill in "Description" with "$NASTYSTRING2"
+    And I fill in "City/town" with "$NASTYSTRING3"
+    And I press "Update profile"
+    And I follow "Edit profile"
+    Then I should not see "NASTYSTRING"
+    And the "Surname" field should match "$NASTYSTRING1" value
+    And the "City/town" field should match "$NASTYSTRING3" value
+
+  Scenario: Use nasty strings on table nodes
+    When I fill the moodle form with:
+      | Surname | $NASTYSTRING1 |
+      | Description | $NASTYSTRING2 |
+      | City/town | $NASTYSTRING3 |
+    And I press "Update profile"
+    And I follow "Edit profile"
+    Then I should not see "NASTYSTRING"
+    And the "Surname" field should match "$NASTYSTRING1" value
+    And the "City/town" field should match "$NASTYSTRING3" value
+
+  Scenario: Use double quotes
+    When I fill the moodle form with:
+      | First name | va"lue1 |
+      | Description | va\"lue2 |
+    And I fill in "City/town" with "va\"lue3"
+    And I press "Update profile"
+    And I follow "Edit profile"
+    Then I should not see "NASTYSTRING"
+    And the "First name" field should match "va\"lue1" value
+    And the "Description" field should match "va\"lue2" value
+    And the "City/town" field should match "va\"lue3" value
+
+  @javascript
+  Scenario: Nasty strings with other contents
+    When I fill in "First name" with "My Firstname $NASTYSTRING1"
+    And I fill the moodle form with:
+      | Surname | My Surname $NASTYSTRING2 |
+    And I press "Update profile"
+    And I follow "Edit profile"
+    Then I should not see "NASTYSTRING"
+    And I should see "My Firstname"
+    And I should see "My Surname"
+    And the "First name" field should match "My Firstname $NASTYSTRING1" value
+    And the "Surname" field should match "My Surname $NASTYSTRING2" value
index f249f1d..88aaeda 100644 (file)
@@ -535,6 +535,7 @@ if ($formdata = $mform2->is_cancelled()) {
                     }
                     if (!property_exists($user, $column) or !property_exists($existinguser, $column)) {
                         // this should never happen
+                        debugging("Could not find $column on the user objects", DEBUG_DEVELOPER);
                         continue;
                     }
                     if ($updatetype == UU_UPDATE_MISSING) {
@@ -618,7 +619,7 @@ if ($formdata = $mform2->is_cancelled()) {
                 // Do not mess with passwords of remote users.
 
             } else if (!$isinternalauth) {
-                $existinguser->password = 'not cached';
+                $existinguser->password = AUTH_PASSWORD_NOT_CACHED;
                 $upt->track('password', '-', 'normal', false);
                 // clean up prefs
                 unset_user_preference('create_password', $existinguser);
@@ -626,6 +627,8 @@ if ($formdata = $mform2->is_cancelled()) {
 
             } else if (!empty($user->password)) {
                 if ($updatepasswords) {
+                    // Check for passwords that we want to force users to reset next
+                    // time they log in.
                     $errmsg = null;
                     $weak = !check_password_policy($user->password, $errmsg);
                     if ($resetpasswords == UU_PWRESET_ALL or ($resetpasswords == UU_PWRESET_WEAK and $weak)) {
@@ -638,7 +641,12 @@ if ($formdata = $mform2->is_cancelled()) {
                         unset_user_preference('auth_forcepasswordchange', $existinguser);
                     }
                     unset_user_preference('create_password', $existinguser); // no need to create password any more
-                    $existinguser->password = hash_internal_user_password($user->password);
+
+                    // Use a low cost factor when generating bcrypt hash otherwise
+                    // hashing would be slow when uploading lots of users. Hashes
+                    // will be automatically updated to a higher cost factor the first
+                    // time the user logs in.
+                    $existinguser->password = hash_internal_user_password($user->password, true);
                     $upt->track('password', $user->password, 'normal', false);
                 } else {
                     // do not print password when not changed
@@ -771,10 +779,14 @@ if ($formdata = $mform2->is_cancelled()) {
                         }
                         $forcechangepassword = true;
                     }
-                    $user->password = hash_internal_user_password($user->password);
+                    // Use a low cost factor when generating bcrypt hash otherwise
+                    // hashing would be slow when uploading lots of users. Hashes
+                    // will be automatically updated to a higher cost factor the first
+                    // time the user logs in.
+                    $user->password = hash_internal_user_password($user->password, true);
                 }
             } else {
-                $user->password = 'not cached';
+                $user->password = AUTH_PASSWORD_NOT_CACHED;
                 $upt->track('password', '-', 'normal', false);
             }
 
index 3c7ca7a..d93fba0 100644 (file)
@@ -239,22 +239,22 @@ class XMLDBAction {
         switch ($plugintype ) {
             case 'lib': // has own savepoint function
                 $result = XMLDB_LINEFEED .
-                          '        // Main savepoint reached' . XMLDB_LINEFEED .
+                          '        // Main savepoint reached.' . XMLDB_LINEFEED .
                           '        upgrade_main_savepoint(true, XXXXXXXXXX);' . XMLDB_LINEFEED;
                 break;
             case 'mod': // has own savepoint function
                 $result = XMLDB_LINEFEED .
-                          '        // ' . $pluginname . ' savepoint reached' . XMLDB_LINEFEED .
+                          '        // ' . ucfirst($pluginname) . ' savepoint reached.' . XMLDB_LINEFEED .
                           '        upgrade_mod_savepoint(true, XXXXXXXXXX, ' . "'$pluginname'" . ');' . XMLDB_LINEFEED;
                 break;
             case 'block': // has own savepoint function
                 $result = XMLDB_LINEFEED .
-                          '        // ' . $pluginname . ' savepoint reached' . XMLDB_LINEFEED .
+                          '        // ' . ucfirst($pluginname) . ' savepoint reached.' . XMLDB_LINEFEED .
                           '        upgrade_block_savepoint(true, XXXXXXXXXX, ' . "'$pluginname'" . ');' . XMLDB_LINEFEED;
                 break;
             default: // rest of plugins
                 $result = XMLDB_LINEFEED .
-                          '        // ' . $pluginname . ' savepoint reached' . XMLDB_LINEFEED .
+                          '        // ' . ucfirst($pluginname) . ' savepoint reached.' . XMLDB_LINEFEED .
                           '        upgrade_plugin_savepoint(true, XXXXXXXXXX, ' . "'$plugintype'" . ', ' . "'$pluginname'" . ');' . XMLDB_LINEFEED;
         }
         return $result;
index 0ddf559..99d4524 100644 (file)
@@ -170,10 +170,10 @@ class view_structure_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Define table ' . $table->getName() . ' to be created' . XMLDB_LINEFEED;
+        $result .= '        // Define table ' . $table->getName() . ' to be created.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Adding fields to table ' . $table->getName() . XMLDB_LINEFEED;
+        $result .= '        // Adding fields to table ' . $table->getName() . '.' . XMLDB_LINEFEED;
         // Iterate over each field
         foreach ($table->getFields() as $field) {
             // The field header, with name
@@ -186,7 +186,7 @@ class view_structure_php extends XMLDBAction {
         // Iterate over each key
         if ($keys = $table->getKeys()) {
             $result .= XMLDB_LINEFEED;
-            $result .= '        // Adding keys to table ' . $table->getName() . XMLDB_LINEFEED;
+            $result .= '        // Adding keys to table ' . $table->getName() . '.' . XMLDB_LINEFEED;
             foreach ($keys as $key) {
                 // The key header, with name
                 $result .= '        $table->add_key(' . "'" . $key->getName() . "', ";
@@ -199,7 +199,7 @@ class view_structure_php extends XMLDBAction {
         // Iterate over each index
         if ($indexes = $table->getIndexes()) {
             $result .= XMLDB_LINEFEED;
-            $result .= '        // Adding indexes to table ' . $table->getName() . XMLDB_LINEFEED;
+            $result .= '        // Adding indexes to table ' . $table->getName() . '.' . XMLDB_LINEFEED;
             foreach ($indexes as $index) {
                 // The index header, with name
                 $result .= '        $table->add_index(' . "'" . $index->getName() . "', ";
@@ -212,7 +212,7 @@ class view_structure_php extends XMLDBAction {
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Conditionally launch create table for ' . $table->getName() . XMLDB_LINEFEED;
+        $result .= '        // Conditionally launch create table for ' . $table->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        if (!$dbman->table_exists($table)) {' . XMLDB_LINEFEED;
         $result .= '            $dbman->create_table($table);' . XMLDB_LINEFEED;
         $result .= '        }' . XMLDB_LINEFEED;
@@ -250,12 +250,12 @@ class view_structure_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Define table ' . $table->getName() . ' to be dropped' . XMLDB_LINEFEED;
+        $result .= '        // Define table ' . $table->getName() . ' to be dropped.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Conditionally launch drop table for ' . $table->getName() . XMLDB_LINEFEED;
+        $result .= '        // Conditionally launch drop table for ' . $table->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        if ($dbman->table_exists($table)) {' . XMLDB_LINEFEED;
         $result .= '            $dbman->drop_table($table);' . XMLDB_LINEFEED;
         $result .= '        }' . XMLDB_LINEFEED;
@@ -293,12 +293,12 @@ class view_structure_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Define table ' . $table->getName() . ' to be renamed to NEWNAMEGOESHERE' . XMLDB_LINEFEED;
+        $result .= '        // Define table ' . $table->getName() . ' to be renamed to NEWNAMEGOESHERE.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Launch rename table for ' . $table->getName() . XMLDB_LINEFEED;
+        $result .= '        // Launch rename table for ' . $table->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $dbman->rename_table($table, ' . "'NEWNAMEGOESHERE'" . ');' . XMLDB_LINEFEED;
 
         // Add the proper upgrade_xxxx_savepoint call
index 235056f..e975201 100644 (file)
@@ -314,13 +314,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Define field ' . $field->getName() . ' to be added to ' . $table->getName() . XMLDB_LINEFEED;
+        $result .= '        // Define field ' . $field->getName() . ' to be added to ' . $table->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= '        $field = new xmldb_field(' . "'" . $field->getName() . "', " . $field->getPHP(true) . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Conditionally launch add field ' . $field->getName() . XMLDB_LINEFEED;
+        $result .= '        // Conditionally launch add field ' . $field->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        if (!$dbman->field_exists($table, $field)) {'. XMLDB_LINEFEED;
         $result .= '            $dbman->add_field($table, $field);' . XMLDB_LINEFEED;
         $result .= '        }'. XMLDB_LINEFEED;
@@ -362,13 +362,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Define field ' . $field->getName() . ' to be dropped from ' . $table->getName() . XMLDB_LINEFEED;
+        $result .= '        // Define field ' . $field->getName() . ' to be dropped from ' . $table->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= '        $field = new xmldb_field(' . "'" . $field->getName() . "'" . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Conditionally launch drop field ' . $field->getName() . XMLDB_LINEFEED;
+        $result .= '        // Conditionally launch drop field ' . $field->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        if ($dbman->field_exists($table, $field)) {' . XMLDB_LINEFEED;
         $result .= '            $dbman->drop_field($table, $field);' . XMLDB_LINEFEED;
         $result .= '        }' . XMLDB_LINEFEED;
@@ -410,13 +410,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Rename field ' . $field->getName() . ' on table ' . $table->getName() . ' to NEWNAMEGOESHERE'. XMLDB_LINEFEED;
+        $result .= '        // Rename field ' . $field->getName() . ' on table ' . $table->getName() . ' to NEWNAMEGOESHERE.'. XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= '        $field = new xmldb_field(' . "'" . $field->getName() . "', " . $field->getPHP(true) . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Launch rename field ' . $field->getName() . XMLDB_LINEFEED;
+        $result .= '        // Launch rename field ' . $field->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $dbman->rename_field($table, $field, ' . "'" . 'NEWNAMEGOESHERE' . "'" . ');' . XMLDB_LINEFEED;
 
         // Add the proper upgrade_xxxx_savepoint call
@@ -465,13 +465,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Changing type of field ' . $field->getName() . ' on table ' . $table->getName() . ' to ' . $type . XMLDB_LINEFEED;
+        $result .= '        // Changing type of field ' . $field->getName() . ' on table ' . $table->getName() . ' to ' . $type . '.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= '        $field = new xmldb_field(' . "'" . $field->getName() . "', " . $field->getPHP(true) . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Launch change of type for field ' . $field->getName() . XMLDB_LINEFEED;
+        $result .= '        // Launch change of type for field ' . $field->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $dbman->change_field_type($table, $field);' . XMLDB_LINEFEED;
 
         // Add the proper upgrade_xxxx_savepoint call
@@ -517,13 +517,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Changing precision of field ' . $field->getName() . ' on table ' . $table->getName() . ' to ' . $precision . XMLDB_LINEFEED;
+        $result .= '        // Changing precision of field ' . $field->getName() . ' on table ' . $table->getName() . ' to ' . $precision . '.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
-        $result .= '        $field = new xmldb_field(' . "'" . $field->getName() . "', " .$field->getPHP(true) . ');' . XMLDB_LINEFEED;
+        $result .= '        $field = new xmldb_field(' . "'" . $field->getName() . "', " . $field->getPHP(true) . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Launch change of precision for field ' . $field->getName() . XMLDB_LINEFEED;
+        $result .= '        // Launch change of precision for field ' . $field->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $dbman->change_field_precision($table, $field);' . XMLDB_LINEFEED;
 
         // Add the proper upgrade_xxxx_savepoint call
@@ -565,13 +565,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Changing nullability of field ' . $field->getName() . ' on table ' . $table->getName() . ' to ' . $notnull . XMLDB_LINEFEED;
+        $result .= '        // Changing nullability of field ' . $field->getName() . ' on table ' . $table->getName() . ' to ' . $notnull . '.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= '        $field = new xmldb_field(' . "'" . $field->getName() . "', " . $field->getPHP(true) . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Launch change of nullability for field ' . $field->getName() . XMLDB_LINEFEED;
+        $result .= '        // Launch change of nullability for field ' . $field->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $dbman->change_field_notnull($table, $field);' . XMLDB_LINEFEED;
 
         // Add the proper upgrade_xxxx_savepoint call
@@ -613,13 +613,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Changing the default of field ' . $field->getName() . ' on table ' . $table->getName() . ' to ' . $default . XMLDB_LINEFEED;
+        $result .= '        // Changing the default of field ' . $field->getName() . ' on table ' . $table->getName() . ' to ' . $default . '.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= '        $field = new xmldb_field(' . "'" . $field->getName() . "', " . $field->getPHP(true) . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Launch change of default for field ' . $field->getName() . XMLDB_LINEFEED;
+        $result .= '        // Launch change of default for field ' . $field->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $dbman->change_field_default($table, $field);' . XMLDB_LINEFEED;
 
         // Add the proper upgrade_xxxx_savepoint call
@@ -659,13 +659,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Define key ' . $key->getName() . ' ('. $key->getXMLDBKeyName($key->getType()) . ') to be added to ' . $table->getName() . XMLDB_LINEFEED;
+        $result .= '        // Define key ' . $key->getName() . ' ('. $key->getXMLDBKeyName($key->getType()) . ') to be added to ' . $table->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= '        $key = new xmldb_key(' . "'" . $key->getName() . "', " . $key->getPHP(true) . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Launch add key ' . $key->getName() . XMLDB_LINEFEED;
+        $result .= '        // Launch add key ' . $key->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $dbman->add_key($table, $key);' . XMLDB_LINEFEED;
 
         // Add the proper upgrade_xxxx_savepoint call
@@ -705,13 +705,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Define key ' . $key->getName() . ' ('. $key->getXMLDBKeyName($key->getType()) . ') to be dropped form ' . $table->getName() . XMLDB_LINEFEED;
+        $result .= '        // Define key ' . $key->getName() . ' ('. $key->getXMLDBKeyName($key->getType()) . ') to be dropped form ' . $table->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= '        $key = new xmldb_key(' . "'" . $key->getName() . "', " . $key->getPHP(true) . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Launch drop key ' . $key->getName() . XMLDB_LINEFEED;
+        $result .= '        // Launch drop key ' . $key->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $dbman->drop_key($table, $key);' . XMLDB_LINEFEED;
 
         // Add the proper upgrade_xxxx_savepoint call
@@ -754,13 +754,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Define key ' . $key->getName() . ' ('. $key->getXMLDBKeyName($key->getType()) . ') to be renamed to NEWNAMEGOESHERE' . XMLDB_LINEFEED;
+        $result .= '        // Define key ' . $key->getName() . ' ('. $key->getXMLDBKeyName($key->getType()) . ') to be renamed to NEWNAMEGOESHERE.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= '        $key = new xmldb_key(' . "'" . $key->getName() . "', " . $key->getPHP(true) . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Launch rename key ' . $key->getName() . XMLDB_LINEFEED;
+        $result .= '        // Launch rename key ' . $key->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $dbman->rename_key($table, $key, ' . "'" . 'NEWNAMEGOESHERE' . "'" . ');' . XMLDB_LINEFEED;
 
         // Add the proper upgrade_xxxx_savepoint call
@@ -800,13 +800,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Define index ' . $index->getName() . ' ('. ($index->getUnique() ? 'unique' : 'not unique') . ') to be added to ' . $table->getName() . XMLDB_LINEFEED;
+        $result .= '        // Define index ' . $index->getName() . ' ('. ($index->getUnique() ? 'unique' : 'not unique') . ') to be added to ' . $table->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= '        $index = new xmldb_index(' . "'" . $index->getName() . "', " . $index->getPHP(true) . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Conditionally launch add index ' . $index->getName() . XMLDB_LINEFEED;
+        $result .= '        // Conditionally launch add index ' . $index->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        if (!$dbman->index_exists($table, $index)) {' . XMLDB_LINEFEED;
         $result .= '            $dbman->add_index($table, $index);' . XMLDB_LINEFEED;
         $result .= '        }' . XMLDB_LINEFEED;
@@ -848,13 +848,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Define index ' . $index->getName() . ' ('. ($index->getUnique() ? 'unique' : 'not unique') . ') to be dropped form ' . $table->getName() . XMLDB_LINEFEED;
+        $result .= '        // Define index ' . $index->getName() . ' ('. ($index->getUnique() ? 'unique' : 'not unique') . ') to be dropped form ' . $table->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= '        $index = new xmldb_index(' . "'" . $index->getName() . "', " . $index->getPHP(true) . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Conditionally launch drop index ' . $index->getName() . XMLDB_LINEFEED;
+        $result .= '        // Conditionally launch drop index ' . $index->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        if ($dbman->index_exists($table, $index)) {' . XMLDB_LINEFEED;
         $result .= '            $dbman->drop_index($table, $index);' . XMLDB_LINEFEED;
         $result .= '        }' . XMLDB_LINEFEED;
@@ -899,13 +899,13 @@ class view_table_php extends XMLDBAction {
 
         // Add contents
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Define index ' . $index->getName() . ' ('. ($index->getUnique() ? 'unique' : 'not unique') . ') to be renamed to NEWNAMEGOESHERE' . XMLDB_LINEFEED;
+        $result .= '        // Define index ' . $index->getName() . ' ('. ($index->getUnique() ? 'unique' : 'not unique') . ') to be renamed to NEWNAMEGOESHERE.' . XMLDB_LINEFEED;
         $result .= '        $table = new xmldb_table(' . "'" . $table->getName() . "'" . ');' . XMLDB_LINEFEED;
         $result .= '        $index = new xmldb_index(' . "'" . $index->getName() . "', " . $index->getPHP(true) . ');' . XMLDB_LINEFEED;
 
         // Launch the proper DDL
         $result .= XMLDB_LINEFEED;
-        $result .= '        // Launch rename index ' . $index->getName() . XMLDB_LINEFEED;
+        $result .= '        // Launch rename index ' . $index->getName() . '.' . XMLDB_LINEFEED;
         $result .= '        $dbman->rename_index($table, $index, ' . "'" . 'NEWNAMEGOESHERE' . "'" . ');' . XMLDB_LINEFEED;
 
         // Add the proper upgrade_xxxx_savepoint call
index 57c46e4..f245d60 100644 (file)
@@ -221,6 +221,9 @@ class auth_plugin_db extends auth_plugin_base {
 
         if ($this->is_internal()) {
             $puser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
+            // This will also update the stored hash to the latest algorithm
+            // if the existing hash is using an out-of-date algorithm (or the
+            // legacy md5 algorithm).
             if (update_internal_user_password($puser, $newpassword)) {
                 $user->password = $puser->password;
                 return true;
index e50c09e..777fe9f 100644 (file)
@@ -59,6 +59,9 @@ class auth_plugin_email extends auth_plugin_base {
      */
     function user_update_password($user, $newpassword) {
         $user = get_complete_user_data('id', $user->id);
+        // This will also update the stored hash to the latest algorithm
+        // if the existing hash is using an out-of-date algorithm (or the
+        // legacy md5 algorithm).
         return update_internal_user_password($user, $newpassword);
     }
 
index 6cd7fd7..9bdd5b3 100644 (file)
@@ -529,6 +529,9 @@ class auth_plugin_ldap extends auth_plugin_base {
         profile_save_data($user);
 
         $this->update_user_record($user->username);
+        // This will also update the stored hash to the latest algorithm
+        // if the existing hash is using an out-of-date algorithm (or the
+        // legacy md5 algorithm).
         update_internal_user_password($user, $plainslashedpassword);
 
         $user = $DB->get_record('user', array('id'=>$user->id));
index 29cb59a..0c521e8 100644 (file)
@@ -82,6 +82,9 @@ class auth_plugin_manual extends auth_plugin_base {
      */
     function user_update_password($user, $newpassword) {
         $user = get_complete_user_data('id', $user->id);
+        // This will also update the stored hash to the latest algorithm
+        // if the existing hash is using an out-of-date algorithm (or the
+        // legacy md5 algorithm).
         return update_internal_user_password($user, $newpassword);
     }
 
index f0771d8..00eaf04 100644 (file)
@@ -59,6 +59,9 @@ class auth_plugin_none extends auth_plugin_base {
      */
     function user_update_password($user, $newpassword) {
         $user = get_complete_user_data('id', $user->id);
+        // This will also update the stored hash to the latest algorithm
+        // if the existing hash is using an out-of-date algorithm (or the
+        // legacy md5 algorithm).
         return update_internal_user_password($user, $newpassword);
     }
 
index 15a7598..59e1603 100644 (file)
@@ -85,6 +85,9 @@ class auth_plugin_webservice extends auth_plugin_base {
      */
     function user_update_password($user, $newpassword) {
         $user = get_complete_user_data('id', $user->id);
+        // This will also update the stored hash to the latest algorithm
+        // if the existing hash is using an out-of-date algorithm (or the
+        // legacy md5 algorithm).
         return update_internal_user_password($user, $newpassword);
     }
 
index d91e2f2..8f7ec30 100644 (file)
@@ -1279,6 +1279,12 @@ class moodle1_file_manager implements loggable {
      */
     public function migrate_directory($rootpath, $relpath='/') {
 
+        // Check the trailing slash in the $rootpath
+        if (substr($rootpath, -1) === '/') {
+            debugging('moodle1_file_manager::migrate_directory() expects $rootpath without the trailing slash', DEBUG_DEVELOPER);
+            $rootpath = substr($rootpath, 0, strlen($rootpath) - 1);
+        }
+
         if (!file_exists($this->basepath.'/'.$rootpath.$relpath)) {
             return array();
         }
index 154b6c8..8fb50b6 100644 (file)
@@ -266,13 +266,6 @@ class moodle1_converter_testcase extends advanced_testcase {
         $fileids = $fileman->get_fileids();
         $this->assertEquals(gettype($fileids), 'array');
         $this->assertEquals(0, count($fileids));
-        // try to migrate a non-existing directory
-        $returned = $fileman->migrate_directory('not/existing/directory');
-        $this->assertEquals(gettype($returned), 'array');
-        $this->assertEquals(0, count($returned));
-        $fileids = $fileman->get_fileids();
-        $this->assertEquals(gettype($fileids), 'array');
-        $this->assertEquals(0, count($fileids));
         // try to migrate an invalid file
         $fileman->itemid = 1;
         $thrown = false;
@@ -314,6 +307,72 @@ class moodle1_converter_testcase extends advanced_testcase {
         $converter->drop_stash_storage();
     }
 
+    public function test_migrate_directory() {
+        $this->resetAfterTest(true);
+
+        // Set-up the file manager.
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+        $converter->create_stash_storage();
+        $contextid = $converter->get_contextid(CONTEXT_MODULE, 32);
+        $fileman   = $converter->get_file_manager($contextid, 'mod_unittest', 'testarea');
+        // This fileman has not converted anything yet.
+        $fileids = $fileman->get_fileids();
+        $this->assertEquals(gettype($fileids), 'array');
+        $this->assertEquals(0, count($fileids));
+        // Try to migrate a non-existing directory.
+        $returned = $fileman->migrate_directory('not/existing/directory');
+        $this->assertEquals(gettype($returned), 'array');
+        $this->assertEquals(0, count($returned));
+        $fileids = $fileman->get_fileids();
+        $this->assertEquals(gettype($fileids), 'array');
+        $this->assertEquals(0, count($fileids));
+        // Try to migrate whole course_files.
+        $returned = $fileman->migrate_directory('course_files');
+        $this->assertEquals(gettype($returned), 'array');
+        $this->assertEquals(4, count($returned)); // Two files, two directories.
+        $fileids = $fileman->get_fileids();
+        $this->assertEquals(gettype($fileids), 'array');
+        $this->assertEquals(4, count($fileids));
+        $subdir = substr($this->iconhash, 0, 2);
+        $this->assertTrue(is_file($converter->get_workdir_path().'/files/'.$subdir.'/'.$this->iconhash));
+
+        // Check the file records.
+        $files = array();
+        $filerecordids = $converter->get_stash_itemids('files');
+        foreach ($filerecordids as $filerecordid) {
+            $filerecord = $converter->get_stash('files', $filerecordid);
+            $files[$filerecord['filepath'].$filerecord['filename']] = $filerecord;
+        }
+        $this->assertEquals('array', gettype($files['/.']));
+        $this->assertEquals('array', gettype($files['/file1.gif']));
+        $this->assertEquals('array', gettype($files['/sub1/.']));
+        $this->assertEquals('array', gettype($files['/sub1/file2.gif']));
+        $this->assertEquals(sha1(''), $files['/.']['contenthash']);
+        $this->assertEquals(sha1(''), $files['/sub1/.']['contenthash']);
+        $this->assertEquals($this->iconhash, $files['/file1.gif']['contenthash']);
+        $this->assertEquals($this->iconhash, $files['/sub1/file2.gif']['contenthash']);
+
+        $converter->drop_stash_storage();
+    }
+
+    public function test_migrate_directory_with_trailing_slash() {
+        $this->resetAfterTest(true);
+
+        // Set-up the file manager.
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+        $converter->create_stash_storage();
+        $contextid = $converter->get_contextid(CONTEXT_MODULE, 32);
+        $fileman   = $converter->get_file_manager($contextid, 'mod_unittest', 'testarea');
+        // Try to migrate a subdirectory passed with the trailing slash.
+        $returned = $fileman->migrate_directory('course_files/sub1/');
+        // Debugging message must be thrown in this case.
+        $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
+        $this->assertEquals(gettype($returned), 'array');
+        $this->assertEquals(2, count($returned)); // One file, one directory.
+
+        $converter->drop_stash_storage();
+    }
+
     public function test_convert_path() {
         $path = new convert_path('foo_bar', '/ROOT/THINGS/FOO/BAR');
         $this->assertEquals('foo_bar', $path->get_name());
index 4de77b2..56349c9 100644 (file)
@@ -11,6 +11,8 @@ require_once($CFG->dirroot . '/backup/util/ui/import_extensions.php');
 $courseid = required_param('id', PARAM_INT);
 // The id of the course we are importing FROM (will only be set if past first stage
 $importcourseid = optional_param('importid', false, PARAM_INT);
+// We just want to check if a search has been run. True if anything is there.
+$coursesearch = optional_param('search', false, PARAM_BOOL);
 // The target method for the restore (adding or deleting)
 $restoretarget = optional_param('target', backup::TARGET_CURRENT_ADDING, PARAM_INT);
 
@@ -36,7 +38,7 @@ $PAGE->set_pagelayout('incourse');
 $renderer = $PAGE->get_renderer('core','backup');
 
 // Check if we already have a import course id
-if ($importcourseid === false) {
+if ($importcourseid === false || $coursesearch) {
     // Obviously not... show the selector so one can be chosen
     $url = new moodle_url('/backup/import.php', array('id'=>$courseid));
     $search = new import_course_search(array('url'=>$url));
index 82c4897..148afa3 100644 (file)
@@ -1052,7 +1052,7 @@ abstract class restore_dbops {
 
                 // Most external plugins do not store passwords locally
                 if (!empty($userauth->preventpassindb)) {
-                    $user->password = 'not cached';
+                    $user->password = AUTH_PASSWORD_NOT_CACHED;
 
                 // If Moodle is responsible for storing/validating pwd and reset functionality is available, mark
                 } else if ($userauth->isinternal and $userauth->canresetpwd) {
index fb0e975..7a733ee 100644 (file)
@@ -81,6 +81,7 @@ abstract class base_moodleform extends moodleform {
     function definition() {
         $ui = $this->uistage->get_ui();
         $mform = $this->_form;
+        $mform->setDisableShortforms();
         $stage = $mform->addElement('hidden', 'stage', $this->uistage->get_stage());
         $stage = $mform->addElement('hidden', $ui->get_name(), $ui->get_uniqueid());
         $params = $this->uistage->get_params();
index d8e260a..48782b7 100644 (file)
@@ -76,6 +76,17 @@ abstract class grouped_parser_processor extends simplified_parser_processor {
      * @param string $path xml path which parsing has started
      */
     public function before_path($path) {
+        if ($this->path_is_grouped($path) and !isset($this->currentdata[$path])) {
+            // If the grouped element itself does not contain any final tags,
+            // we would not get any chunk data for it. So we add an artificial
+            // empty data chunk here that will be eventually replaced with
+            // real data later in {@link self::postprocess_chunk()}.
+            $this->currentdata[$path] = array(
+                'path' => $path,
+                'level' => substr_count($path, '/') + 1,
+                'tags' => array(),
+            );
+        }
         if (!$this->grouped_parent_exists($path)) {
             parent::before_path($path);
         }
@@ -93,7 +104,10 @@ abstract class grouped_parser_processor extends simplified_parser_processor {
             // currentdata, properly built
             $data = $this->currentdata[$path];
             unset($this->currentdata[$path]);
+            // Always, before dispatching any chunk, send all pending start notifications.
+            $this->process_pending_startend_notifications($path, 'start');
             // TODO: If running under DEBUG_DEVELOPER notice about >1MB grouped chunks
+            // And, finally, dispatch it.
             $this->dispatch_chunk($data);
         }
         // Normal notification of path end
@@ -167,7 +181,7 @@ abstract class grouped_parser_processor extends simplified_parser_processor {
      */
     protected function build_currentdata($grouped, $data) {
         // Check the grouped already exists into currentdata
-        if (!array_key_exists($grouped, $this->currentdata)) {
+        if (!is_array($this->currentdata) or !array_key_exists($grouped, $this->currentdata)) {
             $a = new stdclass();
             $a->grouped = $grouped;
             $a->child = $data['path'];
diff --git a/backup/util/xml/parser/tests/fixtures/test6.xml b/backup/util/xml/parser/tests/fixtures/test6.xml
new file mode 100644 (file)
index 0000000..a0399e8
--- /dev/null
@@ -0,0 +1,60 @@
+<test>
+  <MOODLE_BACKUP>
+    <COURSE>
+      <FORMATDATA>
+        <WEEKS>
+          <WEEK>
+            <SECTION>1</SECTION>
+            <HIDENUMBER>1</HIDENUMBER>
+            <HIDEDATE>0</HIDEDATE>
+            <SHOWTO></SHOWTO>
+            <OFFLINEMATERIAL>0</OFFLINEMATERIAL>
+          </WEEK>
+          <WEEK>
+            <SECTION>2</SECTION>
+            <HIDENUMBER>0</HIDENUMBER>
+            <HIDEDATE>0</HIDEDATE>
+            <RESETNUMBER>0</RESETNUMBER>
+            <SHOWTO></SHOWTO>
+            <OFFLINEMATERIAL>0</OFFLINEMATERIAL>
+          </WEEK>
+        </WEEKS>
+        <IMPORTED>
+        </IMPORTED>
+      </FORMATDATA>
+      <GROUPEDNOTOBSERVED>
+        <NOBODYOBSERVERSTHIS>
+          <NOTOBSERVED>Muhehe</NOTOBSERVED>
+        </NOBODYOBSERVERSTHIS>
+      </GROUPEDNOTOBSERVED>
+      <EMPTYGROUPED>
+      </EMPTYGROUPED>
+      <SECONDGROUPED>
+        <SUBS>
+          <SUB>
+            <PROP>Unit tests rock!</PROP>
+          </SUB>
+        </SUBS>
+      </SECONDGROUPED>
+    </COURSE>
+  </MOODLE_BACKUP>
+  <moodle2>
+    <grouped id="this is not parsed at the moment because there are no final elements">
+      <subs>
+        <sub id="34">
+          <prop>Oh yeah</prop>
+        </sub>
+      </subs>
+    </grouped>
+    <groupednonemptywithattr id="78">
+      <prop>Go baby go</prop>
+      <subs>
+        <sub id="89">
+          <prop>http://moodle.org</prop>
+        </sub>
+      </subs>
+    </groupednonemptywithattr>
+    <groupedemptywithattr attr="ay?">
+    </groupedemptywithattr>
+  </moodle2>
+</test>
index 2a1b5cc..39bf2d7 100644 (file)
@@ -632,6 +632,93 @@ class progressive_parser_test extends advanced_testcase {
         $this->assertEquals($errcount, 0); // No errors found, plz
     }
 
+    /**
+     */
+    function test_grouped_at_empty_node() {
+        global $CFG;
+        // Instantiate progressive_parser.
+        $pp =  new progressive_parser();
+        // Instantiate grouped_parser_processor.
+        $pr = new mock_grouped_parser_processor();
+        $this->assertTrue($pr instanceof progressive_parser_processor);
+        // Add interesting paths - moodle1 style.
+        $pr->add_path('/test/MOODLE_BACKUP/COURSE/FORMATDATA', true);
+        $pr->add_path('/test/MOODLE_BACKUP/COURSE/FORMATDATA/WEEKS/WEEK');
+        $pr->add_path('/test/MOODLE_BACKUP/COURSE/EMPTYGROUPED', true);
+        $pr->add_path('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED', true);
+        $pr->add_path('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED/SUBS/SUB');
+        // Add interesting paths - moodle2 style.
+        $pr->add_path('/test/moodle2/grouped', true);
+        $pr->add_path('/test/moodle2/grouped/subs/sub');
+        $pr->add_path('/test/moodle2/groupedemptywithattr', true);
+        $pr->add_path('/test/moodle2/groupednonemptywithattr', true);
+        $pr->add_path('/test/moodle2/groupednonemptywithattr/subs/sub');
+        // Assign processor to parser.
+        $pp->set_processor($pr);
+        // Set file from fixtures.
+        $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test6.xml');
+        // Process the file.
+        $pp->process();
+
+        // Get all the simplified chunks and perform various validations.
+        $chunks = $pr->get_chunks();
+        $this->assertEquals(count($chunks), 6); // All grouped elements.
+
+        // Check some random data.
+        $this->assertEquals('/test/MOODLE_BACKUP/COURSE/FORMATDATA', $chunks[0]['path']);
+        $this->assertEquals(2, $chunks[0]['tags']['WEEKS']['WEEK'][1]['SECTION']);
+
+        $this->assertEquals('/test/MOODLE_BACKUP/COURSE/EMPTYGROUPED', $chunks[1]['path']);
+        $this->assertEquals(array(), $chunks[1]['tags']);
+
+        $this->assertEquals('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED', $chunks[2]['path']);
+        $this->assertEquals('Unit tests rock!', $chunks[2]['tags']['SUBS']['SUB'][0]['PROP']);
+
+        $this->assertEquals('/test/moodle2/grouped', $chunks[3]['path']);
+        $this->assertFalse(isset($chunks[3]['tags']['id'])); // No final elements, this should be fixed one day.
+        $this->assertEquals(34, $chunks[3]['tags']['subs']['sub'][0]['id']); // We have final element so this is parsed.
+        $this->assertEquals('Oh yeah', $chunks[3]['tags']['subs']['sub'][0]['prop']);
+
+        $this->assertEquals('/test/moodle2/groupednonemptywithattr', $chunks[4]['path']);
+        $this->assertEquals(78, $chunks[4]['tags']['id']); // We have final element so this is parsed.
+        $this->assertEquals('Go baby go', $chunks[4]['tags']['prop']);
+        $this->assertEquals(89, $chunks[4]['tags']['subs']['sub'][0]['id']);
+        $this->assertEquals('http://moodle.org', $chunks[4]['tags']['subs']['sub'][0]['prop']);
+
+        $this->assertEquals('/test/moodle2/groupedemptywithattr', $chunks[5]['path']);
+        $this->assertFalse(isset($chunks[5]['tags']['attr'])); // No final elements, this should be fixed one day.
+
+        // Now check start notifications.
+        $snotifs = $pr->get_start_notifications();
+        // Check we have received the correct number of notifications.
+        $this->assertEquals(count($snotifs), 6);
+        // Check the order of notifications (in order they appear in test6.xml).
+        $this->assertEquals('/test/MOODLE_BACKUP/COURSE/FORMATDATA', $snotifs[0]);
+        $this->assertEquals('/test/MOODLE_BACKUP/COURSE/EMPTYGROUPED', $snotifs[1]);
+        $this->assertEquals('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED', $snotifs[2]);
+        $this->assertEquals('/test/moodle2/grouped', $snotifs[3]);
+        $this->assertEquals('/test/moodle2/groupednonemptywithattr', $snotifs[4]);
+        $this->assertEquals('/test/moodle2/groupedemptywithattr', $snotifs[5]);
+
+        // Now check end notifications.
+        $enotifs = $pr->get_end_notifications();
+        // Check we have received the correct number of notifications.
+        $this->assertEquals(count($enotifs), 6);
+        // Check the order of notifications (in order they appear in test6.xml).
+        $this->assertEquals('/test/MOODLE_BACKUP/COURSE/FORMATDATA', $enotifs[0]);
+        $this->assertEquals('/test/MOODLE_BACKUP/COURSE/EMPTYGROUPED', $enotifs[1]);
+        $this->assertEquals('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED', $enotifs[2]);
+        $this->assertEquals('/test/moodle2/grouped', $enotifs[3]);
+        $this->assertEquals('/test/moodle2/groupednonemptywithattr', $enotifs[4]);
+        $this->assertEquals('/test/moodle2/groupedemptywithattr', $enotifs[5]);
+
+        // Now verify that the start/process/end order is correct.
+        $allnotifs = $pr->get_all_notifications();
+        $this->assertEquals(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks));
+        // Check integrity of the notifications.
+        $errcount = $this->helper_check_notifications_order_integrity($allnotifs);
+        $this->assertEquals(0, $errcount);
+    }
 
     /**
      * Helper function that given one array of ordered start/process/end notifications will
index 045e72d..fe21d76 100644 (file)
@@ -142,7 +142,7 @@ $fromformdata['subject'] = optional_param('subject', 'all', PARAM_ALPHANUMEXT);
 $fromformdata['audience'] = optional_param('audience', 'all', PARAM_ALPHANUMEXT);
 $fromformdata['language'] = optional_param('language', current_language(), PARAM_ALPHANUMEXT);
 $fromformdata['educationallevel'] = optional_param('educationallevel', 'all', PARAM_ALPHANUMEXT);
-$fromformdata['downloadable'] = optional_param('downloadable', 0, PARAM_ALPHANUM);
+$fromformdata['downloadable'] = optional_param('downloadable', 1, PARAM_ALPHANUM);
 $fromformdata['orderby'] = optional_param('orderby', 'newest', PARAM_ALPHA);
 $fromformdata['huburl'] = optional_param('huburl', HUB_MOODLEORGHUBURL, PARAM_URL);
 $fromformdata['search'] = $search;
index 641ef17..3e00e62 100644 (file)
@@ -76,7 +76,7 @@ class community_hub_search_form extends moodleform {
         if (isset($this->_customdata['downloadable'])) {
             $downloadable = $this->_customdata['downloadable'];
         } else {
-            $downloadable = 0;
+            $downloadable = 1;
         }
         if (isset($this->_customdata['orderby'])) {
             $orderby = $this->_customdata['orderby'];
@@ -135,60 +135,69 @@ class community_hub_search_form extends moodleform {
         }
 
         if (!empty($hubs)) {
-            //TODO: sort hubs by trusted/prioritize
-            //Public hub list
-            $options = array();
-            $firsthub = false;
+            $htmlhubs = array();
             foreach ($hubs as $hub) {
+                // Name can come from hub directory - need some cleaning.
+                $hubname = clean_text($hub['name'], PARAM_TEXT);
+                $smalllogohtml = '';
                 if (array_key_exists('id', $hub)) {
-                    $params = array('hubid' => $hub['id'],
-                        'filetype' => HUB_HUBSCREENSHOT_FILE_TYPE);
-                    $imgurl = new moodle_url(HUB_HUBDIRECTORYURL .
-                                    "/local/hubdirectory/webservice/download.php", $params);
-                    $ascreenshothtml = html_writer::empty_tag('img',
-                                    array('src' => $imgurl, 'alt' => $hub['name']));
-
-                    $hubdescription = html_writer::tag('a', $hub['name'],
-                                    array('class' => 'hublink clearfix', 'href' => $hub['url'],
-                                        'onclick' => 'this.target="_blank"'));
-                    $hubdescription .= html_writer::tag('span', $ascreenshothtml,
-                                    array('class' => 'hubscreenshot'));
-                    $hubdescriptiontext = html_writer::tag('span', format_text($hub['description'], FORMAT_PLAIN),
-                                    array('class' => 'hubdescription'));
+
+                    // Retrieve hub logo + generate small logo.
+                    $params = array('hubid' => $hub['id'], 'filetype' => HUB_HUBSCREENSHOT_FILE_TYPE);
+                    $imgurl = new moodle_url(HUB_HUBDIRECTORYURL . "/local/hubdirectory/webservice/download.php", $params);
+                    $imgsize = getimagesize($imgurl->out(false));
+                    if ($imgsize[0] > 1) {
+                        $ascreenshothtml = html_writer::empty_tag('img', array('src' => $imgurl, 'alt' => $hubname));
+                        $smalllogohtml = html_writer::empty_tag('img', array('src' => $imgurl, 'alt' => $hubname
+                                        , 'height' => 30, 'width' => 40));
+                    } else {
+                        $ascreenshothtml = '';
+                    }
+                    $hubimage = html_writer::tag('div', $ascreenshothtml, array('class' => 'hubimage'));
+
+                    // Statistics + trusted info.
+                    $hubstats = '';
                     if (isset($hub['enrollablecourses'])) { //check needed to avoid warnings for Moodle version < 2011081700
                         $additionaldesc = get_string('enrollablecourses', 'block_community') . ': ' . $hub['enrollablecourses'] . ' - ' .
                                 get_string('downloadablecourses', 'block_community') . ': ' . $hub['downloadablecourses'];
-                        $hubdescriptiontext .= html_writer::tag('span', $additionaldesc,
-                                        array('class' => 'hubadditionaldesc'));
+                        $hubstats .= html_writer::tag('div', $additionaldesc);
                     }
                     if ($hub['trusted']) {
-                    $hubtrusted =  get_string('hubtrusted', 'block_community');
-                    $hubdescriptiontext .= html_writer::tag('span',
-                                    $hubtrusted . ' ' . $OUTPUT->doc_link('trusted_hubs'),
-                                    array('class' => 'trusted'));
-
+                        $hubtrusted =  get_string('hubtrusted', 'block_community');
+                        $hubstats .= $OUTPUT->doc_link('trusted_hubs') . html_writer::tag('div', $hubtrusted);
                     }
-                    $hubdescriptiontext = html_writer::tag('span', $hubdescriptiontext,
-                            array('class' => 'hubdescriptiontext'));
+                    $hubstats = html_writer::tag('div', $hubstats, array('class' => 'hubstats'));
 
-                    $hubdescription = html_writer::tag('span',
-                                    $hubdescription . $hubdescriptiontext,
-                                    array('class' => $hub['trusted'] ? 'hubtrusted' : 'hubnottrusted'));
-                } else {
-                    $hubdescription = html_writer::tag('a', $hub['name'],
-                                    array('class' => 'hublink hubtrusted', 'href' => $hub['url']));
-                }
+                    // hub name link + hub description.
+                    $hubnamelink = html_writer::link($hub['url'], html_writer::tag('h2',$hubname),
+                                    array('class' => 'hubtitlelink'));
+                    // The description can come from the hub directory - need to clean.
+                    $hubdescription = clean_param($hub['description'], PARAM_TEXT);
+                    $hubdescriptiontext = html_writer::tag('div', format_text($hubdescription, FORMAT_PLAIN),
+                                    array('class' => 'hubdescription'));
 
-                if (empty($firsthub)) {
-                    $mform->addElement('radio', 'huburl', get_string('selecthub', 'block_community'),
-                            $hubdescription, $hub['url']);
-                    $mform->setDefault('huburl', $huburl);
-                    $firsthub = true;
+                    $hubtext = html_writer::tag('div', $hubdescriptiontext . $hubstats, array('class' => 'hubtext'));
+
+                    $hubimgandtext = html_writer::tag('div', $hubimage . $hubtext, array('class' => 'hubimgandtext'));
+
+                    $hubfulldesc = html_writer::tag('div', $hubnamelink . $hubimgandtext, array('class' => 'hubmainhmtl'));
                 } else {
-                    $mform->addElement('radio', 'huburl', '', $hubdescription, $hub['url']);
+                    $hubfulldesc = html_writer::link($hub['url'], $hubname);
                 }
+
+                // Add hub to the hub items.
+                $hubinfo = new stdClass();
+                $hubinfo->mainhtml = $hubfulldesc;
+                $hubinfo->rowhtml = html_writer::tag('div', $smalllogohtml , array('class' => 'hubsmalllogo')) . $hubname;
+                $hubitems[$hub['url']] = $hubinfo;
             }
 
+            // Hub listing form element.
+            $mform->addElement('listing','huburl', '', '', array('items' => $hubitems,
+                'showall' => get_string('showall', 'block_community'),
+                'hideall' => get_string('hideall', 'block_community')));
+            $mform->setDefault('huburl', $huburl);
+
             //display enrol/download select box if the USER has the download capability on the course
             if (has_capability('moodle/community:download',
                             context_course::instance($this->_customdata['courseid']))) {
@@ -197,6 +206,8 @@ class community_hub_search_form extends moodleform {
                 $mform->addElement('select', 'downloadable', get_string('enroldownload', 'block_community'),
                         $options);
                 $mform->addHelpButton('downloadable', 'enroldownload', 'block_community');
+
+                $mform->setDefault('downloadable', $downloadable);
             } else {
                 $mform->addElement('hidden', 'downloadable', 0);
             }
@@ -262,23 +273,30 @@ class community_hub_search_form extends moodleform {
             collatorlib::asort($languages);
             $languages = array_merge(array('all' => get_string('any')), $languages);
             $mform->addElement('select', 'language', get_string('language'), $languages);
+
             $mform->setDefault('language', $language);
             $mform->addHelpButton('language', 'language', 'block_community');
 
-            $mform->addElement('radio', 'orderby', get_string('orderby', 'block_community'),
-                    get_string('orderbynewest', 'block_community'), 'newest');
-            $mform->addElement('radio', 'orderby', null,
-                    get_string('orderbyeldest', 'block_community'), 'eldest');
-            $mform->addElement('radio', 'orderby', null,
-                    get_string('orderbyname', 'block_community'), 'fullname');
-            $mform->addElement('radio', 'orderby', null,
-                    get_string('orderbypublisher', 'block_community'), 'publisher');
-            $mform->addElement('radio', 'orderby', null,
-                    get_string('orderbyratingaverage', 'block_community'), 'ratingaverage');
+            $mform->addElement('select', 'orderby', get_string('orderby', 'block_community'),
+                array('newest' => get_string('orderbynewest', 'block_community'),
+                    'eldest' => get_string('orderbyeldest', 'block_community'),
+                    'fullname' => get_string('orderbyname', 'block_community'),
+                    'publisher' => get_string('orderbypublisher', 'block_community'),
+                    'ratingaverage' => get_string('orderbyratingaverage', 'block_community')));
+
             $mform->setDefault('orderby', $orderby);
+            $mform->addHelpButton('orderby', 'orderby', 'block_community');
             $mform->setType('orderby', PARAM_ALPHA);
 
-            $mform->addElement('text', 'search', get_string('keywords', 'block_community'));
+            $mform->setAdvanced('audience');
+            $mform->setAdvanced('educationallevel');
+            $mform->setAdvanced('subject');
+            $mform->setAdvanced('licence');
+            $mform->setAdvanced('language');
+            $mform->setAdvanced('orderby');
+
+            $mform->addElement('text', 'search', get_string('keywords', 'block_community'),
+                array('size' => 30));
             $mform->addHelpButton('search', 'keywords', 'block_community');
 
 
@@ -298,4 +316,4 @@ class community_hub_search_form extends moodleform {
         return $errors;
     }
 
-}
\ No newline at end of file
+}
index ef509e3..df06794 100644 (file)
@@ -68,9 +68,11 @@ $string['enrollablecourses'] = 'Enrollable courses';
 $string['errorcourselisting'] = 'An error occurred when retrieving the course listing from the selected hub, please try again later. ({$a})';
 $string['errorhublisting'] = 'An error occurred when retrieving the hub listing from Moodle.org, please try again later. ({$a})';
 $string['fileinfo'] = 'Language: {$a->lang} - License: {$a->license} -  Time updated: {$a->timeupdated}';
+$string['hideall'] = 'Hide hubs';
 $string['hub'] = 'hub';
 $string['hubnottrusted'] = 'Not trusted';
 $string['hubtrusted'] = 'This hub is trusted by Moodle.org';
+$string['install'] = 'Install';
 $string['keywords'] = 'Keywords';
 $string['keywords_help'] = 'You can search for courses containing specific text in the name, description and other fields of the database.';
 $string['langdesc'] = 'Language: {$a} - ';
@@ -106,6 +108,7 @@ $string['searchcourse'] = 'Search for community course';
 $string['selecthub'] = 'Select hub';
 $string['selecthub_help'] = 'Select hub where to search the courses.';
 $string['sites'] = 'Sites';
+$string['showall'] = 'Show all hubs';
 $string['subject'] = 'Subject';
 $string['subject_help'] = 'To narrow your search to courses about a particular subject, choose one from this list.';
 $string['userinfo'] = 'Creator: {$a->creatorname} - Publisher: {$a->publishername}';
index 28d6162..96598cf 100644 (file)
@@ -2,30 +2,23 @@
 
 /* HUB SELECTOR */
 #page-blocks-community-communitycourse .hubscreenshot {float: left; }
-#page-blocks-community-communitycourse .hubdescription {
-    color: #003333;
-    font-size: 95%;
-    display:block;
-}
-#page-blocks-community-communitycourse .hubdescriptiontext {margin-left:160px;display:block;}
-#page-blocks-community-communitycourse .hubadditionaldesc {
-    color: #666666;
-    font-size: 90%;
-    display:block;
-}
+#page-blocks-community-communitycourse .hubtitlelink {color: #999; }
+#page-blocks-community-communitycourse .hubsmalllogo {padding-left: 3px; padding-right: 7px; float: left; }
+#page-blocks-community-communitycourse .hubtext {display: block; width: 68%; padding-left: 165px;}
+#page-blocks-community-communitycourse .hubimgandtext {display:table;}
+#page-blocks-community-communitycourse .hubimage {float: left; display: block; width: 100px;}
+#page-blocks-community-communitycourse .hubdescriptiontext {}
+#page-blocks-community-communitycourse .hubstats {padding-top: 10px}
+#page-blocks-community-communitycourse .hubstats .iconhelp {float: left; padding-right: 3px;}
+#page-blocks-community-communitycourse .hubadditionaldesc {color: #666666; font-size: 90%; display:block;}
 #page-blocks-community-communitycourse .hubscreenshot {margin-right: 10px;}
-#page-blocks-community-communitycourse .hubnottrusted {margin-left: 6px;}
-#page-blocks-community-communitycourse .hubtrusted {display:inline;margin-left: 6px;}
+#page-blocks-community-communitycourse .hubnottrusted {}
+#page-blocks-community-communitycourse .hubtrusted {display:inline;}
 #page-blocks-community-communitycourse .hubnottrusted {}
 #page-blocks-community-communitycourse .trustedtr {background-color: #ffe1c3;}
 #page-blocks-community-communitycourse .prioritisetr {background-color: #ffd4ff;}
 #page-blocks-community-communitycourse .blockdescription {font-size: 80%; color: #555555;}
-#page-blocks-community-communitycourse .trusted {
-    font-size: 90%;
-    color: #006633;
-    font-weight: normal;
-    font-style: italic;
-}
+#page-blocks-community-communitycourse .trusted {font-size: 90%; color: #006633; font-weight: normal; font-style: italic;}
 
 /* COURSES RESULT */
 #page-blocks-community-communitycourse .additionaldesc {font-size: 80%; color: #8B8989;}
index 46c0479..d555668 100644 (file)
@@ -906,7 +906,7 @@ M.core_dock.genericblock.prototype = {
         }
 
         // Must set the image src seperatly of we get an error with XML strict headers
-        var movetoimg = Y.Node.create('<img alt="'+M.str.block.undockitem+'" title="'+M.str.block.undockitem+'" />');
+        var movetoimg = Y.Node.create('<img alt="'+M.str.block.undockitem+'" title="'+M.util.get_string('undockblock', 'block', blocktitle.innerHTML)+'" />');
         var icon = 't/dock_to_block';
         if (right_to_left()) {
             icon = 't/dock_to_block_rtl';
index 884c909..db094c9 100644 (file)
@@ -43,8 +43,18 @@ class block_glossary_random extends block_base {
                 $this->instance_config_commit();
             }
 
+            // Get glossary instance, if not found then return without error, as this will be handled in get_content.
+            if (!$glossary = $DB->get_record('glossary', array('id' => $this->config->glossary))) {
+                return false;
+            }
+
+            $this->config->globalglossary = $glossary->globalglossary;
+
+            // Save course id in config, so we can get correct course module.
+            $this->config->courseid = $glossary->course;
+
             // Get module and context, to be able to rewrite urls
-            if (! $cm = get_coursemodule_from_instance("glossary", $this->config->glossary, $this->course->id)) {
+            if (! $cm = get_coursemodule_from_instance('glossary', $glossary->id, $this->config->courseid)) {
                 return false;
             }
             $glossaryctx = context_module::instance($cm->id);
@@ -144,13 +154,22 @@ class block_glossary_random extends block_base {
         }
 
         require_once($CFG->dirroot.'/course/lib.php');
-        $course = $this->page->course;
-        $modinfo = get_fast_modinfo($course);
-        $glossaryid = $this->config->glossary;
 
-        if (!isset($modinfo->instances['glossary'][$glossaryid])) {
-            // we can get here if the glossary has been deleted, so
-            // unconfigure the glossary from the block..
+        // If $this->config->globalglossary is not set then get glossary info from db.
+        if (!isset($this->config->globalglossary)) {
+            if (!$glossary = $DB->get_record('glossary', array('id' => $this->config->glossary))) {
+                return '';
+            } else {
+                $this->config->courseid = $glossary->course;
+                $this->config->globalglossary = $glossary->globalglossary;
+                $this->instance_config_commit();
+            }
+        }
+
+        $modinfo = get_fast_modinfo($this->config->courseid);
+        // If deleted glossary or non-global glossary on different course page, then reset.
+        if (!isset($modinfo->instances['glossary'][$this->config->glossary])
+                || ((empty($this->config->globalglossary) && ($this->config->courseid != $this->page->course->id)))) {
             $this->config->glossary = 0;
             $this->config->cache = '';
             $this->instance_config_commit();
@@ -161,8 +180,7 @@ class block_glossary_random extends block_base {
             return $this->content;
         }
 
-        $cm = $modinfo->instances['glossary'][$glossaryid];
-
+        $cm = $modinfo->instances['glossary'][$this->config->glossary];
         if (!has_capability('mod/glossary:view', context_module::instance($cm->id))) {
             return '';
         }
@@ -176,12 +194,10 @@ class block_glossary_random extends block_base {
         }
 
         $this->content = new stdClass();
-        $this->content->text = $this->config->cache;
-
-        // place link to glossary in the footer if the glossary is visible
 
-        //Obtain the visible property from the instance
-        if ($cm->uservisible) {
+        // Show glossary if visible and place links in footer.
+        if ($cm->visible) {
+            $this->content->text = $this->config->cache;
             if (has_capability('mod/glossary:write', context_module::instance($cm->id))) {
                 $this->content->footer = '<a href="'.$CFG->wwwroot.'/mod/glossary/edit.php?cmid='.$cm->id
                 .'" title="'.$this->config->addentry.'">'.$this->config->addentry.'</a><br />';
@@ -192,7 +208,7 @@ class block_glossary_random extends block_base {
             $this->content->footer .= '<a href="'.$CFG->wwwroot.'/mod/glossary/view.php?id='.$cm->id
                 .'" title="'.$this->config->viewglossary.'">'.$this->config->viewglossary.'</a>';
 
-        // otherwise just place some text, no link
+        // Otherwise just place some text, no link.
         } else {
             $this->content->footer = $this->config->invisible;
         }
index d9c613c..24b2df1 100644 (file)
@@ -41,7 +41,7 @@ class block_glossary_random_edit_form extends block_edit_form {
         $mform->setType('config_title', PARAM_TEXT);
 
         // Select glossaries to put in dropdown box ...
-        $glossaries = $DB->get_records_menu('glossary', array('course' => $this->block->course->id), 'name', 'id,name');
+        $glossaries = $DB->get_records_select_menu('glossary', 'course = ? OR globalglossary = ?', array($this->block->course->id, 1), 'name', 'id,name');
         foreach($glossaries as $key => $value) {
             $glossaries[$key] = strip_tags(format_string($value, true));
         }
index a5d21fa..67280cc 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012112902;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2013020400;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2012112900;        // Requires this Moodle version
 $plugin->component = 'block_glossary_random'; // Full name of the plugin (used for diagnostics)
index d4a491f..6373a01 100644 (file)
@@ -1,46 +1,46 @@
-<?php\r
-\r
-/**\r
- * Provides support for the conversion of moodle1 backup to the moodle2 format\r
- *\r
- * @package    block_html\r
- * @copyright  2012 Paul Nicholls\r
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r
- */\r
-\r
-defined('MOODLE_INTERNAL') || die();\r
-\r
-/**\r
- * Block conversion handler for html\r
- */\r
-class moodle1_block_html_handler extends moodle1_block_handler {\r
-    private $fileman = null;\r
-    protected function convert_configdata(array $olddata) {\r
-        $instanceid = $olddata['id'];\r
-        $contextid  = $this->converter->get_contextid(CONTEXT_BLOCK, $olddata['id']);\r
-        $configdata = unserialize(base64_decode($olddata['configdata']));\r
-\r
-        // get a fresh new file manager for this instance\r
-        $this->fileman = $this->converter->get_file_manager($contextid, 'block_html');\r
-\r
-        // convert course files embedded in the block content\r
-        $this->fileman->filearea = 'content';\r
-        $this->fileman->itemid   = 0;\r
-        $configdata->text = moodle1_converter::migrate_referenced_files($configdata->text, $this->fileman);\r
-        $configdata->format = FORMAT_HTML;\r
-\r
-        return base64_encode(serialize($configdata));\r
-    }\r
-\r
-    protected function write_inforef_xml($newdata, $data) {\r
-        $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/inforef.xml");\r
-        $this->xmlwriter->begin_tag('inforef');\r
-        $this->xmlwriter->begin_tag('fileref');\r
-        foreach ($this->fileman->get_fileids() as $fileid) {\r
-            $this->write_xml('file', array('id' => $fileid));\r
-        }\r
-        $this->xmlwriter->end_tag('fileref');\r
-        $this->xmlwriter->end_tag('inforef');\r
-        $this->close_xml_writer();\r
-    }\r
-}
\ No newline at end of file
+<?php
+
+/**
+ * Provides support for the conversion of moodle1 backup to the moodle2 format
+ *
+ * @package    block_html
+ * @copyright  2012 Paul Nicholls
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Block conversion handler for html
+ */
+class moodle1_block_html_handler extends moodle1_block_handler {
+    private $fileman = null;
+    protected function convert_configdata(array $olddata) {
+        $instanceid = $olddata['id'];
+        $contextid  = $this->converter->get_contextid(CONTEXT_BLOCK, $olddata['id']);
+        $configdata = unserialize(base64_decode($olddata['configdata']));
+
+        // get a fresh new file manager for this instance
+        $this->fileman = $this->converter->get_file_manager($contextid, 'block_html');
+
+        // convert course files embedded in the block content
+        $this->fileman->filearea = 'content';
+        $this->fileman->itemid   = 0;
+        $configdata->text = moodle1_converter::migrate_referenced_files($configdata->text, $this->fileman);
+        $configdata->format = FORMAT_HTML;
+
+        return base64_encode(serialize($configdata));
+    }
+
+    protected function write_inforef_xml($newdata, $data) {
+        $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/inforef.xml");
+        $this->xmlwriter->begin_tag('inforef');
+        $this->xmlwriter->begin_tag('fileref');
+        foreach ($this->fileman->get_fileids() as $fileid) {
+            $this->write_xml('file', array('id' => $fileid));
+        }
+        $this->xmlwriter->end_tag('fileref');
+        $this->xmlwriter->end_tag('inforef');
+        $this->close_xml_writer();
+    }
+}
index 70d6054..7fdea6b 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012112900;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2013020800;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2012112900;        // Requires this Moodle version
 $plugin->component = 'block_navigation'; // Full name of the plugin (used for diagnostics)
index 62f417e..d2631dd 100644 (file)
@@ -86,6 +86,8 @@ var NODETYPE = {
     SYSTEM : 1,
     /** @type int Course category = 10 */
     CATEGORY : 10,
+    /** @type int MYCATEGORY = 11 */
+    MYCATEGORY : 11,
     /** @type int Course = 20 */
     COURSE : 20,
     /** @type int Course section = 30 */
@@ -454,7 +456,8 @@ BRANCH.prototype = {
                         this.addChild(object.children[i]);
                     }
                 }
-                if ((this.get('type') == NODETYPE.CATEGORY || this.get('type') == NODETYPE.ROOTNODE) && coursecount >= M.block_navigation.courselimit) {
+                if ((this.get('type') == NODETYPE.CATEGORY || this.get('type') == NODETYPE.ROOTNODE || this.get('type') == NODETYPE.MYCATEGORY)
+                    && coursecount >= M.block_navigation.courselimit) {
                     this.addViewAllCoursesChild(this);
                 }
                 this.get('tree').toggleExpansion({target:this.node});
@@ -486,7 +489,8 @@ BRANCH.prototype = {
                     branch.addChild(children[i]);
                 }
             }
-            if (branch.get('type') == NODETYPE.CATEGORY && count >= M.block_navigation.courselimit) {
+            if ((branch.get('type') == NODETYPE.CATEGORY || branch.get('type') == NODETYPE.MYCATEGORY)
+                && count >= M.block_navigation.courselimit) {
                 this.addViewAllCoursesChild(branch);
             }
         }
index 846ab98..b62eefb 100644 (file)
@@ -113,7 +113,8 @@ class block_recent_activity extends block_base {
      * Returns list of recent changes in course structure
      *
      * It includes adding, editing or deleting of the resources or activities
-     * Excludes changes on labels, and also if activity was both added and deleted
+     * Excludes changes on modules without a view link (i.e. labels), and also
+     * if activity was both added and deleted
      *
      * @return array array of changes. Each element is an array containing attributes:
      *    'action' - one of: 'add mod', 'update mod', 'delete mod'
@@ -135,13 +136,6 @@ class block_recent_activity extends block_base {
             foreach ($logs as $key => $log) {
                 $info = explode(' ', $log->info);
 
-                // note: in most cases I replaced hardcoding of label with use of
-                // $cm->has_view() but it was not possible to do this here because
-                // we don't necessarily have the $cm for it
-                if ($info[0] == 'label') {     // Labels are ignored in recent activity
-                    continue;
-                }
-
                 if (count($info) != 2) {
                     debugging("Incorrect log entry info: id = ".$log->id, DEBUG_DEVELOPER);
                     continue;
@@ -151,6 +145,11 @@ class block_recent_activity extends block_base {
                 $instanceid = $info[1];
 
                 if ($log->action == 'delete mod') {
+                    if (plugin_supports('mod', $modname, FEATURE_NO_VIEW_LINK, false)) {
+                        // we should better call cm_info::has_view() because it can be
+                        // dynamic. But there is no instance of cm_info now
+                        continue;
+                    }
                     // unfortunately we do not know if the mod was visible
                     if (!array_key_exists($log->info, $newgones)) {
                         $changelist[$log->info] = array('action' => $log->action,
@@ -168,7 +167,7 @@ class block_recent_activity extends block_base {
                         continue;
                     }
                     $cm = $modinfo->instances[$modname][$instanceid];
-                    if ($cm->uservisible && empty($changelist[$log->info])) {
+                    if ($cm->has_view() && $cm->uservisible && empty($changelist[$log->info])) {
                         $changelist[$log->info] = array('action' => $log->action, 'module' => $cm);
                     }
                 }
index 95da634..d698365 100644 (file)
@@ -1,34 +1,34 @@
-<?php\r
-\r
-/**\r
- * Provides support for the conversion of moodle1 backup to the moodle2 format\r
- *\r
- * @package    block_rss_client\r
- * @copyright  2012 Paul Nicholls\r
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r
- */\r
-\r
-defined('MOODLE_INTERNAL') || die();\r
-\r
-/**\r
- * Block conversion handler for rss_client\r
- */\r
-class moodle1_block_rss_client_handler extends moodle1_block_handler {\r
-    public function process_block(array $data) {\r
-        parent::process_block($data);\r
-        $instanceid = $data['id'];\r
-        $contextid = $this->converter->get_contextid(CONTEXT_BLOCK, $data['id']);\r
-\r
-        // Moodle 1.9 backups do not include sufficient data to restore feeds, so we need an empty shell rss_client.xml\r
-        // for the restore process to find\r
-        $this->open_xml_writer("course/blocks/{$data['name']}_{$instanceid}/rss_client.xml");\r
-        $this->xmlwriter->begin_tag('block', array('id' => $instanceid, 'contextid' => $contextid, 'blockname' => 'rss_client'));\r
-        $this->xmlwriter->begin_tag('rss_client', array('id' => $instanceid));\r
-        $this->xmlwriter->full_tag('feeds', '');\r
-        $this->xmlwriter->end_tag('rss_client');\r
-        $this->xmlwriter->end_tag('block');\r
-        $this->close_xml_writer();\r
-\r
-        return $data;\r
-    }\r
-}
\ No newline at end of file
+<?php
+
+/**
+ * Provides support for the conversion of moodle1 backup to the moodle2 format
+ *
+ * @package    block_rss_client
+ * @copyright  2012 Paul Nicholls
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Block conversion handler for rss_client
+ */
+class moodle1_block_rss_client_handler extends moodle1_block_handler {
+    public function process_block(array $data) {
+        parent::process_block($data);
+        $instanceid = $data['id'];
+        $contextid = $this->converter->get_contextid(CONTEXT_BLOCK, $data['id']);
+
+        // Moodle 1.9 backups do not include sufficient data to restore feeds, so we need an empty shell rss_client.xml
+        // for the restore process to find
+        $this->open_xml_writer("course/blocks/{$data['name']}_{$instanceid}/rss_client.xml");
+        $this->xmlwriter->begin_tag('block', array('id' => $instanceid, 'contextid' => $contextid, 'blockname' => 'rss_client'));
+        $this->xmlwriter->begin_tag('rss_client', array('id' => $instanceid));
+        $this->xmlwriter->full_tag('feeds', '');
+        $this->xmlwriter->end_tag('rss_client');
+        $this->xmlwriter->end_tag('block');
+        $this->close_xml_writer();
+
+        return $data;
+    }
+}
index 3563843..2ac37aa 100644 (file)
@@ -25,6 +25,6 @@
  */
 
 $string['enabledock'] = 'Allow the user to dock this block';
-$string['pluginname'] = 'Settings';
-$string['settings:addinstance'] = 'Add a new settings block';
-$string['settings:myaddinstance'] = 'Add a new settings block to the My Moodle page';
+$string['pluginname'] = 'Administration';
+$string['settings:addinstance'] = 'Add a new administration block';
+$string['settings:myaddinstance'] = 'Add a new administration block to the My Moodle page';
index 919fed1..42ab6e3 100644 (file)
@@ -87,8 +87,8 @@ class blog_edit_form extends moodleform {
                 }
 
                 if (has_capability('moodle/blog:associatecourse', $context)) {
-                    $mform->addElement('header', 'assochdr', get_string('associations', 'blog'));\r
-                    $mform->addElement('advcheckbox', 'courseassoc', get_string('associatewithcourse', 'blog', $a), null, null, array(0, $contextid));\r
+                    $mform->addElement('header', 'assochdr', get_string('associations', 'blog'));
+                    $mform->addElement('advcheckbox', 'courseassoc', get_string('associatewithcourse', 'blog', $a), null, null, array(0, $contextid));
                     $mform->setDefault('courseassoc', $contextid);
                 }
 
index fee8aea..232aa46 100644 (file)
@@ -71,6 +71,12 @@ class cache_config {
      */
     protected $configlocks = array();
 
+    /**
+     * The site identifier used when the cache config was last saved.
+     * @var string
+     */
+    protected $siteidentifier = null;
+
     /**
      * Please use cache_config::instance to get an instance of the cache config that is ready to be used.
      */
@@ -139,6 +145,12 @@ class cache_config {
         $this->configdefinitionmappings = array();
         $this->configlockmappings = array();
 
+        $siteidentifier = 'unknown';
+        if (array_key_exists('siteidentifier', $configuration)) {
+            $siteidentifier = $configuration['siteidentifier'];
+        }
+        $this->siteidentifier = $siteidentifier;
+
         // Filter the lock instances.
         $defaultlock = null;
         foreach ($configuration['locks'] as $conf) {
@@ -271,6 +283,14 @@ class cache_config {
         return true;
     }
 
+    /**
+     * Returns the site identifier used by the cache API.
+     * @return string
+     */
+    public function get_site_identifier() {
+        return $this->siteidentifier;
+    }
+
     /**
      * Includes the configuration file and makes sure it contains the expected bits.
      *
index e278bbb..8c5a16a 100644 (file)
@@ -723,7 +723,8 @@ class cache_definition {
      */
     public function generate_single_key_prefix() {
         if ($this->keyprefixsingle === null) {
-            $this->keyprefixsingle = $this->mode.'/'.$this->mode;
+            $this->keyprefixsingle = $this->mode.'/'.$this->component.'/'.$this->area;
+            $this->keyprefixsingle .= '/'.$this->get_cache_identifier();
             $identifiers = $this->get_identifiers();
             if ($identifiers) {
                 foreach ($identifiers as $key => $value) {
@@ -746,6 +747,7 @@ class cache_definition {
                 'mode' => $this->mode,
                 'component' => $this->component,
                 'area' => $this->area,
+                'siteidentifier' => $this->get_cache_identifier()
             );
             if (!empty($this->identifiers)) {
                 $identifiers = array();
@@ -785,4 +787,13 @@ class cache_definition {
     public function get_invalidation_events() {
         return $this->invalidationevents;
     }
+
+    /**
+     * Returns a cache identification string.
+     *
+     * @return string A string to be used as part of keys.
+     */
+    protected function get_cache_identifier() {
+        return cache_helper::get_site_identifier();
+    }
 }
\ No newline at end of file
index f3d520c..24c7c1e 100644 (file)
@@ -203,6 +203,7 @@ class cache_factory {
         }
         // Get the class. Note this is a late static binding so we need to use get_called_class.
         $definition = cache_definition::load_adhoc($mode, $component, $area, $options);
+        $config = $this->create_config_instance();
         $definition->set_identifiers($identifiers);
         $cache = $this->create_cache($definition, $identifiers);
         if ($definition->should_be_persistent()) {
index 4439a56..a8f3a26 100644 (file)
@@ -54,6 +54,13 @@ class cache_helper {
      */
     protected static $instance;
 
+    /**
+     * The site identifier used by the cache.
+     * Set the first time get_site_identifier is called.
+     * @var string
+     */
+    protected static $siteidentifier = null;
+
     /**
      * Returns true if the cache API can be initialised before Moodle has finished initialising itself.
      *
@@ -489,11 +496,52 @@ class cache_helper {
      */
     public static function update_definitions($coreonly = false) {
         global $CFG;
-        // Include locallib
+        // Include locallib.
         require_once($CFG->dirroot.'/cache/locallib.php');
         // First update definitions
         cache_config_writer::update_definitions($coreonly);
         // Second reset anything we have already initialised to ensure we're all up to date.
         cache_factory::reset();
     }
-}
\ No newline at end of file
+
+    /**
+     * Update the site identifier stored by the cache API.
+     *
+     * @param string $siteidentifier
+     */
+    public static function update_site_identifier($siteidentifier) {
+        global $CFG;
+        // Include locallib.
+        require_once($CFG->dirroot.'/cache/locallib.php');
+        $factory = cache_factory::instance();
+        $factory->updating_started();
+        $config = $factory->create_config_instance(true);
+        $config->update_site_identifier($siteidentifier);
+        $factory->updating_finished();
+        cache_factory::reset();
+    }
+
+    /**
+     * Returns the site identifier.
+     *
+     * @return string
+     */
+    public static function get_site_identifier() {
+        if (is_null(self::$siteidentifier)) {
+            $factory = cache_factory::instance();
+            $config = $factory->create_config_instance();
+            self::$siteidentifier = $config->get_site_identifier();
+        }
+        return self::$siteidentifier;
+    }
+
+    /**
+     * Returns the site version.
+     *
+     * @return string
+     */
+    public static function get_site_version() {
+        global $CFG;
+        return (string)$CFG->version;
+    }
+}
index ce926a5..4d6d745 100644 (file)
@@ -119,6 +119,7 @@ class cache_config_writer extends cache_config {
      */
     protected function generate_configuration_array() {
         $configuration = array();
+        $configuration['siteidentifier'] = $this->siteidentifier;
         $configuration['stores'] = $this->configstores;
         $configuration['modemappings'] = $this->configmodemappings;
         $configuration['definitions'] = $this->configdefinitions;
@@ -524,6 +525,15 @@ class cache_config_writer extends cache_config {
         $this->config_save();
     }
 
+    /**
+     * Update the site identifier stored by the cache API.
+     *
+     * @param string $siteidentifier
+     */
+    public function update_site_identifier($siteidentifier) {
+        $this->siteidentifier = md5((string)$siteidentifier);
+        $this->config_save();
+    }
 }
 
 /**
@@ -1002,4 +1012,4 @@ abstract class cache_administration_helper extends cache_helper {
         }
         return $locks;
     }
-}
\ No newline at end of file
+}
index 98d9c5b..545986a 100644 (file)
@@ -553,6 +553,8 @@ class cache_phpunit_tests extends advanced_testcase {
             'mode' => cache_store::MODE_APPLICATION,
             'component' => 'phpunit',
             'area' => 'eventinvalidationtest',
+            'simplekeys' => true,
+            'simpledata' => true,
             'invalidationevents' => array(
                 'crazyevent'
             )
@@ -567,13 +569,16 @@ class cache_phpunit_tests extends advanced_testcase {
 
         // OK data added, data invalidated, and invalidation time has been set.
         // Now we need to manually add back the data and adjust the invalidation time.
-        $timefile = $CFG->dataroot.'/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/a65/a65b1dc524cf6e03c1795197c84d5231eb229b86.cache';
+        $hash = md5(cache_store::MODE_APPLICATION.'/phpunit/eventinvalidationtest/'.$CFG->wwwroot.'phpunit');
+        $timefile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/las/lastinvalidation-$hash.cache";
+        // Make sure the file is correct.
+        $this->assertTrue(file_exists($timefile));
         $timecont = serialize(cache::now() - 60); // Back 60sec in the past to force it to re-invalidate.
         make_writable_directory(dirname($timefile));
         file_put_contents($timefile, $timecont);
         $this->assertTrue(file_exists($timefile));
 
-        $datafile = $CFG->dataroot.'/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/626/626e9c7a45febd98f064c2b383de8d9d4ebbde7b.cache';
+        $datafile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/tes/testkey1-$hash.cache";
         $datacont = serialize("test data 1");
         make_writable_directory(dirname($datafile));
         file_put_contents($datafile, $datacont);
@@ -586,6 +591,8 @@ class cache_phpunit_tests extends advanced_testcase {
             'mode' => cache_store::MODE_APPLICATION,
             'component' => 'phpunit',
             'area' => 'eventinvalidationtest',
+            'simplekeys' => true,
+            'simpledata' => true,
         ));
         $cache = cache::make('phpunit', 'eventinvalidationtest');
         $this->assertEquals('test data 1', $cache->get('testkey1'));
@@ -597,6 +604,8 @@ class cache_phpunit_tests extends advanced_testcase {
             'mode' => cache_store::MODE_APPLICATION,
             'component' => 'phpunit',
             'area' => 'eventinvalidationtest',
+            'simplekeys' => true,
+            'simpledata' => true,
             'invalidationevents' => array(
                 'crazyevent'
             )
index d5e5297..471521b 100644 (file)
@@ -102,6 +102,16 @@ class cache_config_phpunittest extends cache_config_writer {
             'sort' => (int)$sort
         );
     }
+
+    /**
+     * Overrides the default site identifier used by the Cache API so that we can be sure of what it is.
+     *
+     * @return string
+     */
+    public function get_site_identifier() {
+        global $CFG;
+        return $CFG->wwwroot.'phpunit';
+    }
 }
 
 /**
index 91b99ca..c1fe37c 100644 (file)
@@ -22,6 +22,7 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 define('AJAX_SCRIPT', true);
+define('NO_DEBUG_DISPLAY', true);
 
 require_once('../config.php');
 require_once($CFG->dirroot . '/comment/lib.php');
@@ -35,6 +36,10 @@ if (empty($CFG->usecomments)) {
 
 list($context, $course, $cm) = get_context_info_array($contextid);
 
+if ( $contextid == SYSCONTEXTID ) {
+    $course = $SITE;
+}
+
 $PAGE->set_url('/comment/comment_ajax.php');
 
 // Allow anonymous user to view comments providing forcelogin now enabled
index c146119..9ca0a41 100644 (file)
@@ -526,10 +526,10 @@ class comment {
             $c->content     = $u->ccontent;
             $c->format      = $u->cformat;
             $c->timecreated = $u->ctimecreated;
+            $c->strftimeformat = get_string('strftimerecent', 'langconfig');
             $url = new moodle_url('/user/view.php', array('id'=>$u->id, 'course'=>$this->courseid));
             $c->profileurl = $url->out(false);
             $c->fullname = fullname($u);
-            $c->time = userdate($c->timecreated, get_string('strftimerecent', 'langconfig'));
             $c->content = format_text($c->content, $c->format, $formatoptions);
             $c->avatar = $OUTPUT->user_picture($u, array('size'=>18));
 
@@ -628,12 +628,27 @@ class comment {
         $cmt_id = $DB->insert_record('comments', $newcmt);
         if (!empty($cmt_id)) {
             $newcmt->id = $cmt_id;
-            $newcmt->time = userdate($now, get_string('strftimerecent', 'langconfig'));
+            $newcmt->strftimeformat = get_string('strftimerecent', 'langconfig');
             $newcmt->fullname = fullname($USER);
             $url = new moodle_url('/user/view.php', array('id' => $USER->id, 'course' => $this->courseid));
             $newcmt->profileurl = $url->out();
             $newcmt->content = format_text($newcmt->content, $format, array('overflowdiv'=>true));
             $newcmt->avatar = $OUTPUT->user_picture($USER, array('size'=>16));
+
+            $commentlist = array($newcmt);
+
+            if (!empty($this->plugintype)) {
+                // Call the display callback to allow the plugin to format the newly added comment.
+                $commentlist = plugin_callback($this->plugintype,
+                                               $this->pluginname,
+                                               'comment',
+                                               'display',
+                                               array($commentlist, $this->comment_param),
+                                               $commentlist);
+                $newcmt = $commentlist[0];
+            }
+            $newcmt->time = userdate($newcmt->timecreated, $newcmt->strftimeformat);
+
             return $newcmt;
         } else {
             throw new comment_exception('dbupdatefailed');
@@ -793,7 +808,7 @@ class comment {
         $replacements[] = $cmt->avatar;
         $replacements[] = html_writer::link($cmt->profileurl, $cmt->fullname);
         $replacements[] = $cmt->content;
-        $replacements[] = userdate($cmt->timecreated, get_string('strftimerecent', 'langconfig'));
+        $replacements[] = userdate($cmt->timecreated, $cmt->strftimeformat);
 
         // use html template to format a single comment.
         return str_replace($patterns, $replacements, $this->template);
index 88a2c5b..40f8577 100644 (file)
@@ -63,28 +63,7 @@ $CFG->dboptions = array(
 
 
 //=========================================================================
-// 2. SECRET PASSWORD SALT
-//=========================================================================
-// User password salt is very important security feature, it is created
-// automatically in installer, you have to uncomment and modify value
-// on the next line if you are creating config.php manually.
-//
-// $CFG->passwordsaltmain = 'a_very_long_random_string_of_characters#@6&*1';
-//
-// After changing the main salt you have to copy old value into one
-// of the following settings - this allows migration to the new salt
-// during the next login of each user.
-//
-// $CFG->passwordsaltalt1 = '';
-// $CFG->passwordsaltalt2 = '';
-// $CFG->passwordsaltalt3 = '';
-// ....
-// $CFG->passwordsaltalt19 = '';
-// $CFG->passwordsaltalt20 = '';
-
-
-//=========================================================================
-// 3. WEB SITE LOCATION
+// 2. WEB SITE LOCATION
 //=========================================================================
 // Now you need to tell Moodle where it is located. Specify the full
 // web address to where moodle has been installed.  If your web site
@@ -98,7 +77,7 @@ $CFG->wwwroot   = 'http://example.com/moodle';
 
 
 //=========================================================================
-// 4. DATA FILES LOCATION
+// 3. DATA FILES LOCATION
 //=========================================================================
 // Now you need a place where Moodle can save uploaded files.  This
 // directory should be readable AND WRITEABLE by the web server user
@@ -114,7 +93,7 @@ $CFG->dataroot  = '/home/example/moodledata';
 
 
 //=========================================================================
-// 5. DATA FILES PERMISSIONS
+// 4. DATA FILES PERMISSIONS
 //=========================================================================
 // The following parameter sets the permissions of new directories
 // created by Moodle within the data directory.  The format is in
@@ -128,7 +107,7 @@ $CFG->directorypermissions = 02777;
 
 
 //=========================================================================
-// 6. DIRECTORY LOCATION  (most people can just ignore this setting)
+// 5. DIRECTORY LOCATION  (most people can just ignore this setting)
 //=========================================================================
 // A very few webhosts use /admin as a special URL for you to access a
 // control panel or something.  Unfortunately this conflicts with the
@@ -140,7 +119,7 @@ $CFG->admin = 'admin';
 
 
 //=========================================================================
-// 7. OTHER MISCELLANEOUS SETTINGS (ignore these for new installations)
+// 6. OTHER MISCELLANEOUS SETTINGS (ignore these for new installations)
 //=========================================================================
 //
 // These are additional tweaks for which no GUI exists in Moodle yet.
@@ -374,7 +353,8 @@ $CFG->admin = 'admin';
 //
 //     $CFG->themedir = '/location/of/extra/themes';
 //
-// It is possible to specify different cache and temp directories, use local fast filesystem.
+// It is possible to specify different cache and temp directories, use local fast filesystem
+// for normal web servers. Server clusters MUST use shared filesystem for cachedir!
 // The directories must not be accessible via web.
 //
 //     $CFG->tempdir = '/var/www/moodle/temp';
@@ -471,7 +451,7 @@ $CFG->admin = 'admin';
 //      $CFG->svgicons = false;
 //
 //=========================================================================
-// 8. SETTINGS FOR DEVELOPMENT SERVERS - not intended for production use!!!
+// 7. SETTINGS FOR DEVELOPMENT SERVERS - not intended for production use!!!
 //=========================================================================
 //
 // Force a debugging mode regardless the settings in the site administration
@@ -512,7 +492,7 @@ $CFG->admin = 'admin';
 // $CFG->showcrondebugging = true;
 //
 //=========================================================================
-// 9. FORCED SETTINGS
+// 8. FORCED SETTINGS
 //=========================================================================
 // It is possible to specify normal admin settings here, the point is that
 // they can not be changed through the standard admin settings pages any more.
@@ -527,12 +507,35 @@ $CFG->admin = 'admin';
 //                                        'otherplugin' => array('mysetting' => 'myvalue', 'thesetting' => 'thevalue'));
 //
 //=========================================================================
-// 10. PHPUNIT SUPPORT
+// 9. PHPUNIT SUPPORT
 //=========================================================================
 // $CFG->phpunit_prefix = 'phpu_';
 // $CFG->phpunit_dataroot = '/home/example/phpu_moodledata';
 // $CFG->phpunit_directorypermissions = 02777; // optional
 //
+//
+//=========================================================================
+// 10. SECRET PASSWORD SALT
+//=========================================================================
+// A single site-wide password salt is no longer required *unless* you are
+// upgrading an older version of Moodle (prior to 2.5), or if you are using
+// a PHP version below 5.3.7. If upgrading, keep any values from your old
+// config.php file. If you are using PHP < 5.3.7 set to a long random string
+// below:
+//
+// $CFG->passwordsaltmain = 'a_very_long_random_string_of_characters#@6&*1';
+//
+// You may also have some alternative salts to allow migration from previously
+// used salts.
+//
+// $CFG->passwordsaltalt1 = '';
+// $CFG->passwordsaltalt2 = '';
+// $CFG->passwordsaltalt3 = '';
+// ....
+// $CFG->passwordsaltalt19 = '';
+// $CFG->passwordsaltalt20 = '';
+//
+//
 //=========================================================================
 // 11. BEHAT SUPPORT
 //=========================================================================
index 1ee8395..291583c 100644 (file)
@@ -435,7 +435,7 @@ M.course_dndupload = {
      * @param section the DOM element reperesenting the course section
      * @return DOM element containing the new item
      */
-    add_resource_element: function(name, section) {
+    add_resource_element: function(name, section, module) {
         var modsel = this.get_mods_element(section);
 
         var resel = {
@@ -451,7 +451,7 @@ M.course_dndupload = {
             progress: document.createElement('span')
         };
 
-        resel.li.className = 'activity resource modtype_resource';
+        resel.li.className = 'activity ' + module + ' modtype_' + module;
 
         resel.indentdiv.className = 'mod-indent';
         resel.li.appendChild(resel.indentdiv);
@@ -549,7 +549,7 @@ M.course_dndupload = {
         var extension = '';
         var dotpos = file.name.lastIndexOf('.');
         if (dotpos != -1) {
-            extension = file.name.substr(dotpos+1, file.name.length);
+            extension = file.name.substr(dotpos+1, file.name.length).toLowerCase();
         }
 
         for (var i=0; i<filehandlers.length; i++) {
@@ -707,7 +707,7 @@ M.course_dndupload = {
         }
 
         // Add the file to the display
-        var resel = this.add_resource_element(file.name, section);
+        var resel = this.add_resource_element(file.name, section, module);
 
         // Update the progress bar as the file is uploaded
         xhr.upload.addEventListener('progress', function(e) {
@@ -726,30 +726,37 @@ M.course_dndupload = {
                     if (result) {
                         if (result.error == 0) {
                             // All OK - update the dummy element
-                            resel.icon.src = result.icon;
-                            resel.a.href = result.link;
-                            resel.namespan.innerHTML = result.name;
-                            if (!parseInt(result.visible, 10)) {
-                                resel.a.className = 'dimmed';
-                            }
-
-                            if (result.groupingname) {
-                                resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
+                            if (result.content) {
+                                // A label
+                                resel.indentdiv.innerHTML = '<div class="activityinstance" ></div>' + result.content + result.commands;
                             } else {
-                                resel.div.removeChild(resel.groupingspan);
+                                // Not a label
+                                resel.icon.src = result.icon;
+                                resel.a.href = result.link;
+                                resel.namespan.innerHTML = result.name;
+
+                                if (!parseInt(result.visible, 10)) {
+                                    resel.a.className = 'dimmed';
+                                }
+
+                                if (result.groupingname) {
+                                    resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
+                                } else {
+                                    resel.div.removeChild(resel.groupingspan);
+                                }
+
+                                resel.div.removeChild(resel.progressouter);
+                                resel.indentdiv.innerHTML += result.commands;
+                                if (result.onclick) {
+                                    resel.a.onclick = result.onclick;
+                                }
+                                if (self.Y.UA.gecko > 0) {
+                                    // Fix a Firefox bug which makes sites with a '~' in their wwwroot
+                                    // log the user out when clicking on the link (before refreshing the page).
+                                    resel.div.innerHTML = unescape(resel.div.innerHTML);
+                                }
                             }
-
-                            resel.div.removeChild(resel.progressouter);
                             resel.li.id = result.elementid;
-                            resel.indentdiv.innerHTML += result.commands;
-                            if (result.onclick) {
-                                resel.a.onclick = result.onclick;
-                            }
-                            if (self.Y.UA.gecko > 0) {
-                                // Fix a Firefox bug which makes sites with a '~' in their wwwroot
-                                // log the user out when clicking on the link (before refreshing the page).
-                                resel.div.innerHTML = unescape(resel.div.innerHTML);
-                            }
                             self.add_editing(result.elementid);
                         } else {
                             // Error - remove the dummy element
@@ -905,7 +912,7 @@ M.course_dndupload = {
         var self = this;
 
         // Add the item to the display
-        var resel = this.add_resource_element(name, section);
+        var resel = this.add_resource_element(name, section, module);
 
         // Wait for the AJAX call to complete, then update the
         // dummy element with the returned details
@@ -916,30 +923,37 @@ M.course_dndupload = {
                     if (result) {
                         if (result.error == 0) {
                             // All OK - update the dummy element
-                            resel.icon.src = result.icon;
-                            resel.a.href = result.link;
-                            resel.namespan.innerHTML = result.name;
-                            if (!parseInt(result.visible, 10)) {
-                                resel.a.className = 'dimmed';
-                            }
-
-                            if (result.groupingname) {
-                                resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
+                            if (result.content) {
+                                // A label
+                                resel.indentdiv.innerHTML = '<div class="activityinstance" ></div>' + result.content + result.commands;
                             } else {
-                                resel.div.removeChild(resel.groupingspan);
+                                // Not a label
+                                resel.icon.src = result.icon;
+                                resel.a.href = result.link;
+                                resel.namespan.innerHTML = result.name;
+
+                                if (!parseInt(result.visible, 10)) {
+                                    resel.a.className = 'dimmed';
+                                }
+
+                                if (result.groupingname) {
+                                    resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
+                                } else {
+                                    resel.div.removeChild(resel.groupingspan);
+                                }
+
+                                resel.div.removeChild(resel.progressouter);
+                                resel.div.innerHTML += result.commands;
+                                if (result.onclick) {
+                                    resel.a.onclick = result.onclick;
+                                }
+                                if (self.Y.UA.gecko > 0) {
+                                    // Fix a Firefox bug which makes sites with a '~' in their wwwroot
+                                    // log the user out when clicking on the link (before refreshing the page).
+                                    resel.div.innerHTML = unescape(resel.div.innerHTML);
+                                }
                             }
-
-                            resel.div.removeChild(resel.progressouter);
                             resel.li.id = result.elementid;
-                            resel.div.innerHTML += result.commands;
-                            if (result.onclick) {
-                                resel.a.onclick = result.onclick;
-                            }
-                            if (self.Y.UA.gecko > 0) {
-                                // Fix a Firefox bug which makes sites with a '~' in their wwwroot
-                                // log the user out when clicking on the link (before refreshing the page).
-                                resel.div.innerHTML = unescape(resel.div.innerHTML);
-                            }
                             self.add_editing(result.elementid, sectionnumber);
                         } else {
                             // Error - remove the dummy element
index 73c8a79..0cdd8bc 100644 (file)
@@ -678,7 +678,12 @@ class dndupload_ajax_processor {
         $resp->error = self::ERROR_OK;
         $resp->icon = $mod->get_icon_url()->out();
         $resp->name = $mod->name;
-        $resp->link = $mod->get_url()->out();
+        if ($mod->has_view()) {
+            $resp->link = $mod->get_url()->out();
+        } else {
+            $resp->link = null;
+        }
+        $resp->content = $mod->get_content();
         $resp->elementid = 'module-'.$mod->id;
         $actions = course_get_cm_edit_actions($mod, 0, $mod->sectionnum);
         $resp->commands = ' '. $courserenderer->course_section_cm_edit_actions($actions);
index 82af237..f353a4b 100644 (file)
@@ -336,6 +336,7 @@ class format_legacy extends format_base {
      * @return bool whether there were any changes to the options values
      */
     public function update_course_format_options($data, $oldcourse = null) {
+        global $DB;
         if ($oldcourse !== null) {
             $data = (array)$data;
             $oldcourse = (array)$oldcourse;
index 71e8797..172d1d9 100644 (file)
@@ -40,7 +40,7 @@ M.course.format.swap_sections = function(Y, node1, node2) {
     };
 
     var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.course.format.get_section_selector(Y));
-    // Swap menus
+    // Swap menus.
     sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
 }
 
@@ -59,7 +59,13 @@ M.course.format.process_sections = function(Y, sectionlist, response, sectionfro
     };
 
     if (response.action == 'move') {
-        // update titles in all affected sections
+        // If moving up swap around 'sectionfrom' and 'sectionto' so the that loop operates.
+        if (sectionfrom > sectionto) {
+            var temp = sectionto;
+            sectionto = sectionfrom;
+            sectionfrom = temp;
+        }
+        // Update titles in all affected sections.
         for (var i = sectionfrom; i <= sectionto; i++) {
             sectionlist.item(i).one('.'+CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
         }
index f26cafd..debe045 100644 (file)
@@ -271,6 +271,7 @@ class format_topics extends format_base {
      * @return bool whether there were any changes to the options values
      */
     public function update_course_format_options($data, $oldcourse = null) {
+        global $DB;
         if ($oldcourse !== null) {
             $data = (array)$data;
             $oldcourse = (array)$oldcourse;
index 28ec82a..6433702 100644 (file)
@@ -40,7 +40,7 @@ M.course.format.swap_sections = function(Y, node1, node2) {
     };
 
     var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.course.format.get_section_selector(Y));
-    // Swap menus
+    // Swap menus.
     sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
 }
 
@@ -59,7 +59,13 @@ M.course.format.process_sections = function(Y, sectionlist, response, sectionfro
     };
 
     if (response.action == 'move') {
-        // update titles in all affected sections
+        // If moving up swap around 'sectionfrom' and 'sectionto' so the that loop operates.
+        if (sectionfrom > sectionto) {
+            var temp = sectionto;
+            sectionto = sectionfrom;
+            sectionfrom = temp;
+        }
+        // Update titles in all affected sections.
         for (var i = sectionfrom; i <= sectionto; i++) {
             sectionlist.item(i).one('.'+CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
         }
index a5c1153..40686d4 100644 (file)
@@ -276,6 +276,7 @@ class format_weeks extends format_base {
      * @return bool whether there were any changes to the options values
      */
     public function update_course_format_options($data, $oldcourse = null) {
+        global $DB;
         if ($oldcourse !== null) {
             $data = (array)$data;
             $oldcourse = (array)$oldcourse;
index 241580f..6bc2895 100644 (file)
@@ -1920,14 +1920,14 @@ function print_course_search($value="", $return=false, $format="plain") {
         $output  = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
         $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
         $output .= '<label for="shortsearchbox">'.$strsearchcourses.': </label>';
-        $output .= '<input type="text" id="shortsearchbox" size="12" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
+        $output .= '<input type="text" id="shortsearchbox" size="12" name="search" value="'.s($value).'" />';
         $output .= '<input type="submit" value="'.get_string('go').'" />';
         $output .= '</fieldset></form>';
     } else if ($format == 'navbar') {
         $output  = '<form id="coursesearchnavbar" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
         $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
         $output .= '<label for="navsearchbox">'.$strsearchcourses.': </label>';
-        $output .= '<input type="text" id="navsearchbox" size="20" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
+        $output .= '<input type="text" id="navsearchbox" size="20" name="search" value="'.s($value).'" />';
         $output .= '<input type="submit" value="'.get_string('go').'" />';
         $output .= '</fieldset></form>';
     }
@@ -2193,15 +2193,22 @@ function course_delete_module($cmid) {
     if (file_exists($modlib)) {
         require_once($modlib);
     } else {
-        throw new moodle_exception("This module is missing mod/$modulename/lib.php", '', '',
-            null, 'failedtodeletemodulemissinglibfile');
+        throw new moodle_exception('cannotdeletemodulemissinglib', '', '', null,
+            "Cannot delete this module as the file mod/$modulename/lib.php is missing.");
     }
 
-    $deleteinstancefunction = $modulename . "_delete_instance";
+    $deleteinstancefunction = $modulename . '_delete_instance';
 
+    // Ensure the delete_instance function exists for this module.
+    if (!function_exists($deleteinstancefunction)) {
+        throw new moodle_exception('cannotdeletemodulemissingfunc', '', '', null,
+            "Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/$modulename/lib.php.");
+    }
+
+    // Call the delete_instance function, if it returns false throw an exception.
     if (!$deleteinstancefunction($cm->instance)) {
-        throw new moodle_exception("Could not delete the $modulename (instance)", '', '',
-            null, 'failedtodeletemoduleinstance');
+        throw new moodle_exception('cannotdeletemoduleinstance', '', '', null,
+            "Cannot delete the module $modulename (instance).");
     }
 
     // Remove all module files in case modules forget to do that.
@@ -2240,8 +2247,8 @@ function course_delete_module($cmid) {
 
     // 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');
+        throw new moodle_exception('cannotdeletemodulefromsection', '', '', null,
+            "Cannot delete the module $modulename (instance) from section.");
     }
 
     // Trigger a mod_deleted event with information about this module.
@@ -2524,7 +2531,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
     $actions = array();
 
     // AJAX edit title
-    if ($mod->modname !== 'label' && $hasmanageactivities &&
+    if ($mod->has_view() && $hasmanageactivities &&
                 (($mod->course == $COURSE->id && course_ajax_enabled($COURSE)) ||
                  ($mod->course == SITEID && course_ajax_enabled($SITE)))) {
         // we will not display link if we are on some other-course page (where we should not see this module anyway)
index 26827a7..177e922 100644 (file)
@@ -339,8 +339,8 @@ if (!isset($category)) {
     $table->head = array(
         get_string('categories'),
         get_string('courses'),
-        get_string('movecategoryto'),
         get_string('edit'),
+        get_string('movecategoryto'),
     );
     $table->colclasses = array(
         'leftalign name',
index 75eba57..33c0f0a 100644 (file)
@@ -25,14 +25,13 @@ Feature: Add activities to courses
       | Introduction | Test database description |
       | Required entries | 9 |
       | Comments | Yes |
-      | ID number | ASD123 |
+    And I turn editing mode off
     Then I should not see "Adding a new"
     And I follow "Test name"
     And I follow "Edit settings"
     And the "Name" field should match "Test name" value
     And the "Required entries" field should match "9" value
     And the "Comments" field should match "Yes" value
-    And the "ID number" field should match "ASD123" value
 
   @javascript
   Scenario: Add an activity without the required fields
index 3f5b4ba..0f1e0ca 100644 (file)
@@ -66,10 +66,6 @@ class behat_course extends behat_base {
      */
     public function i_add_to_section_and_i_fill_the_form_with($activity, $section, TableNode $data) {
 
-        $activity = $this->fixStepArgument($activity);
-        $section = $this->fixStepArgument($section);
-
-        // The 'I wait until the page is ready' is just in case.
         return array(
             new Given('I add a "'.$activity.'" to section "'.$section.'"'),
             new Given('I fill the moodle form with:', $data),
@@ -81,23 +77,21 @@ class behat_course extends behat_base {
      * Opens the activity chooser and opens the activity/resource form page.
      *
      * @Given /^I add a "(?P<activity_or_resource_string>(?:[^"]|\\")*)" to section "(?P<section_number>\d+)"$/
+     * @throws ElementNotFoundException Thrown by behat_base::find
      * @param string $activity
      * @param string $section
      */
     public function i_add_to_section($activity, $section) {
 
-        $activity = $this->fixStepArgument($activity);
-        $section = $this->fixStepArgument($section);
-
         // Clicks add activity or resource section link.
-        $sectionxpath = "//*[@id='section-" . $section . "']/*/*/*/div[@class='section-modchooser']/*/*";
-        $section = $this->getSession()->getPage()->find('xpath', $sectionxpath);
-        $section->click();
+        $sectionxpath = "//*[@id='section-" . $section . "']/*/*/*/div[@class='section-modchooser']/span/a";
+        $sectionnode = $this->find('xpath', $sectionxpath);
+        $sectionnode->click();
 
         // Clicks the selected activity if it exists.
         $activityxpath = ".//label[contains(.,'" . $activity . "')]/input";
-        $activity = $this->getSession()->getPage()->find('xpath', $activityxpath);
-        $activity->doubleClick();
+        $activitynode = $this->find('xpath', $activityxpath);
+        $activitynode->doubleClick();
     }
 
 }
index a59351b..ef8ef1b 100644 (file)
@@ -297,22 +297,71 @@ class courselib_testcase extends advanced_testcase {
     }
 
     public function test_move_module_in_course() {
+        global $DB;
+
         $this->resetAfterTest(true);
         // Setup fixture
-        $course = $this->getDataGenerator()->create_course(array('numsections'=>5));
+        $course = $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections' => true));
         $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 
         $cms = get_fast_modinfo($course)->get_cms();
         $cm = reset($cms);
 
-        course_create_sections_if_missing($course, 3);
-        $section3 = get_fast_modinfo($course)->get_section_info(3);
+        $newsection = get_fast_modinfo($course)->get_section_info(3);
+        $oldsectionid = $cm->section;
+
+        // Perform the move
+        moveto_module($cm, $newsection);
 
-        moveto_module($cm, $section3);
+        // reset of get_fast_modinfo is usually called the code calling moveto_module so call it here
+        get_fast_modinfo(0, 0, true);
+        $cms = get_fast_modinfo($course)->get_cms();
+        $cm = reset($cms);
 
+        // Check that the cached modinfo contains the correct section info
         $modinfo = get_fast_modinfo($course);
         $this->assertTrue(empty($modinfo->sections[0]));
         $this->assertFalse(empty($modinfo->sections[3]));
+
+        // Check that the old section's sequence no longer contains this ID
+        $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
+        $oldsequences = explode(',', $newsection->sequence);
+        $this->assertFalse(in_array($cm->id, $oldsequences));
+
+        // Check that the new section's sequence now contains this ID
+        $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
+        $newsequences = explode(',', $newsection->sequence);
+        $this->assertTrue(in_array($cm->id, $newsequences));
+
+        // Check that the section number has been changed in the cm
+        $this->assertEquals($newsection->id, $cm->section);
+
+
+        // Perform a second move as some issues were only seen on the second move
+        $newsection = get_fast_modinfo($course)->get_section_info(2);
+        $oldsectionid = $cm->section;
+        $result = moveto_module($cm, $newsection);
+        $this->assertTrue($result);
+
+        // reset of get_fast_modinfo is usually called the code calling moveto_module so call it here
+        get_fast_modinfo(0, 0, true);
+        $cms = get_fast_modinfo($course)->get_cms();
+        $cm = reset($cms);
+
+        // Check that the cached modinfo contains the correct section info
+        $modinfo = get_fast_modinfo($course);
+        $this->assertTrue(empty($modinfo->sections[0]));
+        $this->assertFalse(empty($modinfo->sections[2]));
+
+        // Check that the old section's sequence no longer contains this ID
+        $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
+        $oldsequences = explode(',', $newsection->sequence);
+        $this->assertFalse(in_array($cm->id, $oldsequences));
+
+        // Check that the new section's sequence now contains this ID
+        $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
+        $newsequences = explode(',', $newsection->sequence);
+        $this->assertTrue(in_array($cm->id, $newsequences));
     }
 
     public function test_module_visibility() {
index 4e6ad15..8560eef 100644 (file)
@@ -15,7 +15,6 @@ YUI.add('moodle-course-toolboxes', function(Y) {
         GROUPSNONE : 'a.editing_groupsnone',
         GROUPSSEPARATE : 'a.editing_groupsseparate',
         GROUPSVISIBLE : 'a.editing_groupsvisible',
-        HASLABEL : 'label',
         HIDE : 'a.editing_hide',
         HIGHLIGHT : 'a.editing_highlight',
         INSTANCENAME : 'span.instancename',
@@ -62,7 +61,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
 
             var dimarea;
             var toggle_class;
-            if (this.is_label(element)) {
+            if (this.get_instance_name(element) == null) {
                 toggle_class = CSS.DIMMEDTEXT;
                 dimarea = element.all(CSS.MODINDENTDIV + ' > div').item(1);
             } else {
@@ -172,8 +171,19 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             Y.io(uri, config);
             return responsetext;
         },
-        is_label : function(target) {
-            return target.hasClass(CSS.HASLABEL);
+        /**
+         * Return the name of the activity instance
+         *
+         * If activity has no name (for example label) null is returned
+         *
+         * @param element The <li> element to determine a name for
+         * @return string|null Instance name
+         */
+        get_instance_name : function(target) {
+            if (target.one(CSS.INSTANCENAME)) {
+                return target.one(CSS.INSTANCENAME).get('firstChild').get('data');
+            }
+            return null;
         },
         /**
          * Return the module ID for the specified element
@@ -330,19 +340,16 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             // Get the element we're working on
             var element   = e.target.ancestor(CSS.ACTIVITYLI);
 
+            // Create confirm string (different if element has or does not have name)
             var confirmstring = '';
-            if (this.is_label(element)) {
-                // Labels are slightly different to other activities
-                var plugindata = {
-                    type : M.util.get_string('pluginname', 'label')
-                }
-                confirmstring = M.util.get_string('deletechecktype', 'moodle', plugindata)
-            } else {
-                var plugindata = {
-                    type : M.util.get_string('pluginname', element.getAttribute('class').match(/modtype_([^\s]*)/)[1]),
-                    name : element.one(CSS.INSTANCENAME).get('firstChild').get('data')
-                }
+            var plugindata = {
+                type : M.util.get_string('pluginname', element.getAttribute('class').match(/modtype_([^\s]*)/)[1])
+            }
+            if (this.get_instance_name(element) != null) {
+                plugindata.name = this.get_instance_name(element)
                 confirmstring = M.util.get_string('deletechecktypename', 'moodle', plugindata);
+            } else {
+                confirmstring = M.util.get_string('deletechecktype', 'moodle', plugindata)
             }
 
             // Confirm element removal
index a2370d4..6741732 100644 (file)
@@ -118,7 +118,8 @@ class enrol_cohort_plugin extends enrol_plugin {
 
         if (has_capability('enrol/cohort:config', $context)) {
             $editlink = new moodle_url("/enrol/cohort/edit.php", array('courseid'=>$instance->courseid, 'id'=>$instance->id));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('i/edit', get_string('edit'), 'core', array('class'=>'icon')));
+            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
+                    array('class' => 'smallicon')));
         }
 
         return $icons;
index 401d11a..8198f4b 100644 (file)
@@ -386,13 +386,6 @@ function process_group_tag($tagcontents) {
                             $this->log_line('No ' . $imsname . ' description tag found for ' . $coursecode . ' coursecode, using ' . $coursecode . ' instead');
                             $course->{$courseattr} = $coursecode;
                         }
-
-                        if ($courseattr == 'summary') {
-                            $format = FORMAT_HTML;
-                        } else {
-                            $format = FORMAT_PLAIN;
-                        }
-                        $course->{$courseattr} = format_text($course->$courseattr, $format);
                     }
 
                     $course->idnumber = $coursecode;
index 13c6114..921bb30 100644 (file)
@@ -114,9 +114,9 @@ class imsenterprise_courses {
      * @return array Array of assignable values
      */
     function get_imsnames($courseattr) {
-\r
-        $values = $this->imsnames;\r
-        if ($courseattr == 'summary') {\r
+
+        $values = $this->imsnames;
+        if ($courseattr == 'summary') {
             $values = array_merge(array('ignore' => get_string('emptyattribute', 'enrol_imsenterprise')), $values);
         }
         return $values;
index 5e947b6..3f6a506 100644 (file)
@@ -318,11 +318,12 @@ class course_enrolment_manager {
      * @param array $params query parameters.
      * @param int $page which page number of the results to show.
      * @param int $perpage number of users per page.
+     * @param int $addedenrollment number of users added to enrollment.
      * @return array with two elememts:
      *      int total number of users matching the search.
      *      array of user objects returned by the query.
      */
-    protected function execute_search_queries($search, $fields, $countfields, $sql, array $params, $page, $perpage) {
+    protected function execute_search_queries($search, $fields, $countfields, $sql, array $params, $page, $perpage, $addedenrollment=0) {
         global $DB, $CFG;
 
         list($sort, $sortparams) = users_order_by_sql('u', $search, $this->get_context());
@@ -330,7 +331,7 @@ class course_enrolment_manager {
 
         $totalusers = $DB->count_records_sql($countfields . $sql, $params);
         $availableusers = $DB->get_records_sql($fields . $sql . $order,
-                array_merge($params, $sortparams), $page*$perpage, $perpage);
+                array_merge($params, $sortparams), ($page*$perpage) - $addedenrollment, $perpage);
 
         return array('totalusers' => $totalusers, 'users' => $availableusers);
     }
@@ -344,9 +345,10 @@ class course_enrolment_manager {
      * @param bool $searchanywhere
      * @param int $page Defaults to 0
      * @param int $perpage Defaults to 25
+     * @param int $addedenrollment Defaults to 0
      * @return array Array(totalusers => int, users => array)
      */
-    public function get_potential_users($enrolid, $search='', $searchanywhere=false, $page=0, $perpage=25) {
+    public function get_potential_users($enrolid, $search='', $searchanywhere=false, $page=0, $perpage=25, $addedenrollment=0) {
         global $DB;
 
         list($ufields, $params, $wherecondition) = $this->get_basic_search_conditions($search, $searchanywhere);
@@ -359,7 +361,7 @@ class course_enrolment_manager {
                       AND ue.id IS NULL";
         $params['enrolid'] = $enrolid;
 
-        return $this->execute_search_queries($search, $fields, $countfields, $sql, $params, $page, $perpage);
+        return $this->execute_search_queries($search, $fields, $countfields, $sql, $params, $page, $perpage, $addedenrollment);
     }
 
     /**
index 093ee05..ea4acd8 100644 (file)
@@ -67,7 +67,9 @@ switch ($action) {
         $enrolid = required_param('enrolid', PARAM_INT);
         $search = optional_param('search', '', PARAM_RAW);
         $page = optional_param('page', 0, PARAM_INT);
-        $outcome->response = $manager->get_potential_users($enrolid, $search, $searchanywhere, $page);
+        $addedenrollment = optional_param('enrolcount', 0, PARAM_INT);
+        $perpage = optional_param('perpage', 25, PARAM_INT);  //  This value is hard-coded to 25 in quickenrolment.js
+        $outcome->response = $manager->get_potential_users($enrolid, $search, $searchanywhere, $page, $perpage, $addedenrollment);
         $extrafields = get_extra_user_fields($context);
         foreach ($outcome->response['users'] as &$user) {
             $user->picture = $OUTPUT->user_picture($user);
index 7ffba73..5b6ae7e 100644 (file)
@@ -117,7 +117,8 @@ class enrol_manual_plugin extends enrol_plugin {
         }
         if (has_capability('enrol/manual:config', $context)) {
             $editlink = new moodle_url("/enrol/manual/edit.php", array('courseid'=>$instance->courseid));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('i/edit', get_string('edit'), 'core', array('class'=>'icon')));
+            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
+                    array('class' => 'iconsmall')));
         }
 
         return $icons;
index a969354..16ad4a2 100644 (file)
@@ -406,6 +406,7 @@ function enrol_manual_migrate_plugin_enrolments($enrol) {
 
         if (!$minstance) {
             // This should never happen unless adding of default instance fails unexpectedly.
+            debugging('Failed to find manual enrolment instance', DEBUG_DEVELOPER);
             continue;
         }
 
index aad6e62..8362185 100644 (file)
@@ -23,7 +23,9 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
         DEFAULTDURATION : 'defaultDuration',
         ASSIGNABLEROLES : 'assignableRoles',
         DISABLEGRADEHISTORY : 'disableGradeHistory',
-        RECOVERGRADESDEFAULT : 'recoverGradesDefault'
+        RECOVERGRADESDEFAULT : 'recoverGradesDefault',
+        ENROLCOUNT : 'enrolCount',
+        PERPAGE : 'perPage'
     };
     /** CSS classes for nodes in structure **/
     var CSS = {
@@ -309,6 +311,9 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
             params['action'] = 'searchusers';
             params['search'] = this.get(UEP.SEARCH).get('value');
             params['page'] = this.get(UEP.PAGE);
+            params['enrolcount'] = this.get(UEP.ENROLCOUNT);
+            params['perpage'] = this.get(UEP.PERPAGE);
+
             if (this.get(UEP.MULTIPLE)) {
                 alert('oh no there are multiple');
             } else {
@@ -376,7 +381,7 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
                 var content = create('<div class="'+CSS.SEARCHRESULTS+'"></div>')
                     .append(create('<div class="'+CSS.TOTALUSERS+'">'+usersstr+'</div>'))
                     .append(users);
-                if (result.response.totalusers > (this.get(UEP.PAGE)+1)*25) {
+                if (result.response.totalusers > (this.get(UEP.PAGE)+1)*this.get(UEP.PERPAGE)) {
                     var fetchmore = create('<div class="'+CSS.MORERESULTS+'"><a href="#">'+M.str.enrol.ajaxnext25+'</a></div>');
                     fetchmore.on('click', this.search, this, true);
                     content.append(fetchmore)
@@ -384,7 +389,7 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
                 this.setContent(content);
                 Y.delegate("click", this.enrolUser, users, '.'+CSS.USER+' .'+CSS.ENROL, this, args);
             } else {
-                if (result.response.totalusers <= (this.get(UEP.PAGE)+1)*25) {
+                if (result.response.totalusers <= (this.get(UEP.PAGE)+1)*this.get(UEP.PERPAGE)) {
                     this.get(UEP.BASE).one('.'+CSS.MORERESULTS).remove();
                 }
             }
@@ -420,6 +425,8 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
                                 args.userNode.addClass(CSS.ENROLLED);
                                 args.userNode.one('.'+CSS.ENROL).remove();
                                 this.set(UEP.REQUIREREFRESH, true);
+                                var countenrol = this.get(UEP.ENROLCOUNT)+1;
+                                this.set(UEP.ENROLCOUNT, countenrol);
                             }
                         } catch (e) {
                             new M.core.exception(e);
@@ -532,6 +539,14 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
             },
             recoverGradesDefault : {
                 value : ''
+            },
+            enrolCount : {
+                value : 0,
+                validator : Y.Lang.isNumber
+            },
+            perPage : {
+                value: 25,
+                Validator: Y.Lang.isNumber
             }
         }
     });
index ca3cd74..b79dbe1 100644 (file)
@@ -118,7 +118,8 @@ class enrol_paypal_plugin extends enrol_plugin {
 
         if (has_capability('enrol/paypal:config', $context)) {
             $editlink = new moodle_url("/enrol/paypal/edit.php", array('courseid'=>$instance->courseid, 'id'=>$instance->id));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('i/edit', get_string('edit'), 'core', array('class'=>'icon')));
+            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
+                    array('class' => 'smallicon')));
         }
 
         return $icons;
index b624d92..31679c7 100644 (file)
@@ -160,7 +160,8 @@ class enrol_self_plugin extends enrol_plugin {
 
         if (has_capability('enrol/self:config', $context)) {
             $editlink = new moodle_url("/enrol/self/edit.php", array('courseid'=>$instance->courseid, 'id'=>$instance->id));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('i/edit', get_string('edit'), 'core', array('class'=>'icon')));
+            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
+                array('class' => 'smallicon')));
         }
 
         return $icons;
index bef80bc..dc2b636 100644 (file)
@@ -36,8 +36,6 @@ class filter_activitynames extends moodle_text_filter {
     static $cachedcourseid;
 
     function filter($text, array $options = array()) {
-        global $CFG, $COURSE, $DB;
-
         if (!$courseid = get_courseid_from_context($this->context)) {
             return $text;
         }
@@ -53,35 +51,25 @@ class filter_activitynames extends moodle_text_filter {
         if (is_null(self::$activitylist)) {
             self::$activitylist = array();
 
-            if ($COURSE->id == $courseid) {
-                $course = $COURSE;
-            } else {
-                $course = $DB->get_record("course", array("id"=>$courseid));
-            }
-
-            if (!isset($course->modinfo)) {
-                return $text;
-            }
-
-        /// Casting $course->modinfo to string prevents one notice when the field is null
-            $modinfo = unserialize((string)$course->modinfo);
-
-            if (!empty($modinfo)) {
-
+            $modinfo = get_fast_modinfo($courseid);
+            if (!empty($modinfo->cms)) {
                 self::$activitylist = array();      /// We will store all the activities here
 
                 //Sort modinfo by name length
-                usort($modinfo, 'filter_activitynames_comparemodulenamesbylength');
+                $sortedactivities = fullclone($modinfo->cms);
+                usort($sortedactivities, 'filter_activitynames_comparemodulenamesbylength');
 
-                foreach ($modinfo as $activity) {
+                foreach ($sortedactivities as $cm) {
                     //Exclude labels, hidden activities and activities for group members only
-                    if ($activity->mod != "label" and $activity->visible and empty($activity->groupmembersonly)) {
-                        $title = s(trim(strip_tags($activity->name)));
-                        $currentname = trim($activity->name);
+                    if ($cm->visible and empty($cm->groupmembersonly) and $cm->has_view()) {
+                        $title = s(trim(strip_tags($cm->name)));
+                        $currentname = trim($cm->name);
                         $entitisedname  = s($currentname);
                         /// Avoid empty or unlinkable activity names
                         if (!empty($title)) {
-                            $href_tag_begin = "<a class=\"autolink\" title=\"$title\" href=\"$CFG->wwwroot/mod/$activity->mod/view.php?id=$activity->cm\">";
+                            $href_tag_begin = html_writer::start_tag('a',
+                                    array('class' => 'autolink', 'title' => $title,
+                                        'href' => $cm->get_url()));
                             self::$activitylist[] = new filterobject($currentname, $href_tag_begin, '</a>', false, true);
                             if ($currentname != $entitisedname) { /// If name has some entity (&amp; &quot; &lt; &gt;) add that filter too. MDL-17545
                                 self::$activitylist[] = new filterobject($entitisedname, $href_tag_begin, '</a>', false, true);
index c1d9b5b..658d5ce 100644 (file)
@@ -132,6 +132,7 @@ if ($courseid and $outcomes = grade_outcome::fetch_all_local($courseid)) {
         $scale = $outcome->load_scale();
         if (empty($scale->id)) {   // hopefully never happens
             $line[] = $scale->get_name();
+            debugging("Found a scale with no ID ({$scale->get_name()}) while outputting course outcomes", DEBUG_DEVELOPER);
         } else {
             if (empty($scale->courseid)) {
                 $caneditthisscale = $caneditsystemscales;
@@ -181,6 +182,7 @@ if ($outcomes = grade_outcome::fetch_all_global()) {
         $scale = $outcome->load_scale();
         if (empty($scale->id)) {   // hopefully never happens
             $line[] = $scale->get_name();
+            debugging("Found a scale with no ID ({$scale->get_name()}) while outputting global outcomes", DEBUG_DEVELOPER);
         } else {
             if (empty($scale->courseid)) {
                 $caneditthisscale = $caneditsystemscales;
index 82fc9eb..f0c7188 100644 (file)
@@ -81,10 +81,13 @@ switch ($action) {
                 // Warn if the grade is out of bounds.
                 if (is_null($finalgrade)) {
                     // ok
-                } else if ($finalgrade < $grade_item->grademin) {
-                    $errorstr = 'lessthanmin';
-                } else if ($finalgrade > $grade_item->grademax) {
-                    $errorstr = 'morethanmax';
+                } else {
+                    $bounded = $grade_item->bounded_grade($finalgrade);
+                    if ($bounded > $finalgrade) {
+                        $errorstr = 'lessthanmin';
+                    } else if ($bounded < $finalgrade) {
+                        $errorstr = 'morethanmax';
+                    }
                 }
 
                 if ($errorstr) {
index 2890159..067453c 100644 (file)
@@ -256,7 +256,7 @@ class grade_report_grader extends grade_report {
                         } else {
                             $bounded = $gradeitem->bounded_grade($finalgrade);
                             if ($bounded > $finalgrade) {
-                            $errorstr = 'lessthanmin';
+                                $errorstr = 'lessthanmin';
                             } else if ($bounded < $finalgrade) {
                                 $errorstr = 'morethanmax';
                             }
index 0ee92ba..54af548 100644 (file)
@@ -323,9 +323,9 @@ M.gradereport_grader.classes.ajax = function(report, cfg) {
                 this.existingfields[userid][itemid] = new M.gradereport_grader.classes.existingfield(this, userid, itemid);
             }
         }
-        // Hide the Update button
+        // Disable the Update button as we're saving using ajax.
         submitbutton = this.report.Y.one('#gradersubmit');
-        submitbutton.setStyle('visibility', 'hidden');
+        submitbutton.set('disabled', true);
     }
 };
 /**
diff --git a/grade/tests/reportgrader_test.php b/grade/tests/reportgrader_test.php
new file mode 100644 (file)
index 0000000..07be5e3
--- /dev/null
@@ -0,0 +1,116 @@
+<?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/>.
+
+/**
+ * Unit tests for grade/report/user/lib.php.
+ *
+ * @package  core_grade
+ * @category phpunit
+ * @copyright 2012 Andrew Davis
+ * @license  http://www.gnu.org/copyleft/gpl.html GNU Public License
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot.'/grade/lib.php');
+require_once($CFG->dirroot.'/grade/report/grader/lib.php');
+
+/**
+ * Tests grade_report_grader (the grader report)
+ */
+class grade_report_graderlib_testcase extends advanced_testcase {
+
+    /**
+     * Tests grade_report_grader::process_data()
+     *
+     * process_data() processes submitted grade and feedback data
+     */
+    public function test_process_data() {
+        global $DB, $CFG;
+
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $coursecontext = context_course::instance($course->id);
+
+        // Create and enrol a student.
+        $student = $this->getDataGenerator()->create_user(array('username' => 'Student Sam'));
+        $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
+        $this->getDataGenerator()->enrol_user($student->id, $course->id, $role->id);
+
+        // Test with limited grades.
+        $CFG->unlimitedgrades = 0;
+
+        $forummax = 80;
+        $forum1 = $this->getDataGenerator()->create_module('forum', array('assessed' => 1, 'scale' => $forummax, 'course' => $course->id));
+        // Switch the stdClass instance for a grade item instance.
+        $forum1 = grade_item::fetch(array('itemtype' => 'mod', 'itemmodule' => 'forum', 'iteminstance' => $forum1->id, 'courseid' => $course->id));
+
+        $report = $this->create_report($course, $coursecontext);
+        $testgrade = 60.00;
+
+        $data = new stdClass();
+        $data->id = $course->id;
+        $data->report = 'grader';
+
+        $data->grade = array();
+        $data->grade[$student->id] = array();
+        $data->grade[$student->id][$forum1->id] = $testgrade;
+
+        $warnings = $report->process_data($data);
+        $this->assertEquals(count($warnings), 0);
+
+        $studentgrade = grade_grade::fetch(array('itemid' => $forum1->id, '' => $student->id));
+        $this->assertEquals($studentgrade->finalgrade, $testgrade);
+
+        // Grade above max. Should be pulled down to max.
+        $toobig = 200.00;
+        $data->grade[$student->id][$forum1->id] = $toobig;
+        $warnings = $report->process_data($data);
+        $this->assertEquals(count($warnings), 1);
+
+        $studentgrade = grade_grade::fetch(array('itemid' => $forum1->id, '' => $student->id));
+        $this->assertEquals($studentgrade->finalgrade, $forummax);
+
+        // Grade below min. Should be pulled up to min.
+        $toosmall = -10.00;
+        $data->grade[$student->id][$forum1->id] = $toosmall;
+        $warnings = $report->process_data($data);
+        $this->assertEquals(count($warnings), 1);
+
+        $studentgrade = grade_grade::fetch(array('itemid' => $forum1->id, '' => $student->id));
+        $this->assertEquals($studentgrade->finalgrade, 0);
+
+        // Test unlimited grades so we can give a student a grade about max.
+        $CFG->unlimitedgrades = 1;
+
+        $data->grade[$student->id][$forum1->id] = $toobig;
+        $warnings = $report->process_data($data);
+        $this->assertEquals(count($warnings), 0);
+
+        $studentgrade = grade_grade::fetch(array('itemid' => $forum1->id, '' => $student->id));
+        $this->assertEquals($studentgrade->finalgrade, $toobig);
+    }
+
+    private function create_report($course, $coursecontext) {
+
+        $gpr = new grade_plugin_return(array('type' => 'report', 'plugin'=>'grader', 'courseid' => $course->id));
+        $report = new grade_report_grader($course->id, $gpr, $coursecontext);
+
+        return $report;
+    }
+}
index 5963c6e..48d656f 100644 (file)
@@ -760,6 +760,7 @@ class core_group_external extends external_api {
             array(
                 'groupingids' => new external_multiple_structure(new external_value(PARAM_INT, 'grouping ID')
                         , 'List of grouping id. A grouping id is an integer.'),
+                'returngroups' => new external_value(PARAM_BOOL, 'return associated groups', VALUE_DEFAULT, 0)
             )
         );
     }
@@ -768,15 +769,18 @@ class core_group_external extends external_api {
      * Get groupings definition specified by ids
      *
      * @param array $groupingids arrays of grouping ids
+     * @param boolean $returngroups return the associated groups if true. The default is false.
      * @return array of grouping objects (id, courseid, name)
      * @since Moodle 2.3
      */
-    public static function get_groupings($groupingids) {
-        global $CFG;
+    public static function get_groupings($groupingids, $returngroups = false) {
+        global $CFG, $DB;
         require_once("$CFG->dirroot/group/lib.php");
         require_once("$CFG->libdir/filelib.php");
 
-        $params = self::validate_parameters(self::get_groupings_parameters(), array('groupingids'=>$groupingids));
+        $params = self::validate_parameters(self::get_groupings_parameters(),
+                                            array('groupingids' => $groupingids,
+                                                  'returngroups' => $returngroups));
 
         $groupings = array();
         foreach ($params['groupingids'] as $groupingid) {
@@ -799,7 +803,30 @@ class core_group_external extends external_api {
                 external_format_text($grouping->description, $grouping->descriptionformat,
                         $context->id, 'grouping', 'description', $grouping->id);
 
-            $groupings[] = (array)$grouping;
+            $groupingarray = (array)$grouping;
+
+            if ($params['returngroups']) {
+                $grouprecords = $DB->get_records_sql("SELECT * FROM {groups} g INNER JOIN {groupings_groups} gg ".
+                                               "ON g.id = gg.groupid WHERE gg.groupingid = ? ".
+                                               "ORDER BY groupid", array($groupingid));
+                if ($grouprecords) {
+                    $groups = array();
+                    foreach ($grouprecords as $grouprecord) {
+                        list($grouprecord->description, $grouprecord->descriptionformat) =
+                        external_format_text($grouprecord->description, $grouprecord->descriptionformat,
+                        $context->id, 'group', 'description', $grouprecord->groupid);
+                        $groups[] = array('id' => $grouprecord->groupid,
+                                          'name' => $grouprecord->name,
+                                          'description' => $grouprecord->description,
+                                          'descriptionformat' => $grouprecord->descriptionformat,
+                                          'enrolmentkey' => $grouprecord->enrolmentkey,
+                                          'courseid' => $grouprecord->courseid
+                                          );
+                    }
+                    $groupingarray['groups'] = $groups;
+                }
+            }
+            $groupings[] = $groupingarray;
         }
 
         return $groupings;
@@ -819,7 +846,19 @@ class core_group_external extends external_api {
                     'courseid' => new external_value(PARAM_INT, 'id of course'),
                     'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
                     'description' => new external_value(PARAM_RAW, 'grouping description text'),
-                    'descriptionformat' => new external_format_value('description')
+                    'descriptionformat' => new external_format_value('description'),
+                    'groups' => new external_multiple_structure(
+                        new external_single_structure(
+                            array(
+                                'id' => new external_value(PARAM_INT, 'group record id'),
+                                'courseid' => new external_value(PARAM_INT, 'id of course'),
+                                'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
+                                'description' => new external_value(PARAM_RAW, 'group description text'),
+                                'descriptionformat' => new external_format_value('description'),
+                                'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase')
+                            )
+                        ),
+                    'optional groups', VALUE_OPTIONAL)
                 )
             )
         );
index 04800d2..ec8b094 100644 (file)
@@ -83,17 +83,20 @@ if ($groupings = $DB->get_records('groupings', array('courseid'=>$course->id), '
         }
         $line[2] = $DB->count_records('course_modules', array('course'=>$course->id, 'groupingid'=>$grouping->id));
 
-        $buttons  = "<a title=\"$stredit\" href=\"grouping.php?id=$grouping->id\"><img".
-                    " src=\"" . $OUTPUT->pix_url('t/edit') . "\" class=\"iconsmall\" alt=\"$stredit\" /></a> ";
+        $url = new moodle_url('/group/grouping.php', array('id' => $grouping->id));
+        $buttons  = html_writer::link($url, $OUTPUT->pix_icon('t/edit', $stredit, 'core',
+                array('class' => 'iconsmall')), array('title' => $stredit));
         if (empty($grouping->idnumber) || $canchangeidnumber) {
-            // It's only possible to delete groups without an idnumber unless the user has the changeidnumber capability
-            $buttons .= "<a title=\"$strdelete\" href=\"grouping.php?id=$grouping->id&amp;delete=1\"><img".
-                        " src=\"" . $OUTPUT->pix_url('t/delete') . "\" class=\"iconsmall\" alt=\"$strdelete\" /></a> ";
+            // It's only possible to delete groups without an idnumber unless the user has the changeidnumber capability.
+            $url = new moodle_url('/group/grouping.php', array('id' => $grouping->id, 'delete' => 1));
+            $buttons .= html_writer::link($url, $OUTPUT->pix_icon('t/delete', $strdelete, 'core',
+                    array('class' => 'iconsmall')), array('title' => $strdelete));
         } else {
             $buttons .= $OUTPUT->spacer();
         }
-        $buttons .= "<a title=\"$strmanagegrping\" href=\"assign.php?id=$grouping->id\"><img".
-                    " src=\"" . $OUTPUT->pix_url('i/group') . "\" class=\"icon\" alt=\"$strmanagegrping\" /></a> ";
+        $url = new moodle_url('/group/assign.php', array('id' => $grouping->id));
+        $buttons .= html_writer::link($url, $OUTPUT->pix_icon('t/groups', $strmanagegrping, 'core',
+                array('class' => 'iconsmall')), array('title' => $strmanagegrping));
 
         $line[3] = $buttons;
         $data[] = $line;
index 996d3c6..8048d18 100644 (file)
@@ -30,6 +30,7 @@ global $CFG;
 
 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
 require_once($CFG->dirroot . '/group/externallib.php');
+require_once($CFG->dirroot . '/group/lib.php');
 
 class core_group_external_testcase extends externallib_advanced_testcase {
 
@@ -207,4 +208,81 @@ class core_group_external_testcase extends externallib_advanced_testcase {
         $this->setExpectedException('required_capability_exception');
         $froups = core_group_external::delete_groups(array($group3->id));
     }
+
+    /**
+     * Test get_groupings
+     */
+    public function test_get_groupings() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $course = self::getDataGenerator()->create_course();
+
+        $groupingdata = array();
+        $groupingdata['courseid'] = $course->id;
+        $groupingdata['name'] = 'Grouping Test';
+        $groupingdata['description'] = 'Grouping Test description';
+        $groupingdata['descriptionformat'] = FORMAT_MOODLE;
+
+        $grouping = self::getDataGenerator()->create_grouping($groupingdata);
+
+        // Set the required capabilities by the external function.
+        $context = context_course::instance($course->id);
+        $roleid = $this->assignUserCapability('moodle/course:managegroups', $context->id);
+        $this->assignUserCapability('moodle/course:view', $context->id, $roleid);
+
+        // Call the external function without specifying the optional parameter.
+        $groupings = core_group_external::get_groupings(array($grouping->id));
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $groupings = external_api::clean_returnvalue(core_group_external::get_groupings_returns(), $groupings);
+
+        $this->assertEquals(1, count($groupings));
+
+        $group1data = array();
+        $group1data['courseid'] = $course->id;
+        $group1data['name'] = 'Group Test 1';
+        $group1data['description'] = 'Group Test 1 description';
+        $group1data['descriptionformat'] = FORMAT_MOODLE;
+        $group2data = array();
+        $group2data['courseid'] = $course->id;
+        $group2data['name'] = 'Group Test 2';
+        $group2data['description'] = 'Group Test 2 description';
+        $group2data['descriptionformat'] = FORMAT_MOODLE;
+
+        $group1 = self::getDataGenerator()->create_group($group1data);
+        $group2 = self::getDataGenerator()->create_group($group2data);
+
+        groups_assign_grouping($grouping->id, $group1->id);
+        groups_assign_grouping($grouping->id, $group2->id);
+
+        // Call the external function specifying that groups are returned.
+        $groupings = core_group_external::get_groupings(array($grouping->id), true);
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $groupings = external_api::clean_returnvalue(core_group_external::get_groupings_returns(), $groupings);
+        $this->assertEquals(1, count($groupings));
+        $this->assertEquals(2, count($groupings[0]['groups']));
+        foreach ($groupings[0]['groups'] as $group) {
+            $dbgroup = $DB->get_record('groups', array('id' => $group['id']), '*', MUST_EXIST);
+            $dbgroupinggroups = $DB->get_record('groupings_groups',
+                                                array('groupingid' => $groupings[0]['id'],
+                                                      'groupid' => $group['id']),
+                                                '*', MUST_EXIST);
+            switch ($dbgroup->name) {
+                case $group1->name:
+                    $groupdescription = $group1->description;
+                    $groupcourseid = $group1->courseid;
+                    break;
+                case $group2->name:
+                    $groupdescription = $group2->description;
+                    $groupcourseid = $group2->courseid;
+                    break;
+                default:
+                    throw new moodle_exception('unknowgroupname');
+                    break;
+            }
+            $this->assertEquals($dbgroup->description, $groupdescription);
+            $this->assertEquals($dbgroup->courseid, $groupcourseid);
+        }
+    }
 }
index 6afdd97..4323461 100644 (file)
--- a/help.php
+++ b/help.php
@@ -48,15 +48,15 @@ $PAGE->set_context(context_system::instance());
 
 if ($ajax) {
     @header('Content-Type: text/plain; charset=utf-8');
-} else {
-    echo $OUTPUT->header();
 }
 
 if (!$sm->string_exists($identifier.'_help', $component)) {
-    // strings on-diskc cache may be dirty - try to rebuild it and check again
+    // strings on disk-cache may be dirty - try to rebuild it and check again
     $sm->load_component_strings($component, current_language(), true);
 }
 
+$data = new stdClass();
+
 if ($sm->string_exists($identifier.'_help', $component)) {
     $options = new stdClass();
     $options->trusted = false;
@@ -67,26 +67,38 @@ if ($sm->string_exists($identifier.'_help', $component)) {
     $options->newlines = false;
     $options->overflowdiv = !$ajax;
 
-    if ($ajax) {
-        // When using AJAX, the header should be H2 as it is in the same DOM as the calling page.
-        echo $OUTPUT->heading(format_string(get_string($identifier, $component)), 2, 'helpheading');
-    } else {
-        // When not using AJAX, the header should be H1 as it is in it's own window.
-        echo $OUTPUT->heading(format_string(get_string($identifier, $component)), 1, 'helpheading');
-    }
+    $data->heading = format_string(get_string($identifier, $component));
     // Should be simple wiki only MDL-21695
-    echo format_text(get_string($identifier.'_help', $component), FORMAT_MARKDOWN, $options);
+    $data->text =  format_text(get_string($identifier.'_help', $component), FORMAT_MARKDOWN, $options);
 
-    if ($sm->string_exists($identifier.'_link', $component)) {  // Link to further info in Moodle docs
-        $link = get_string($identifier.'_link', $component);
+    $helplink = $identifier . '_link';
+    if ($sm->string_exists($helplink, $component)) {  // Link to further info in Moodle docs
+        $link = get_string($helplink, $component);
         $linktext = get_string('morehelp');
-        echo '<div class="helpdoclink">'.$OUTPUT->doc_link($link, $linktext).'</div>';
-    }
 
+        $data->doclink = new stdClass();
+        $url = new moodle_url(get_docs_url($link));
+        $data->doclink->link = $url->out();
+        $data->doclink->linktext = $linktext;
+        $data->doclink->class = ($CFG->doctonewwindow) ? 'helplinkpopup' : '';
+
+        $completedoclink = html_writer::tag('div', $OUTPUT->doc_link($link, $linktext), array('class' => 'helpdoclink'));
+    }
 } else {
-    echo "<p><strong>TODO</strong>: missing help string [{$identifier}_help, $component]</p>";
+    $data->text = html_writer::tag('p',
+            html_writer::tag('strong', 'TODO') . ": missing help string [{$identifier}_help, {$component}]");
 }
 
-if (!$ajax) {
+if ($ajax) {
+    echo json_encode($data);
+} else {
+    echo $OUTPUT->header();
+    if (isset($data->heading)) {
+        echo $OUTPUT->heading($data->heading, 1, 'helpheading');
+    }
+    echo $data->text;
+    if (isset($completedoclink)) {
+        echo $completedoclink;
+    }
     echo $OUTPUT->footer();
 }
index b6ba495..3a54324 100644 (file)
@@ -43,6 +43,6 @@ $string['downloadedfilecheckfailed'] = 'Alla laetud faili kontroll ebaõnnestus.
 $string['invalidmd5'] = 'Vigane md5';
 $string['missingrequiredfield'] = 'Mõned nõutud väljad on puudu';
 $string['remotedownloaderror'] = 'Komponendi alla tõmbamine serverisse ebaõnnestus, palun kontrolli proksi seadeid, PHP cURL laiendus on tungivalt soovitatav.<br /><br />Sa pead tõmbama <a href="{$a->url}">{$a->url}</a> faili käsitsi, kopeerima selle "{$a->dest}" oma serveris ja pakkima lahti sinna.';
-$string['wrongdestpath'] = 'Vale sihtrada.';
+$string['wrongdestpath'] = 'Vale sihtrada';
 $string['wrongsourcebase'] = 'Vale allika URL\'i baas.';
-$string['wrongzipfilename'] = 'Vale ZIP failinimi.';
+$string['wrongzipfilename'] = 'Vale ZIP failinimi';
diff --git a/install/lang/kmr/langconfig.php b/install/lang/kmr/langconfig.php
new file mode 100644 (file)
index 0000000..d8fd01a
--- /dev/null
@@ -0,0 +1,33 @@
+<?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/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['thislanguage'] = 'Kurmanji';
index 75418a9..9c7880f 100644 (file)
@@ -35,7 +35,7 @@ $string['availablelangs'] = 'Lista de idiomas disponíveis';
 $string['chooselanguagehead'] = 'Escolha um idioma';
 $string['chooselanguagesub'] = 'Por favor, escolha o idioma para a instalação.Este idioma também será utilizado como idioma padrão do site, embora você possa mudar mais tarde.';
 $string['clialreadyconfigured'] = 'Arquivo config.php já existente. Por favor use admin/cli/install_database.php se você quer instalar este site';
-$string['clialreadyinstalled'] = 'O arquivo config.php já existe, por favor use admin/cli/upgrade.php, se você quiser atualizar o seu site.';
+$string['clialreadyinstalled'] = 'O arquivo config.php já existe, por favor use admin/cli/upgrade.php, se você quiser atualizar este site.';
 $string['cliinstallheader'] = 'Programa de instalação por linha de comando do Moodle {$a}';
 $string['databasehost'] = 'Host da base de dados';
 $string['databasename'] = 'Nome da base de dados';
index 4996af6..53f68e2 100644 (file)
@@ -1035,7 +1035,7 @@ $string['updateavailable_version'] = 'Version {$a}';
 $string['updateavailableinstall'] = 'Install this update';
 $string['updateavailablenot'] = 'Your Moodle code is up-to-date!';
 $string['updatenotifications'] = 'Update notifications';
-$string['updatenotificationfooter'] = 'Your Moodle site {$a->siteurl} is configured to automatically check for available updates. You are receiving this message as the administrator of the site. You can disable automatic checks for available updates in the Site administration section of the Settings block. You can customize the delivery of this message via your personal Messaging setting in the My profile settings section.';
+$string['updatenotificationfooter'] = 'Your Moodle site {$a->siteurl} is configured to automatically check for available updates. You are receiving this message as the administrator of the site. You can disable automatic checks for available updates in the Site administration section of the Administration block. You can customize the delivery of this message via your personal Messaging setting in the My profile settings section.';
 $string['updatenotificationsubject'] = 'Moodle updates are available ({$a->siteurl})';
 $string['updateautocheck'] = 'Automatically check for available updates';
 $string['updateautocheck_desc'] = 'If enabled, your site will automatically check for available updates for both Moodle code and all additional plugins. If there is a new update available, a notification will be sent to site admins.';
index 0a1a1b2..8e7d0c0 100644 (file)
@@ -63,6 +63,7 @@ $string['subpages'] = 'Select pages';
 $string['restrictpagetypes'] = 'Display on page types';
 $string['thisspecificpage'] = 'This specific page';
 $string['undockall'] = 'Undock all';
+$string['undockblock'] = 'Undock {$a} block';
 $string['undockitem'] = 'Undock this item';
 $string['visible'] = 'Visible';
 $string['weight'] = 'Weight';
index b4ef21a..190e57d 100644 (file)
@@ -30,7 +30,6 @@ $string['AG'] = 'Antigua And Barbuda';
 $string['AI'] = 'Anguilla';
 $string['AL'] = 'Albania';
 $string['AM'] = 'Armenia';
-$string['AN'] = 'Netherlands Antilles';
 $string['AO'] = 'Angola';
 $string['AQ'] = 'Antarctica';
 $string['AR'] = 'Argentina';
@@ -52,7 +51,8 @@ $string['BJ'] = 'Benin';
 $string['BL'] = 'Saint Barthélemy';
 $string['BM'] = 'Bermuda';
 $string['BN'] = 'Brunei Darussalam';
-$string['BO'] = 'Bolivia';
+$string['BO'] = 'Bolivia, Plurinational State Of';
+$string['BQ'] = 'Bonaire, Sint Eustatius And Saba';
 $string['BR'] = 'Brazil';
 $string['BS'] = 'Bahamas';
 $string['BT'] = 'Bhutan';
@@ -65,6 +65,7 @@ $string['CC'] = 'Cocos (Keeling) Islands';
 $string['CD'] = 'Congo, The Democratic Republic Of The';
 $string['CF'] = 'Central African Republic';
 $string['CG'] = 'Congo';
+$string['CH'] = 'Switzerland';
 $string['CI'] = 'Côte D\'Ivoire';
 $string['CK'] = 'Cook Islands';
 $string['CL'] = 'Chile';
@@ -74,6 +75,7 @@ $string['CO'] = 'Colombia';
 $string['CR'] = 'Costa Rica';
 $string['CU'] = 'Cuba';
 $string['CV'] = 'Cape Verde';
+$string['CW'] = 'Curaçao';
 $string['CX'] = 'Christmas Island';
 $string['CY'] = 'Cyprus';
 $string['CZ'] = 'Czech Republic';
@@ -121,7 +123,6 @@ $string['HN'] = 'Honduras';
 $string['HR'] = 'Croatia';
 $string['HT'] = 'Haiti';
 $string['HU'] = 'Hungary';
-$string['CH'] = 'Switzerland';
 $string['ID'] = 'Indonesia';
 $string['IE'] = 'Ireland';
 $string['IL'] = 'Israel';
@@ -157,12 +158,12 @@ $string['LS'] = 'Lesotho';
 $string['LT'] = 'Lithuania';
 $string['LU'] = 'Luxembourg';
 $string['LV'] = 'Latvia';
-$string['LY'] = 'Libyan Arab Jamahiriya';
+$string['LY'] = 'Libya';
 $string['MA'] = 'Morocco';
 $string['MC'] = 'Monaco';
 $string['MD'] = 'Moldova, Republic Of';
 $string['ME'] = 'Montenegro';
-$string['MF'] = 'Saint Martin';
+$string['MF'] = 'Saint Martin (French Part)';
 $string['MG'] = 'Madagascar';
 $string['MH'] = 'Marshall Islands';
 $string['MK'] = 'Macedonia, The Former Yugoslav Republic Of';
@@ -204,7 +205,7 @@ $string['PL'] = 'Poland';
 $string['PM'] = 'Saint Pierre And Miquelon';
 $string['PN'] = 'Pitcairn';
 $string['PR'] = 'Puerto Rico';
-$string['PS'] = 'Palestinian Territory, Occupied';
+$string['PS'] = 'Palestine, State Of';
 $string['PT'] = 'Portugal';
 $string['PW'] = 'Palau';
 $string['PY'] = 'Paraguay';
@@ -220,7 +221,7 @@ $string['SC'] = 'Seychelles';
 $string['SD'] = 'Sudan';
 $string['SE'] = 'Sweden';
 $string['SG'] = 'Singapore';
-$string['SH'] = 'Saint Helena';
+$string['SH'] = 'Saint Helena, Ascension And Tristan Da Cunha';
 $string['SI'] = 'Slovenia';
 $string['SJ'] = 'Svalbard And Jan Mayen';
 $string['SK'] = 'Slovakia';
@@ -229,8 +230,10 @@ $string['SM'] = 'San Marino';
 $string['SN'] = 'Senegal';
 $string['SO'] = 'Somalia';
 $string['SR'] = 'Suriname';
+$string['SS'] = 'South Sudan';
 $string['ST'] = 'Sao Tome And Principe';
 $string['SV'] = 'El Salvador';
+$string['SX'] = 'Sint Maarten (Dutch Part)';
 $string['SY'] = 'Syrian Arab Republic';
 $string['SZ'] = 'Swaziland';
 $string['TC'] = 'Turks And Caicos Islands';
@@ -257,7 +260,7 @@ $string['UY'] = 'Uruguay';
 $string['UZ'] = 'Uzbekistan';
 $string['VA'] = 'Holy See (Vatican City State)';
 $string['VC'] = 'Saint Vincent And The Grenadines';
-$string['VE'] = 'Venezuela';
+$string['VE'] = 'Venezuela, Bolivarian Republic Of';
 $string['VG'] = 'Virgin Islands, British';
 $string['VI'] = 'Virgin Islands, U.S.';
 $string['VN'] = 'Viet Nam';
index d5f2c6c..e1db4bc 100644 (file)
@@ -58,6 +58,8 @@ $string['security'] = 'Security';
 $string['selectallornone'] = 'Select all/none';
 $string['selected'] = 'Selected';
 $string['showadvanced'] = 'Show advanced';
+$string['showless'] = 'Show less...';
+$string['showmore'] = 'Show more...';
 $string['showeditortoolbar'] = 'Show editing tools';
 $string['somefieldsrequired'] = 'There are required fields in this form marked {$a}.';
 $string['time'] = 'Time';
index fed4bbc..3be622d 100644 (file)
@@ -31,7 +31,7 @@ $string['activemethodinfonone'] = 'There is no advanced grading method selected
 $string['changeactivemethod'] = 'Change active grading method to';
 $string['clicktoclose'] = 'click to close';
 $string['exc_gradingformelement'] = 'Unable to instantiate grading form element';
-$string['formnotavailable'] = 'Advanced grading method was selected to use but the grading form is not available yet. You may need to define it first via a link in the Settings block.';
+$string['formnotavailable'] = 'Advanced grading method was selected to use but the grading form is not available yet. You may need to define it first via a link in the Administration block.';
 $string['gradingformunavailable'] = 'Please note: the advanced grading form is not ready at the moment. Simple grading method will be used until the form has a valid status.';
 $string['gradingmanagement'] = 'Advanced grading';
 $string['gradingmanagementtitle'] = 'Advanced grading: {$a->component} ({$a->area})';
index 1543b76..93361fe 100644 (file)
@@ -361,7 +361,6 @@ $string['coursestart'] = 'Course start';
 $string['coursesummary'] = 'Course summary';
 $string['coursesummary_help'] = 'The course summary is displayed in the list of courses. A course search searches course summary text in addition to course names.';
 $string['courseupdates'] = 'Course updates';
-$string['courseuploadlimit'] = 'Course upload limit';
 $string['create'] = 'Create';
 $string['createaccount'] = 'Create my new account';
 $string['createcategory'] = 'Create category';
@@ -924,6 +923,7 @@ $string['list'] = 'List';
 $string['listfiles'] = 'List of files in {$a}';
 $string['listofallpeople'] = 'List of all people';
 $string['listofcourses'] = 'List of courses';
+$string['loadinghelp'] = 'Loading...';
 $string['local'] = 'Local';
 $string['localplugindeleteconfirm'] = 'You are about to completely delete the local plugin \'{$a}\'. This will completely delete everything in the database associated with this plugin. Are you SURE you want to continue?';
 $string['localplugins'] = 'Local plugins';
@@ -1714,6 +1714,7 @@ $string['uploadfailednotrecovering'] = 'Your file upload has failed because ther
 $string['uploadfilelog'] = 'Upload log for file {$a}';
 $string['uploadformlimit'] = 'Uploaded file {$a} exceeded the maximum size limit set by the form';
 $string['uploadlabel'] = 'Title:';
+$string['uploadlimitwithsize'] = '{$a->contextname} upload limit ({$a->displaysize})';
 $string['uploadnewfile'] = 'Upload new file';
 $string['uploadnofilefound'] = 'No file was found - are you sure you selected one to upload?';
 $string['uploadnotallowed'] = 'Uploads are not allowed';
index 3ab441e..79cc8d2 100644 (file)
@@ -54,4 +54,4 @@ $string['ratinginvalid'] = 'Rating is invalid';
 $string['ratingtime'] = 'Restrict ratings to items with dates in this range:';
 $string['ratings'] = 'Ratings';
 $string['rolewarning'] = 'Roles with permission to rate';
-$string['rolewarning_help'] = 'To submit ratings users require the moodle/rating:rate capability and any module specific capabilities. Users assigned the following roles should be able to rate items. The list of roles may be amended via the permissions link in the settings block.';
+$string['rolewarning_help'] = 'To submit ratings users require the moodle/rating:rate capability and any module specific capabilities. Users assigned the following roles should be able to rate items. The list of roles may be amended via the permissions link in the administration block.';
index c6d7f2c..5a59ea6 100644 (file)
@@ -393,6 +393,7 @@ function has_capability($capability, context $context, $user = null, $doanything
     if (!isset($USER->id)) {
         // should never happen
         $USER->id = 0;
+        debugging('Capability check being performed on a user with no ID.', DEBUG_DEVELOPER);
     }
 
     // make sure there is a real user specified
@@ -2062,6 +2063,7 @@ function can_access_course(stdClass $course, $user = null, $withcapability = '',
     if (!isset($USER->id)) {
         // should never happen
         $USER->id = 0;
+        debugging('Course access check being performed on a user with no ID.', DEBUG_DEVELOPER);
     }
 
     // make sure there is a user specified
index c957837..d703a77 100644 (file)
@@ -28,6 +28,9 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
+use Behat\Mink\Exception\ExpectationException as ExpectationException,
+    Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
+
 /**
  * Steps definitions base class.
  *
@@ -48,21 +51,6 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
      */
     const TIMEOUT = 6;
 
-    /**
-     * Returns fixed step argument (with \\" replaced back to ").
-     *
-     * \\ is the chars combination to add when you
-     * want to escape the " character that is used as var
-     * delimiter.
-     *
-     * @see Behat\MinkExtension\Context\MinkContext
-     * @param string $argument
-     * @return string
-     */
-    protected function fixStepArgument($argument) {
-        return str_replace('\\"', '"', $argument);
-    }
-
     /**
      * Locates url, based on provided path.
      * Override to provide custom routing mechanism.
@@ -76,6 +64,89 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
         return 0 !== strpos($path, 'http') ? $startUrl . ltrim($path, '/') : $path;
     }
 
+    /**
+     * Adapter to Behat\Mink\Element\Element::find() using the spin() method.
+     *
+     * @link http://mink.behat.org/#traverse-the-page-selectors
+     * @param Exception $exception Otherwise we throw expcetion with generic info
+     * @param string $selector The selector type (css, xpath, named...)
+     * @param mixed $locator It depends on the $selector, can be the xpath, a name, a css locator...
+     * @return NodeElement
+     */
+    protected function find($selector, $locator, $exception = false) {
+
+        // Generic info.
+        if (!$exception) {
+
+            // With named selectors we can be more specific.
+            if ($selector == 'named') {
+                $exceptiontype = $locator[0];
+                $exceptionlocator = $locator[1];
+            } else {
+                $exceptiontype = $selector;
+                $exceptionlocator = $locator;
+            }
+
+            $exception = new ElementNotFoundException($this->getSession(), $exceptiontype, null, $exceptionlocator);
+        }
+
+        // Waits for the node to appear if it exists, otherwise will timeout and throw the provided exception.
+        return $this->spin(
+            function($context, $args) {
+                return $context->getSession()->getPage()->find($args[0], $args[1]);
+            },
+            array($selector, $locator),
+            self::TIMEOUT,
+            $exception
+       );
+    }
+
+    /**
+     * Finds DOM nodes in the page using named selectors.
+     *
+     * The point of using this method instead of Mink ones is the spin
+     * method of behat_base::find() that looks for the element until it
+     * is available or it timeouts, this avoids the false failures received
+     * when selenium tries to execute commands on elements that are not
+     * ready to be used.
+     *
+     * All steps that requires elements to be available before interact with
+     * them should use one of the find* methods.
+     *
+     * The methods calls requires a {'find_' . $elementtype}($locator)
+     * format, like find_link($locator), find_select($locator),
+     * find_button($locator)...
+     *
+     * @link http://mink.behat.org/#named-selectors
+     * @throws coding_exception
+     * @param string $method The name of the called method
+     * @param mixed $arguments
+     * @return NodeElement
+     */
+    public function __call($name, $arguments) {
+
+        if (substr($name, 0, 5) !== 'find_') {
+            throw new coding_exception('The "' . $name . '" method does not exist');
+        }
+
+        // Only the named selector identifier.
+        $cleanname = substr($name, 5);
+
+        // All named selectors shares the interface.
+        if (count($arguments) !== 1) {
+            throw new coding_exception('The "' . $cleanname . '" named selector needs the locator as it\'s single argument');
+        }
+
+        // Redirecting execution to the find method with the specified selector.
+        // It will detect if it's pointing to an unexisting named selector.
+        return $this->find('named',
+            array(
+                $cleanname,
+                $this->getSession()->getSelectorsHandler()->xpathLiteral($arguments[0])
+            )
+        );
+    }
+
     /**
      * Executes the passed closure until returns true or time outs.
      *
@@ -91,15 +162,20 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
      * - Must return something != false if finishes as expected, this will be the (mixed) value
      * returned by spin()
      *
-     * Requires the exception to provide more accurate feedback to tests writers.
+     * The arguments of the closure are mixed, use $args depending on your needs.
+     *
+     * You can provide an exception to give more accurate feedback to tests writers, otherwise the
+     * closure exception will be used, but you must provide an exception if the closure does not throws
+     * an exception.
      *
-     * @throws Exception If it timeouts without receiving something != false from the closure
-     * @param Closure $lambda The function to execute.
-     * @param Exception $exception The exception to throw in case it time outs.
-     * @param array $args Arguments to pass to the closure
+     * @throws Exception            If it timeouts without receiving something != false from the closure
+     * @param  Closure   $lambda    The function to execute.
+     * @param  mixed     $args      Arguments to pass to the closure
+     * @param  int       $timeout   Timeout
+     * @param  Exception $exception The exception to throw in case it time outs.
      * @return mixed The value returned by the closure
      */
-    protected function spin($lambda, $exception, $args, $timeout = false) {
+    protected function spin($lambda, $args = false, $timeout = false, $exception = false) {
 
         // Using default timeout which is pretty high.
         if (!$timeout) {
@@ -112,11 +188,18 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
             try {
 
                 // We don't check with !== because most of the time closures will return
-                // direct Behat methods returns and we are not sure it will be always (bool)false.
+                // direct Behat methods returns and we are not sure it will be always (bool)false
+                // if it just runs the behat method without returning anything $return == null.
                 if ($return = $lambda($this, $args)) {
                     return $return;
                 }
             } catch(Exception $e) {
+
+                // We would use the first closure exception if no exception has been provided.
+                if (!$exception) {
+                    $exception = $e;
+                }
+
                 // We wait until no exception is thrown or timeout expires.
                 continue;
             }
@@ -124,6 +207,11 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
             sleep(1);
         }
 
+        // Using coding_exception as is a development issue if no exception has been provided.
+        if (!$exception) {
+            $exception = new coding_exception('spin method requires an exception if the closure doesn\'t throw an exception itself');
+        }
+
         // Throwing exception to the user.
         throw $exception;
     }
index 1c45dbd..e0ec4d4 100644 (file)
@@ -189,6 +189,9 @@ class behat_config_manager {
                         'features' => $features,
                         'steps_definitions' => $stepsdefinitions
                     )
+                ),
+                'formatter' => array(
+                    'name' => 'progress'
                 )
             )
         );
index c2bf778..04e0b3c 100644 (file)
@@ -49,10 +49,21 @@ class behat_form_editor extends behat_form_field {
      */
     public function set_value($value) {
 
-        // Set the value to the iframe and save it to the textarea.
-        $editorid = $this->field->getAttribute('id');
-        $this->session->executeScript('tinyMCE.get("'.$editorid.'").setContent("' . $value . '");');
-        $this->session->executeScript('tinyMCE.get("'.$editorid.'").save();');
+        // If tinyMCE var exists means that we are using that editor.
+        if ($this->is_editor_available()) {
+
+            // Set the value to the iframe and save it to the textarea.
+            $editorid = $this->field->getAttribute('id');
+
+            $this->session->executeScript('
+                tinyMCE.get("'.$editorid.'").setContent("' . $value . '");
+                tinyMCE.get("'.$editorid.'").save();
+            ');
+
+        } else {
+            // Set the value to a textarea otherwise.
+            parent::set_value($value);
+        }
     }
 
     /**
@@ -62,12 +73,36 @@ class behat_form_editor extends behat_form_field {
      */
     public function get_value() {
 
-        // Save the current iframe value in case default value has been edited.
-        $editorid = $this->field->getAttribute('id');
-        $this->session->executeScript('tinyMCE.get("'.$editorid.'").save();');
+        // If tinyMCE var exists means that we are using that editor.
+        if ($this->is_editor_available()) {
+
+            // Save the current iframe value in case default value has been edited.
+            $editorid = $this->field->getAttribute('id');
+            $this->session->executeScript('tinyMCE.get("'.$editorid.'").save();');
+        }
 
         return $this->field->getValue();
     }
 
+    /**
+     * Returns if the HTML editor is available.
+     *
+     * The editor availability depends on the driver running the tests; Goutte
+     * can not execute Javascript, also some Moodle settings disables the HTML
+     * editor.
+     *
+     * @return bool
+     */
+    protected function is_editor_available() {
+
+        // Non-JS drivers throws exceptions when running JS.
+        try {
+            $available = $this->session->evaluateScript('return (typeof tinyMCE != "undefined")');
+        } catch (Exception $e) {
+            $available = false;
+        }
+
+        return $available;
+    }
 }
 
index 79e808b..0ae0ae3 100644 (file)
@@ -216,7 +216,11 @@ function cron_run() {
 
         // note: we can not send emails to suspended accounts
         foreach ($newusers as $newuser) {
-            if (setnew_password_and_mail($newuser)) {
+            // Use a low cost factor when generating bcrypt hash otherwise
+            // hashing would be slow when emailing lots of users. Hashes
+            // will be automatically updated to a higher cost factor the first
+            // time the user logs in.
+            if (setnew_password_and_mail($newuser, true)) {
                 unset_user_preference('create_password', $newuser);
                 set_user_preference('auth_forcepasswordchange', 1, $newuser);
             } else {
index b246000..0d4a626 100644 (file)
@@ -1747,7 +1747,13 @@ class css_rule {
         $css = $this->out();
         $errors = array();
         foreach ($this->styles as $style) {
-            if ($style->has_error()) {
+            if (is_array($style)) {
+                foreach ($style as $s) {
+                    if ($style instanceof css_style && $style->has_error()) {
+                        $errors[] = "  * ".$style->get_last_error();
+                    }
+                }
+            } else if ($style instanceof css_style && $style->has_error()) {
                 $errors[] = "  * ".$style->get_last_error();
             }
         }
index a1e5ab9..f54eae6 100644 (file)
@@ -57,7 +57,9 @@ function get_admin() {
     static $mainadmin = null;
     static $prevadmins = null;
 
-    if (empty($CFG->siteadmins)) {  // Should not happen on an ordinary site.
+    if (empty($CFG->siteadmins)) {
+        // Should not happen on an ordinary site.
+        // It does however happen during unit tests.
         return false;
     }
 
index d4eb4fc..1602999 100644 (file)
         <FIELD NAME="suspended" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="suspended flag prevents users to log in"/>
         <FIELD NAME="mnethostid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="username" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="password" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="idnumber" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="firstname" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="lastname" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
index a2a8d93..ddf6a4f 100644 (file)
@@ -273,6 +273,15 @@ $functions = array(
         'capabilities'=> 'moodle/user:create',
     ),
 
+    'core_user_get_users' => array(
+        'classname'   => 'core_user_external',
+        'methodname'  => 'get_users',
+        'classpath'   => 'user/externallib.php',
+        'description' => 'search for users matching the parameters',
+        'type'        => 'read',
+        'capabilities'=> 'moodle/user:viewdetails, moodle/user:viewhiddendetails, moodle/course:useremail, moodle/user:update',
+    ),
+
     'moodle_user_get_users_by_id' => array(
         'classname'   => 'core_user_external',
         'methodname'  => 'get_users_by_id',
index 2a307bb..201a7a1 100644 (file)
@@ -1565,6 +1565,28 @@ function xmldb_main_upgrade($oldversion) {
     }
 
     if ($oldversion < 2013021100.01) {
+
+        // Changing precision of field password on table user to (255).
+        $table = new xmldb_table('user');
+        $field = new xmldb_field('password', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'username');
+
+        // Launch change of precision for field password.
+        $dbman->change_field_precision($table, $field);
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2013021100.01);
+    }
+
+    if ($oldversion < 2013021800.00) {
+        // Add the site identifier to the cache config's file.
+        $siteidentifier = $DB->get_field('config', 'value', array('name' => 'siteidentifier'));
+        cache_helper::update_site_identifier($siteidentifier);
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2013021800.00);
+    }
+
+    if ($oldversion < 2013021801.00) {
         // Fixing possible wrong MIME types for SMART Notebook files.
         $extensions = array('%.gallery', '%.galleryitem', '%.gallerycollection', '%.nbk', '%.notebook', '%.xbk');
         $select = $DB->sql_like('filename', '?', false);
@@ -1577,9 +1599,65 @@ function xmldb_main_upgrade($oldversion) {
                 array($extension)
             );
         }
+        upgrade_main_savepoint(true, 2013021801.00);
+    }
+
+    if ($oldversion < 2013021801.01) {
+        // Retrieve the list of course_sections as a recordset to save memory
+        $coursesections = $DB->get_recordset('course_sections', null, 'course, id', 'id, course, sequence');
+        foreach ($coursesections as $coursesection) {
+            // Retrieve all of the actual modules in this course and section combination to reduce DB calls
+            $actualsectionmodules = $DB->get_records('course_modules',
+                    array('course' => $coursesection->course, 'section' => $coursesection->id), '', 'id, section');
+
+            // Break out the current sequence so that we can compare it
+            $currentsequence = explode(',', $coursesection->sequence);
+            $newsequence = array();
+
+            // Check each of the modules in the current sequence
+            foreach ($currentsequence as $module) {
+                if (isset($actualsectionmodules[$module])) {
+                    $newsequence[] = $module;
+                    // We unset the actualsectionmodules so that we don't get duplicates and that we can add orphaned
+                    // modules later
+                    unset($actualsectionmodules[$module]);
+                }
+            }
+
+            // Append any modules which have somehow been orphaned
+            foreach ($actualsectionmodules as $module) {
+                $newsequence[] = $module->id;
+            }
+
+            // Piece it all back together
+            $sequence = implode(',', $newsequence);
+
+            // Only update if there have been changes
+            if ($sequence !== $coursesection->sequence) {
+                $coursesection->sequence = $sequence;
+                $DB->update_record('course_sections', $coursesection);
+
+                // And clear the sectioncache and modinfo cache - they'll be regenerated on next use
+                $course = new stdClass();
+                $course->id = $coursesection->course;
+                $course->sectioncache = null;
+                $course->modinfo = null;
+                $DB->update_record('course', $course);
+            }
+        }
+        $coursesections->close();
 
         // Main savepoint reached.
-        upgrade_main_savepoint(true, 2013021100.01);
+        upgrade_main_savepoint(true, 2013021801.01);
+    }
+
+    if ($oldversion < 2013021902.00) {
+        // ISO country change: Netherlands Antilles is split into BQ, CW & SX
+        // http://www.iso.org/iso/iso_3166-1_newsletter_vi-8_split_of_the_dutch_antilles_final-en.pdf
+        $sql = "UPDATE {user} SET country = '' WHERE country = ?";
+        $DB->execute($sql, array('AN'));
+
+        upgrade_main_savepoint(true, 2013021902.00);
     }
 
     return true;
index 4dd4446..3e54990 100644 (file)
@@ -2945,14 +2945,22 @@ class curl {
     }
 
     /**
-     * Set curl options
+     * Set curl options.
      *
-     * @param array $options If array is null, this function will
-     * reset the options to default value.
+     * Do not use the curl constants to define the options, pass a string
+     * corresponding to that constant. Ie. to set CURLOPT_MAXREDIRS, pass
+     * array('CURLOPT_MAXREDIRS' => 10) or array('maxredirs' => 10) to this method.
+     *
+     * @param array $options If array is null, this function will reset the options to default value.
+     * @return void
+     * @throws coding_exception If an option uses constant value instead of option name.
      */
     public function setopt($options = array()) {
         if (is_array($options)) {
-            foreach($options as $name => $val){
+            foreach ($options as $name => $val){
+                if (!is_string($name)) {
+                    throw new coding_exception('Curl options should be defined using strings, not constant values.');
+                }
                 if (stripos($name, 'CURLOPT_') === false) {
                     $name = strtoupper('CURLOPT_'.$name);
                 }
@@ -3077,11 +3085,9 @@ class curl {
             var_dump($this->header);
         }
 
-        // set options
+        // Set options.
         foreach($this->options as $name => $val) {
-            if (is_string($name)) {
-                $name = constant(strtoupper($name));
-            }
+            $name = constant(strtoupper($name));
             curl_setopt($curl, $name, $val);
         }
         return $curl;
@@ -4300,7 +4306,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
                 send_file_not_found();
             }
 
-            $bprecord = $DB->get_record('block_positions', array('blockinstanceid' => $context->instanceid), 'visible');
+            $bprecord = $DB->get_record('block_positions', array('contextid' => $context->id, 'blockinstanceid' => $context->instanceid));
             // User can't access file, if block is hidden or doesn't have block:view capability
             if (($bprecord && !$bprecord->visible) || !has_capability('moodle/block:view', $context)) {
                  send_file_not_found();
index b8760c0..a01de83 100644 (file)
@@ -90,6 +90,25 @@ class filter_manager {
         return self::$singletoninstance;
     }
 
+    /**
+     * Resets the caches, usually to be called between unit tests
+     */
+    public static function reset_caches() {
+        if (self::$singletoninstance) {
+            self::$singletoninstance->unload_all_filters();
+        }
+        self::$singletoninstance = null;
+    }
+
+    /**
+     * Unloads all filters and other cached information
+     */
+    protected function unload_all_filters() {
+        $this->textfilters = array();
+        $this->stringfilters = array();
+        $this->stringfilternames = array();
+    }
+
     /**
      * Load all the filters required by this context.
      *
@@ -286,6 +305,16 @@ class performance_measuring_filter_manager extends filter_manager {
     protected $textsfiltered = 0;
     protected $stringsfiltered = 0;
 
+    /**
+     * Unloads all filters and other cached information
+     */
+    protected function unload_all_filters() {
+        parent::unload_all_filters();
+        $this->filterscreated = 0;
+        $this->textsfiltered = 0;
+        $this->stringsfiltered = 0;
+    }
+
     /**
      * @param string $filtername
      * @param object $context
index 1f7856b..e9f9490 100644 (file)
@@ -6,58 +6,6 @@
 //&n