Merge branch 'wip-MDL-42833-master' of git://github.com/marinaglancy/moodle
authorDamyon Wiese <damyon@moodle.com>
Wed, 13 Nov 2013 07:57:07 +0000 (15:57 +0800)
committerDamyon Wiese <damyon@moodle.com>
Wed, 13 Nov 2013 07:57:07 +0000 (15:57 +0800)
284 files changed:
admin/settings/development.php
admin/tool/behat/lang/en/tool_behat.php
admin/tool/langimport/lang/en/tool_langimport.php
backup/util/ui/tests/behat/duplicate_activities.feature
badges/cron.php
badges/edit_form.php
badges/tests/badgeslib_test.php
blocks/mentees/block_mentees.php
blocks/tests/behat/behat_blocks.php
blocks/tests/behat/configure_block_throughout_site.feature
blocks/tests/behat/manage_blocks.feature
blocks/tests/behat/return_block_original_state.feature
cache/stores/memcache/lib.php
cache/stores/memcached/lib.php
course/ajax/management.php
course/classes/management_renderer.php
course/format/lib.php
course/lib.php
course/management.php
course/renderer.php
course/resources.php
course/tests/behat/activities_visibility_icons.feature
course/tests/behat/behat_course.php
course/tests/behat/category_resort.feature
course/tests/behat/course_category_management_listing.feature
course/tests/behat/course_change_visibility.feature
course/yui/build/moodle-course-management/moodle-course-management-debug.js
course/yui/build/moodle-course-management/moodle-course-management-min.js
course/yui/build/moodle-course-management/moodle-course-management.js
course/yui/dragdrop/dragdrop.js
course/yui/src/management/js/category.js
course/yui/src/management/js/console.js
course/yui/src/management/js/item.js
course/yui/toolboxes/toolboxes.js
group/overview.php
group/tests/behat/create_groups.feature
group/tests/behat/delete_groups.feature [new file with mode: 0644]
group/tests/behat/update_groups.feature [new file with mode: 0644]
lang/en/admin.php
lang/en/backup.php
lang/en/badges.php
lang/en/form.php
lang/en/moodle.php
lang/en/question.php
lib/badgeslib.php
lib/boxlib.php
lib/classes/collator.php
lib/classes/plugin_manager.php
lib/classes/user.php
lib/coursecatlib.php
lib/db/messages.php
lib/db/upgrade.php
lib/ddl/sql_generator.php
lib/ddl/tests/ddl_test.php
lib/dml/mssql_native_moodle_database.php
lib/dml/oci_native_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/editor/tinymce/db/upgrade.php
lib/editor/tinymce/plugins/pdw/readme_moodle.txt
lib/editor/tinymce/plugins/pdw/tinymce/editor_plugin.js
lib/editor/tinymce/settings.php
lib/editor/tinymce/styles.css
lib/editor/tinymce/tiny_mce/3.5.8/themes/advanced/skins/moodle/img/button_bg.png
lib/editor/tinymce/tiny_mce/3.5.8/themes/advanced/skins/moodle/ui.css
lib/editor/tinymce/version.php
lib/editorlib.php
lib/filestorage/mbz_packer.php
lib/filestorage/tests/file_storage_test.php
lib/filestorage/tests/mbz_packer_test.php
lib/filestorage/tests/tgz_packer_test.php
lib/filestorage/tgz_packer.php
lib/flickrlib.php
lib/ldaplib.php
lib/moodlelib.php
lib/navigationlib.php
lib/oauthlib.php
lib/outputrenderers.php
lib/tablelib.php
lib/testing/generator/repository_generator.php
lib/tests/behat/behat_general.php
lib/tests/moodlelib_test.php
lib/upgrade.txt
lib/weblib.php
lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-debug.js
lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-min.js
lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu.js
lib/yui/build/moodle-core-dock-loader/moodle-core-dock-loader-debug.js
lib/yui/build/moodle-core-dock-loader/moodle-core-dock-loader-min.js
lib/yui/build/moodle-core-dock-loader/moodle-core-dock-loader.js
lib/yui/build/moodle-core-dock/moodle-core-dock-debug.js
lib/yui/build/moodle-core-dock/moodle-core-dock-min.js
lib/yui/build/moodle-core-dock/moodle-core-dock.js
lib/yui/dragdrop/dragdrop.js
lib/yui/src/actionmenu/js/actionmenu.js
lib/yui/src/dock/js/block.js
lib/yui/src/dock/js/loader.js
message/index.php
message/lib.php
mod/assign/feedback/editpdf/classes/pdf.php
mod/assign/feedback/editpdf/db/install.php [new file with mode: 0644]
mod/assign/feedback/editpdf/db/upgrade.php [new file with mode: 0644]
mod/assign/feedback/editpdf/lang/en/assignfeedback_editpdf.php
mod/assign/feedback/editpdf/pix/cross.png [new file with mode: 0644]
mod/assign/feedback/editpdf/pix/sad.png [new file with mode: 0644]
mod/assign/feedback/editpdf/pix/smile.png [new file with mode: 0644]
mod/assign/feedback/editpdf/pix/tick.png [new file with mode: 0644]
mod/assign/feedback/editpdf/settings.php
mod/assign/feedback/editpdf/styles.css
mod/assign/feedback/editpdf/version.php
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotation.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotationhighlight.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotationline.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotationoval.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotationpen.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotationrectangle.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotationstamp.js
mod/assign/feedback/editpdf/yui/src/editor/js/comment.js
mod/assign/feedback/editpdf/yui/src/editor/js/commentsearch.js
mod/assign/feedback/editpdf/yui/src/editor/js/dropdown.js
mod/assign/feedback/editpdf/yui/src/editor/js/editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/rect.js
mod/assign/feedback/editpdf/yui/src/editor/js/stamppicker.js
mod/assign/lang/en/assign.php
mod/assign/locallib.php
mod/assignment/index.php
mod/book/index.php
mod/chat/index.php
mod/choice/index.php
mod/choice/lang/en/choice.php
mod/data/index.php
mod/data/view.php
mod/feedback/index.php
mod/feedback/styles.css
mod/folder/index.php
mod/forum/lib.php
mod/glossary/index.php
mod/imscp/index.php
mod/lesson/index.php
mod/lti/classes/plugininfo/ltisource.php [new file with mode: 0644]
mod/lti/db/subplugins.php [new file with mode: 0644]
mod/lti/grade.php
mod/lti/index.php
mod/lti/lang/en/lti.php
mod/lti/lib.php
mod/lti/mod_form.php
mod/lti/request_tool.php
mod/lti/return.php
mod/lti/service.php
mod/lti/servicelib.php
mod/lti/source/readme.txt [new file with mode: 0644]
mod/lti/view.php
mod/page/index.php
mod/quiz/addrandom.php
mod/quiz/comment.php
mod/quiz/index.php
mod/quiz/lang/en/quiz.php
mod/quiz/locallib.php
mod/quiz/overridedelete.php
mod/quiz/overrideedit.php
mod/quiz/overrides.php
mod/quiz/renderer.php
mod/quiz/report/attemptsreport_table.php
mod/quiz/report/default.php
mod/quiz/report/grading/gradingsettings_form.php
mod/quiz/report/grading/report.php
mod/quiz/report/overview/report.php
mod/quiz/report/reportlib.php
mod/quiz/report/responses/responses_table.php
mod/quiz/report/statistics/classes/calculator.php
mod/quiz/report/statistics/report.php
mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php
mod/quiz/report/upgrade.txt
mod/quiz/reviewquestion.php
mod/quiz/startattempt.php
mod/quiz/styles.css
mod/quiz/upgrade.txt
mod/resource/lang/en/resource.php
mod/scorm/index.php
mod/scorm/module.js
mod/scorm/report/basic/report.php
mod/scorm/report/interactions/report.php
mod/scorm/report/objectives/report.php
mod/scorm/tests/behat/add_scorm.feature
mod/scorm/thirdpartylibs.xml [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview-sortable/assets/moodle-mod_scorm-treeview-core.css [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview-sortable/assets/skins/sam/folder.png [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview-sortable/assets/skins/sam/folder@2x.png [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview-sortable/assets/skins/sam/item.png [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview-sortable/assets/skins/sam/item@2x.png [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview-sortable/assets/skins/sam/moodle-mod_scorm-treeview-skin.css [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview-sortable/assets/skins/sam/moodle-mod_scorm-treeview.css [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview-sortable/moodle-mod_scorm-treeview-sortable-debug.js [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview-sortable/moodle-mod_scorm-treeview-sortable-min.js [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview-sortable/moodle-mod_scorm-treeview-sortable.js [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview/assets/moodle-mod_scorm-treeview-core.css [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview/assets/skins/sam/folder.png [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview/assets/skins/sam/folder@2x.png [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview/assets/skins/sam/item.png [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview/assets/skins/sam/item@2x.png [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview/assets/skins/sam/moodle-mod_scorm-treeview-skin.css [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview/assets/skins/sam/moodle-mod_scorm-treeview.css [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview/moodle-mod_scorm-treeview-debug.js [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview/moodle-mod_scorm-treeview-min.js [new file with mode: 0644]
mod/scorm/yui/build/moodle-mod_scorm-treeview/moodle-mod_scorm-treeview.js [new file with mode: 0644]
mod/scorm/yui/src/treeview/assets/moodle-mod_scorm-treeview-core.css [new file with mode: 0644]
mod/scorm/yui/src/treeview/assets/skins/sam/folder.png [new file with mode: 0644]
mod/scorm/yui/src/treeview/assets/skins/sam/folder@2x.png [new file with mode: 0644]
mod/scorm/yui/src/treeview/assets/skins/sam/item.png [new file with mode: 0644]
mod/scorm/yui/src/treeview/assets/skins/sam/item@2x.png [new file with mode: 0644]
mod/scorm/yui/src/treeview/assets/skins/sam/moodle-mod_scorm-treeview-skin.css [new file with mode: 0644]
mod/scorm/yui/src/treeview/assets/skins/sam/moodle-mod_scorm-treeview.css [new file with mode: 0644]
mod/scorm/yui/src/treeview/build.json [new file with mode: 0644]
mod/scorm/yui/src/treeview/js/gallery-sm-treeview-debug.js [new file with mode: 0644]
mod/scorm/yui/src/treeview/js/gallery-sm-treeview-sortable-debug.js [new file with mode: 0644]
mod/scorm/yui/src/treeview/js/treeview-sortable.js [new file with mode: 0644]
mod/scorm/yui/src/treeview/js/treeview.js [new file with mode: 0644]
mod/scorm/yui/src/treeview/meta/sm-treeview.json [new file with mode: 0644]
mod/scorm/yui/src/treeview/readme_moodle.txt [new file with mode: 0644]
mod/survey/index.php
mod/survey/lang/en/survey.php
mod/url/index.php
mod/url/lang/en/url.php
mod/wiki/index.php
mod/workshop/allocation/random/lang/en/workshopallocation_random.php
mod/workshop/index.php
mod/workshop/lib.php
mod/workshop/locallib.php
mod/workshop/renderer.php
mod/workshop/view.php
portfolio/boxnet/db/upgrade.php [new file with mode: 0644]
portfolio/boxnet/db/upgradelib.php [new file with mode: 0644]
portfolio/boxnet/lang/en/portfolio_boxnet.php
portfolio/boxnet/lib.php
portfolio/boxnet/version.php
portfolio/flickr/lib.php
question/behaviour/deferredcbm/behaviourtype.php
question/behaviour/deferredcbm/lang/en/qbehaviour_deferredcbm.php
question/editlib.php
question/engine/datalib.php
question/engine/lib.php
question/engine/renderer.php
question/engine/upgrade.txt
question/type/description/tests/walkthrough_test.php
question/type/essay/tests/helper.php
question/type/essay/tests/walkthrough_test.php
question/type/numerical/lang/en/qtype_numerical.php
question/type/random/lang/en/qtype_random.php
question/type/truefalse/tests/walkthrough_test.php
repository/boxnet/cli/migrationv1.php [new file with mode: 0644]
repository/boxnet/db/upgrade.php [new file with mode: 0644]
repository/boxnet/db/upgradelib.php [new file with mode: 0644]
repository/boxnet/lang/en/repository_boxnet.php
repository/boxnet/lib.php
repository/boxnet/locallib.php [new file with mode: 0644]
repository/boxnet/migrationv1.php [new file with mode: 0644]
repository/boxnet/tests/generator/lib.php
repository/boxnet/version.php
repository/recent/lang/en/repository_recent.php
repository/skydrive/lib.php
theme/afterburner/lib.php
theme/anomaly/renderers.php
theme/base/style/core.css
theme/base/style/course.css
theme/base/style/dock.css
theme/base/style/filemanager.css
theme/base/style/question.css
theme/bootstrapbase/less/editor.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/course.less
theme/bootstrapbase/less/moodle/editor.less [new file with mode: 0644]
theme/bootstrapbase/less/moodle/filemanager.less
theme/bootstrapbase/less/moodle/forms.less
theme/bootstrapbase/less/moodle/question.less
theme/bootstrapbase/style/editor.css
theme/bootstrapbase/style/moodle.css
theme/brick/lib.php
theme/magazine/lib.php
user/editlib.php
user/tests/editlib_test.php
version.php

index b99d519..a1ad6e0 100644 (file)
@@ -27,6 +27,9 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configcheckbox('enabletgzbackups',
             new lang_string('enabletgzbackups', 'admin'),
             new lang_string('enabletgzbackups_desc', 'admin'), 0));
+    $temp->add(new admin_setting_php_extension_enabled('zlibenabled',
+            get_string('zlibenabled', 'admin'),
+            get_string('enabletgzbackups_nozlib', 'admin'), 'zlib'));
 
     $ADMIN->add('experimental', $temp);
 
index a67581d..d8b2b51 100644 (file)
@@ -38,7 +38,7 @@ $string['stepsdefinitionscontains'] = 'Contains';
 $string['stepsdefinitionsfilters'] = 'Steps definitions';
 $string['stepsdefinitionstype'] = 'Type';
 $string['theninfo'] = 'Then. Checkings to ensure the outcomes are the expected ones';
-$string['unknownexceptioninfo'] = 'There was a problem with Selenium or the browser, try to upgrade Selenium to the latest version. Error: ';
+$string['unknownexceptioninfo'] = 'There was a problem with Selenium or your browser. Please ensure you are using the latest version of Selenium. Error:';
 $string['viewsteps'] = 'Filter';
 $string['wheninfo'] = 'When. Actions that provokes an event';
 $string['wrongbehatsetup'] = 'Something is wrong with behat setup, ensure:<ul>
index 87dee88..d31ec68 100644 (file)
@@ -38,7 +38,7 @@ $string['nolangupdateneeded'] = 'All your language packs are up to date, no upda
 $string['pluginname'] = 'Language packs';
 $string['purgestringcaches'] = 'Purge string caches';
 $string['remotelangnotavailable'] = 'Because Moodle cannot connect to download.moodle.org, it is not possible for language packs to be installed automatically. Please download the appropriate ZIP file(s) from <a href="http://download.moodle.org/langpack/">download.moodle.org/langpack</a>, copy them to your {$a} directory and unzip them manually.';
-$string['uninstall'] = 'Uninstall selected language pack(s)';
+$string['uninstall'] = 'Uninstall selected language pack';
 $string['uninstallconfirm'] = 'You are about to completely uninstall language pack {$a}, are you sure?';
 $string['updatelangs'] = 'Update all installed language packs';
 
index 8fe100c..9675a62 100644 (file)
@@ -23,6 +23,7 @@ Feature: Duplicate activities
       | Description | Test database description |
     And I open "Test database name" actions menu
     When I click on "Duplicate" "link" in the "Test database name" activity
+    And I open "Test database name" actions menu
     And I click on "Edit settings" "link" in the "Test database name" activity
     And I fill the moodle form with:
       | Name | Original database name |
index a1dcfcc..a0bee0f 100644 (file)
@@ -116,14 +116,9 @@ function badge_message_cron() {
  * @param object $badge A badge which is notified about.
  */
 function badge_assemble_notification(stdClass $badge) {
-    global $CFG, $DB;
-
-    $admin = get_admin();
-    $userfrom = new stdClass();
-    $userfrom->id = $admin->id;
-    $userfrom->email = !empty($CFG->badges_defaultissuercontact) ? $CFG->badges_defaultissuercontact : $admin->email;
-    $userfrom->firstname = !empty($CFG->badges_defaultissuername) ? $CFG->badges_defaultissuername : $admin->firstname;
-    $userfrom->lastname = !empty($CFG->badges_defaultissuername) ? '' : $admin->lastname;
+    global $DB;
+
+    $userfrom = core_user::get_noreply_user();
     $userfrom->maildisplay = true;
 
     if ($msgs = $DB->get_records_select('badge_issued', 'issuernotified IS NULL AND badgeid = ?', array($badge->id))) {
@@ -147,15 +142,15 @@ function badge_assemble_notification(stdClass $badge) {
         // Create a message object.
         $eventdata = new stdClass();
         $eventdata->component         = 'moodle';
-        $eventdata->name              = 'instantmessage';
+        $eventdata->name              = 'badgecreatornotice';
         $eventdata->userfrom          = $userfrom;
         $eventdata->userto            = $creator;
         $eventdata->notification      = 1;
         $eventdata->subject           = $creatorsubject;
-        $eventdata->fullmessage       = $creatormessage;
+        $eventdata->fullmessage       = format_text_email($creatormessage, FORMAT_HTML);
         $eventdata->fullmessageformat = FORMAT_PLAIN;
-        $eventdata->fullmessagehtml   = format_text($creatormessage, FORMAT_HTML);
-        $eventdata->smallmessage      = '';
+        $eventdata->fullmessagehtml   = $creatormessage;
+        $eventdata->smallmessage      = $creatorsubject;
 
         message_send($eventdata);
     }
index 7ebe2f4..24e52d7 100644 (file)
@@ -225,6 +225,9 @@ class edit_message_form extends moodleform {
 
         $mform->addElement('advcheckbox', 'attachment', get_string('attachment', 'badges'), '', null, array(0, 1));
         $mform->addHelpButton('attachment', 'attachment', 'badges');
+        if (empty($CFG->allowattachments)) {
+            $mform->freeze('attachment');
+        }
 
         $options = array(
                 BADGE_MESSAGE_NEVER   => get_string('never'),
index cc1a0d4..b70d377 100644 (file)
@@ -166,6 +166,7 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
     }
 
     public function test_badge_awards() {
+        $this->preventResetByRollback(); // Messaging is not compatible with transactions.
         $badge = new badge($this->badgeid);
         $user1 = $this->getDataGenerator()->create_user();
 
@@ -225,6 +226,7 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
      * Test badges observer when course module completion event id fired.
      */
     public function test_badges_observer_course_module_criteria_review() {
+        $this->preventResetByRollback(); // Messaging is not compatible with transactions.
         $badge = new badge($this->coursebadge);
         $this->assertFalse($badge->is_issued($this->user->id));
 
@@ -257,6 +259,7 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
      * Test badges observer when course_completed event is fired.
      */
     public function test_badges_observer_course_criteria_review() {
+        $this->preventResetByRollback(); // Messaging is not compatible with transactions.
         $badge = new badge($this->coursebadge);
         $this->assertFalse($badge->is_issued($this->user->id));
 
@@ -282,6 +285,7 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
      * Test badges observer when user_updated event is fired.
      */
     public function test_badges_observer_profile_criteria_review() {
+        $this->preventResetByRollback(); // Messaging is not compatible with transactions.
         $badge = new badge($this->coursebadge);
         $this->assertFalse($badge->is_issued($this->user->id));
 
@@ -304,6 +308,7 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
      * Test badges assertion generated when a badge is issued.
      */
     public function test_badges_assertion() {
+        $this->preventResetByRollback(); // Messaging is not compatible with transactions.
         $badge = new badge($this->coursebadge);
         $this->assertFalse($badge->is_issued($this->user->id));
 
index aa340af..95c3059 100644 (file)
@@ -46,5 +46,14 @@ class block_mentees extends block_base {
 
         return $this->content;
     }
+
+    /**
+     * Returns true if the block can be docked.
+     * The mentees block can only be docked if it has a non-empty title.
+     * @return bool
+     */
+    public function instance_can_be_docked() {
+        return parent::instance_can_be_docked() && isset($this->config->title) && !empty($this->config->title);
+    }
 }
 
index 42fe9a1..36d8be2 100644 (file)
@@ -58,4 +58,43 @@ class behat_blocks extends behat_base {
         return $steps;
     }
 
+    /**
+     * Opens a block's actions menu if it is not already opened.
+     *
+     * @Given /^I open the "(?P<block_name_string>(?:[^"]|\\")*)" blocks action menu$/
+     * @throws DriverException The step is not available when Javascript is disabled
+     * @param string $blockname
+     * @return Given
+     */
+    public function i_open_the_blocks_action_menu($blockname) {
+
+        if (!$this->running_javascript()) {
+            throw new DriverException('Blocks action menu not available when Javascript is disabled');
+        }
+
+        // If it is already opened we do nothing.
+        $blocknode = $this->get_block_node($blockname);
+        $classes = array_flip(explode(' ', $blocknode->getAttribute('class')));
+        if (!empty($classes['action-menu-shown'])) {
+            return;
+        }
+
+        return new Given('I click on "a[role=\'menuitem\']" "css_element" in the "' . $this->escape($blockname) . '" "block"');
+    }
+
+    /**
+     * Returns the DOM node of the block from <div>.
+     *
+     * @throws ElementNotFoundException Thrown by behat_base::find
+     * @param string $blockname The block name
+     * @return NodeElement
+     */
+    protected function get_block_node($blockname) {
+
+        $blockname = $this->getSession()->getSelectorsHandler()->xpathLiteral($blockname);
+        $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' block ')][contains(., $blockname)]";
+
+        return $this->find('xpath', $xpath);
+    }
+
 }
index af2bc33..bc1c316 100644 (file)
@@ -18,7 +18,7 @@ Feature: Add and configure blocks throughout the site
     And I log in as "manager1"
     And I follow "Turn editing on"
     And I add the "Comments" block
-    And I click on "Actions" "link" in the "Comments" "block"
+    And I open the "Comments" blocks action menu
     And I follow "Configure Comments block"
     And I fill the moodle form with:
       | Page contexts | Display throughout the entire site |
@@ -27,7 +27,7 @@ Feature: Add and configure blocks throughout the site
     Then I should see "Comments" in the "Comments" "block"
     And I should see "Save comment" in the "Comments" "block"
     And I am on homepage
-    And I click on "Actions" "link" in the "Comments" "block"
+    And I open the "Comments" blocks action menu
     And I follow "Configure Comments block"
     And I fill the moodle form with:
       | Default weight | -10 (first) |
index 2c3cb7f..dea4dcc 100644 (file)
@@ -42,6 +42,7 @@ Feature: Block appearances
     And I follow "Course 1"
     And I follow "Turn editing on"
     And I add the "Comments" block
+    And I open the "Comments" blocks action menu
     And I follow "Configure Comments block"
     And I fill the moodle form with:
       | Display on page types | Any page |
@@ -52,6 +53,7 @@ Feature: Block appearances
     When I follow "Test survey name"
     Then I should see "Comments" in the "Comments" "block"
     And I follow "Course 1"
+    And I open the "Comments" blocks action menu
     And I follow "Configure Comments block"
     And I fill the moodle form with:
       | Display on page types | Any course page |
@@ -63,6 +65,7 @@ Feature: Block appearances
   @javascript
   Scenario: Block settings can be modified so that a block can be hidden or moved
     When I follow "Test book name"
+    And I open the "Comments" blocks action menu
     And I follow "Configure Comments block"
     And I fill the moodle form with:
       | Visible | No |
@@ -72,6 +75,7 @@ Feature: Block appearances
     Then I should not see "Comments"
     And I expand "Course administration" node
     And I follow "Turn editing on"
+    And I open the "Comments" blocks action menu
     And I follow "Configure Comments block"
     And I fill the moodle form with:
       | Visible | Yes |
index 8605ccc..af9bce2 100644 (file)
@@ -15,7 +15,7 @@ Feature: The context of a block can always be returned to it's original state.
     And I add the "Tags" block
     Then I should see "Tags" in the "Tags" "block"
     And I click on "Participants" "link" in the "//li[p/span[contains(normalize-space(string(.)), 'Current course')]]" "xpath_element"
-    And I click on "Actions" "link" in the "Tags" "block"
+    And I open the "Tags" blocks action menu
     And I follow "Configure Tags block"
     And I fill the moodle form with:
       | Display on page types | Any page |
@@ -25,7 +25,7 @@ Feature: The context of a block can always be returned to it's original state.
       | Assignment name | Assignment1 |
       | Description | Description |
     And I follow "Assignment1"
-    And I click on "Actions" "link" in the "Tags" "block"
+    And I open the "Tags" blocks action menu
     And I follow "Configure Tags block"
     And I fill the moodle form with:
       | Display on page types | Any assignment module page |
@@ -41,7 +41,7 @@ Feature: The context of a block can always be returned to it's original state.
       | Description | Description |
     And I follow "Assignment2"
     And I should see "Tags" in the "Tags" "block"
-    And I click on "Actions" "link" in the "Tags" "block"
+    And I open the "Tags" blocks action menu
     And I follow "Configure Tags block"
     And I fill the moodle form with:
       | Display on page types | Any page |
index 1569da6..c71f108 100644 (file)
@@ -350,7 +350,12 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
         $lines = explode("\n", $data->servers);
         $servers = array();
         foreach ($lines as $line) {
-            $line = trim($line, ':');
+            // Trim surrounding colons and default whitespace.
+            $line = trim(trim($line), ":");
+            // Skip blank lines.
+            if ($line === '') {
+                continue;
+            }
             $servers[] = explode(':', $line, 3);
         }
         return array(
index 508628e..1bce7aa 100644 (file)
@@ -374,7 +374,12 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
         $lines = explode("\n", $data->servers);
         $servers = array();
         foreach ($lines as $line) {
-            $line = trim($line, ':');
+            // Trim surrounding colons and default whitespace.
+            $line = trim(trim($line), ":");
+            // Skip blank lines.
+            if ($line === '') {
+                continue;
+            }
             $servers[] = explode(':', $line, 3);
         }
         return array(
index 5574dfd..b3b8a0d 100644 (file)
@@ -140,7 +140,8 @@ switch ($action) {
         $categoryid = required_param('categoryid', PARAM_INT);
         /* @var core_course_management_renderer $renderer */
         $renderer = $PAGE->get_renderer('core_course', 'management');
-        $outcome->html = html_writer::start_tag('ul', array('class' => 'ml'));
+        $outcome->html = html_writer::start_tag('ul',
+            array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoriesof'.$categoryid));
         $coursecat = coursecat::get($categoryid);
         foreach ($coursecat->get_children() as $subcat) {
             $outcome->html .= $renderer->category_listitem($subcat, array(), $subcat->get_children_count());
index 87609ea..61973b1 100644 (file)
@@ -44,7 +44,19 @@ class core_course_management_renderer extends plugin_renderer_base {
     public function enhance_management_interface() {
         $this->page->requires->yui_module('moodle-course-management', 'M.course.management.init');
         $this->page->requires->strings_for_js(
-            array('show', 'hide', 'expand', 'collapse', 'confirmcoursemove', 'move', 'cancel', 'confirm'),
+            array(
+                'show',
+                'showcategory',
+                'hide',
+                'expand',
+                'expandcategory',
+                'collapse',
+                'collapsecategory',
+                'confirmcoursemove',
+                'move',
+                'cancel',
+                'confirm'
+            ),
             'moodle'
         );
     }
@@ -63,6 +75,8 @@ class core_course_management_renderer extends plugin_renderer_base {
             $html .= $this->heading($heading);
         }
         if ($viewmode !== null) {
+            $html .= html_writer::start_div();
+            $html .= $this->view_mode_selector(\core_course\management\helper::get_management_viewmodes(), $viewmode);
             if ($viewmode === 'courses') {
                 $categories = coursecat::make_categories_list(array('moodle/category:manage', 'moodle/course:create'));
                 $nothing = false;
@@ -73,7 +87,7 @@ class core_course_management_renderer extends plugin_renderer_base {
                 $select = new single_select($this->page->url, 'categoryid', $categories, $categoryid, $nothing);
                 $html .= $this->render($select);
             }
-            $html .= $this->view_mode_selector(\core_course\management\helper::get_management_viewmodes(), $viewmode);
+            $html .= html_writer::end_div();
         }
         $html .= html_writer::end_div();
         return $html;
@@ -183,8 +197,20 @@ class core_course_management_renderer extends plugin_renderer_base {
             'aria-expanded' => $isexpanded ? 'true' : 'false'
         );
         $text = $category->get_formatted_name();
+        if ($category->parent) {
+            $a = new stdClass;
+            $a->category = $text;
+            $a->parentcategory = $category->get_parent_coursecat()->get_formatted_name();
+            $textlabel = get_string('categorysubcategoryof', 'moodle', $a);
+        }
         $courseicon = $this->output->pix_icon('i/course', get_string('courses'));
-        $bcatinput = array('type' => 'checkbox', 'name' => 'bcat[]', 'value' => $category->id, 'class' => 'bulk-action-checkbox');
+        $bcatinput = array(
+            'type' => 'checkbox',
+            'name' => 'bcat[]',
+            'value' => $category->id,
+            'class' => 'bulk-action-checkbox',
+            'aria-label' => get_string('bulkactionselect', 'moodle', $text)
+        );
 
         if (!$category->can_resort_subcategories() && !$category->has_manage_capability()) {
             // Very very hardcoded here.
@@ -193,14 +219,36 @@ class core_course_management_renderer extends plugin_renderer_base {
 
         $viewcaturl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
         if ($isexpanded) {
-            $icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'), 'moodle', array('class' => 'tree-icon'));
-            $icon = html_writer::link($viewcaturl, $icon, array('class' => 'float-left', 'data-action' => 'collapse'));
+            $icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'), 'moodle', array('class' => 'tree-icon', 'title' => ''));
+            $icon = html_writer::link(
+                $viewcaturl,
+                $icon,
+                array(
+                    'class' => 'float-left',
+                    'data-action' => 'collapse',
+                    'title' => get_string('collapsecategory', 'moodle', $text),
+                    'aria-controls' => 'subcategoryof'.$category->id
+                )
+            );
         } else if ($isexpandable) {
-            $icon = $this->output->pix_icon('t/switch_plus', get_string('expand'), 'moodle', array('class' => 'tree-icon'));
-            $icon = html_writer::link($viewcaturl, $icon, array('class' => 'float-left', 'data-action' => 'expand'));
+            $icon = $this->output->pix_icon('t/switch_plus', get_string('expand'), 'moodle', array('class' => 'tree-icon', 'title' => ''));
+            $icon = html_writer::link(
+                $viewcaturl,
+                $icon,
+                array(
+                    'class' => 'float-left',
+                    'data-action' => 'expand',
+                    'title' => get_string('expandcategory', 'moodle', $text)
+                )
+            );
         } else {
-            $icon = $this->output->pix_icon('i/navigationitem', '', 'moodle', array('class' => 'tree-icon'));
-            $icon = html_writer::link($viewcaturl, $icon, array('class' => 'float-left'));
+            $icon = $this->output->pix_icon(
+                'i/navigationitem',
+                '',
+                'moodle',
+                array('class' => 'tree-icon', 'title' => get_string('showcategory', 'moodle', $text))
+            );
+            $icon = html_writer::span($icon, 'float-left');
         }
         $actions = \core_course\management\helper::get_category_listitem_actions($category);
         $hasactions = !empty($actions) || $category->can_create_course();
@@ -212,10 +260,14 @@ class core_course_management_renderer extends plugin_renderer_base {
         $html .= html_writer::end_div();
         $html .= $icon;
         if ($hasactions) {
-            $html .= html_writer::link($viewcaturl, $text, array('class' => 'float-left categoryname'));
+            $textattributes = array('class' => 'float-left categoryname');
         } else {
-            $html .= html_writer::link($viewcaturl, $text, array('class' => 'float-left categoryname without-actions'));
+            $textattributes = array('class' => 'float-left categoryname without-actions');
+        }
+        if (isset($textlabel)) {
+            $textattributes['aria-label'] = $textlabel;
         }
+        $html .= html_writer::link($viewcaturl, $text, $textattributes);
         $html .= html_writer::start_div('float-right');
         if ($category->idnumber) {
             $html .= html_writer::tag('span', s($category->idnumber), array('class' => 'dimmed idnumber'));
@@ -224,16 +276,18 @@ class core_course_management_renderer extends plugin_renderer_base {
             $html .= $this->category_listitem_actions($category, $actions);
         }
         $countid = 'course-count-'.$category->id;
-        $html .= html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid));
         $html .= html_writer::span(
-            html_writer::span($category->get_courses_count()).$courseicon,
+            html_writer::span($category->get_courses_count()) .
+            html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid)) .
+            $courseicon,
             'course-count dimmed',
             array('aria-labelledby' => $countid)
         );
         $html .= html_writer::end_div();
         $html .= html_writer::end_div();
         if ($isexpanded) {
-            $html .= html_writer::start_tag('ul', array('class' => 'ml', 'role' => 'group'));
+            $html .= html_writer::start_tag('ul',
+                array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
             $catatlevel = \core_course\management\helper::get_expanded_categories($category->path);
             $catatlevel[] = array_shift($selectedcategories);
             $catatlevel = array_unique($catatlevel);
@@ -323,9 +377,13 @@ class core_course_management_renderer extends plugin_renderer_base {
     public function category_bulk_actions(coursecat $category = null) {
         // Resort courses.
         // Change parent.
+        if (!coursecat::can_resort_any() && !coursecat::can_change_parent_any()) {
+            return '';
+        }
         $strgo = new lang_string('go');
 
         $html  = html_writer::start_div('category-bulk-actions bulk-actions');
+        $html .= html_writer::div(get_string('categorybulkaction'), 'accesshide', array('tabindex' => '0'));
         if (coursecat::can_resort_any()) {
             $selectoptions = array(
                 'selectedcategories' => get_string('selectedcategories'),
@@ -341,7 +399,8 @@ class core_course_management_renderer extends plugin_renderer_base {
                     $selectoptions,
                     'selectsortby',
                     'selectedcategories',
-                    false
+                    false,
+                    array('aria-label' => get_string('selectcategorysort'))
                 )
             );
             $form .= html_writer::div(
@@ -353,7 +412,8 @@ class core_course_management_renderer extends plugin_renderer_base {
                     ),
                     'resortcategoriesby',
                     'name',
-                    false
+                    false,
+                    array('aria-label' => get_string('selectcategorysortby'))
                 )
             );
             $form .= html_writer::div(
@@ -366,15 +426,17 @@ class core_course_management_renderer extends plugin_renderer_base {
                     ),
                     'resortcoursesby',
                     'fullname',
-                    false
+                    false,
+                    array('aria-label' => get_string('selectcoursesortby'))
                 )
             );
             $form .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'bulksort', 'value' => get_string('sort')));
             $form .= html_writer::end_div();
-            $html .= $this->detail_pair(
-                get_string('sorting'),
-                $form
-            );
+
+            $html .= html_writer::start_div('detail-pair row yui3-g');
+            $html .= html_writer::div(html_writer::span(get_string('sorting')), 'pair-key span3 yui3-u-1-4');
+            $html .= html_writer::div($form, 'pair-value span9 yui3-u-3-4');
+            $html .= html_writer::end_div();
         }
         if (coursecat::can_change_parent_any()) {
             $options = array();
@@ -412,7 +474,8 @@ class core_course_management_renderer extends plugin_renderer_base {
 
         if ($category === null) {
             $html = html_writer::start_div('select-a-category');
-            $html .= html_writer::tag('h3', get_string('courses'));
+            $html .= html_writer::tag('h3', get_string('courses'),
+                array('id' => 'course-listing-title', 'tabindex' => '0'));
             $html .= $this->output->notification(get_string('selectacategory'), 'notifymessage');
             $html .= html_writer::end_div();
             return $html;
@@ -445,10 +508,11 @@ class core_course_management_renderer extends plugin_renderer_base {
             'data-totalcourses' => $totalcourses,
             'data-canmoveoutof' => $category->can_move_courses_out_of() && $category->can_move_courses_into()
         ));
-        $html .= html_writer::tag('h3', $category->get_formatted_name());
+        $html .= html_writer::tag('h3', $category->get_formatted_name(),
+            array('id' => 'course-listing-title', 'tabindex' => '0'));
         $html .= $this->course_listing_actions($category, $course, $perpage);
         $html .= $this->listing_pagination($category, $page, $perpage);
-        $html .= html_writer::start_tag('ul', array('class' => 'ml'));
+        $html .= html_writer::start_tag('ul', array('class' => 'ml', 'role' => 'group'));
         foreach ($category->get_courses($options) as $listitem) {
             $html .= $this->course_listitem($category, $listitem, $courseid);
         }
@@ -546,7 +610,13 @@ class core_course_management_renderer extends plugin_renderer_base {
             'data-visible' => $course->visible ? '1' : '0'
         );
 
-        $bulkcourseinput = array('type' => 'checkbox', 'name' => 'bc[]', 'value' => $course->id, 'class' => 'bulk-action-checkbox');
+        $bulkcourseinput = array(
+            'type' => 'checkbox',
+            'name' => 'bc[]',
+            'value' => $course->id,
+            'class' => 'bulk-action-checkbox',
+            'aria-label' => get_string('bulkactionselect', 'moodle', $text)
+        );
         if (!$category->has_manage_capability()) {
             // Very very hardcoded here.
             $bulkcourseinput['style'] = 'visibility:hidden';
@@ -559,7 +629,7 @@ class core_course_management_renderer extends plugin_renderer_base {
 
         if ($category->can_resort_courses()) {
             // In order for dnd to be available the user must be able to resort the category children..
-            $html .= html_writer::div($this->output->pix_icon('i/dragdrop', get_string('dndcourse')), 'float-left drag-handle');
+            $html .= html_writer::div($this->output->pix_icon('i/move_2d', get_string('dndcourse')), 'float-left drag-handle');
         }
 
         $html .= html_writer::start_div('ba-checkbox float-left');
@@ -662,6 +732,7 @@ class core_course_management_renderer extends plugin_renderer_base {
     public function course_bulk_actions(coursecat $category) {
         $html  = html_writer::start_div('course-bulk-actions bulk-actions');
         if ($category->can_move_courses_out_of()) {
+            $html .= html_writer::div(get_string('coursebulkaction'), 'accesshide', array('tabindex' => '0'));
             $options = coursecat::make_categories_list('moodle/category:manage');
             $select = html_writer::select(
                 $options,
@@ -691,7 +762,7 @@ class core_course_management_renderer extends plugin_renderer_base {
         $fullname = $details['fullname']['value'];
 
         $html  = html_writer::start_div('course-detail');
-        $html .= html_writer::tag('h3', $fullname);
+        $html .= html_writer::tag('h3', $fullname, array('id' => 'course-detail-title', 'tabindex' => '0'));
         $html .= $this->course_detail_actions($course);
         foreach ($details as $class => $data) {
             $html .= $this->detail_pair($data['key'], $data['value'], $class);
@@ -741,12 +812,15 @@ class core_course_management_renderer extends plugin_renderer_base {
      * @param string $text The text for the button.
      * @param string $id An id to give the button.
      * @param string $class A class to give the button.
+     * @param array $attributes Any additional attributes
      * @return string
      */
-    protected function action_button(moodle_url $url, $text, $id = null, $class = null, $title = null) {
-        $attributes = array(
-            'class' => 'yui3-button',
-        );
+    protected function action_button(moodle_url $url, $text, $id = null, $class = null, $title = null, array $attributes = array()) {
+        if (isset($attributes['class'])) {
+            $attributes['class'] .= ' yui3-button';
+        } else {
+            $attributes['class'] = 'yui3-button';
+        }
         if (!is_null($id)) {
             $attributes['id'] = $id;
         }
@@ -757,6 +831,9 @@ class core_course_management_renderer extends plugin_renderer_base {
             $title = $text;
         }
         $attributes['title'] = $title;
+        if (!isset($attributes['role'])) {
+            $attributes['role'] = 'button';
+        }
         return html_writer::link($url, $text, $attributes);
     }
 
@@ -921,10 +998,10 @@ class core_course_management_renderer extends plugin_renderer_base {
             $menu->add(new action_menu_link_secondary($modeurl, null, $modestr, $attributes));
         }
 
-        $menu->set_menu_trigger(get_string('viewing', 'moodle', $selected));
+        $menu->set_menu_trigger($selected);
 
         $html = html_writer::start_div('view-mode-selector vms');
-        $html .= $this->render($menu);
+        $html .= get_string('viewing').' '.$this->render($menu);
         $html .= html_writer::end_div();
 
         return $html;
@@ -1124,4 +1201,31 @@ class core_course_management_renderer extends plugin_renderer_base {
         return html_writer::span(join('', $actions), 'course-item-actions item-actions');
     }
 
+    /**
+     * Creates access hidden skip to links for the displayed sections.
+     *
+     * @param bool $displaycategorylisting
+     * @param bool $displaycourselisting
+     * @param bool $displaycoursedetail
+     * @return string
+     */
+    public function accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail) {
+        $html = html_writer::start_div('skiplinks accesshide');
+        $url = new moodle_url($this->page->url);
+        if ($displaycategorylisting) {
+            $url->set_anchor('category-listing');
+            $html .= html_writer::link($url, get_string('skiptocategorylisting'), array('class' => 'skip'));
+        }
+        if ($displaycourselisting) {
+            $url->set_anchor('course-listing');
+            $html .= html_writer::link($url, get_string('skiptocourselisting'), array('class' => 'skip'));
+        }
+        if ($displaycoursedetail) {
+            $url->set_anchor('course-detail');
+            $html .= html_writer::link($url, get_string('skiptocoursedetails'), array('class' => 'skip'));
+        }
+        $html .= html_writer::end_div();
+        return $html;
+    }
+
 }
index 1980441..38c4a69 100644 (file)
@@ -572,7 +572,7 @@ abstract class format_base {
             $sectionid = $section->id;
         } else if ($this->courseid && is_int($section) &&
                 ($sectionobj = $DB->get_record('course_sections',
-                        array('section' => $section, 'courseid' => $this->courseid), 'id'))) {
+                        array('section' => $section, 'course' => $this->courseid), 'id'))) {
             // course section format options will be returned
             $sectionid = $sectionobj->id;
         } else {
index cbca4ca..bbdad82 100644 (file)
@@ -1995,7 +1995,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
             new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
             new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')),
             $str->moveright,
-            array('class' => 'editing_moveright ' . $enabledclass, 'data-action' => 'moveright')
+            array('class' => 'editing_moveright ' . $enabledclass, 'data-action' => 'moveright', 'data-keepopen' => true)
         );
 
         if ($indent <= $indentlimits->min) {
@@ -2007,7 +2007,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
             new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
             new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')),
             $str->moveleft,
-            array('class' => 'editing_moveleft ' . $enabledclass, 'data-action' => 'moveleft')
+            array('class' => 'editing_moveleft ' . $enabledclass, 'data-action' => 'moveleft', 'data-keepopen' => true)
         );
 
     }
@@ -2065,9 +2065,9 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
 
             $actions[$actionname] = new action_menu_link_primary(
                 new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $nextgroupmode)),
-                new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                new pix_icon($groupimage, null, 'moodle', array('class' => 'iconsmall')),
                 $grouptitle,
-                array('class' => 'editing_'. $actionname, 'data-action' => $actionname, 'data-nextgroupmode' => $nextgroupmode)
+                array('class' => 'editing_'. $actionname, 'data-action' => $actionname, 'data-nextgroupmode' => $nextgroupmode, 'aria-live' => 'assertive')
             );
         } else {
             $actions['nogroupsupport'] = new action_menu_filler();
index e4d22b6..ecdee05 100644 (file)
@@ -99,13 +99,13 @@ if ($modulelist !== '') {
 }
 
 $strmanagement = new lang_string('coursecatmanagement');
-$title = format_string($SITE->fullname, true, array('context' => $systemcontext));
+$pageheading = format_string($SITE->fullname, true, array('context' => $systemcontext));
 
 $PAGE->set_context($context);
 $PAGE->set_url($url);
 $PAGE->set_pagelayout('admin');
-$PAGE->set_title($title);
-$PAGE->set_heading($strmanagement);
+$PAGE->set_title($strmanagement);
+$PAGE->set_heading($pageheading);
 
 // This is a system level page that operates on other contexts.
 require_login();
@@ -460,6 +460,10 @@ if (($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'cours
 $renderer = $PAGE->get_renderer('core_course', 'management');
 $renderer->enhance_management_interface();
 
+$displaycategorylisting = ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'categories');
+$displaycourselisting = ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses');
+$displaycoursedetail = (isset($courseid));
+
 echo $renderer->header();
 
 if (!$issearching) {
@@ -478,13 +482,16 @@ if (count($notificationsfail) > 0) {
 // Start the management form.
 echo $renderer->management_form_start();
 
+echo $renderer->accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail);
+
 echo $renderer->grid_start('course-category-listings', $class);
-if ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'categories') {
+
+if ($displaycategorylisting) {
     echo $renderer->grid_column_start($categorysize, 'category-listing');
     echo $renderer->category_listing($category);
     echo $renderer->grid_column_end();
 }
-if ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses') {
+if ($displaycourselisting) {
     echo $renderer->grid_column_start($coursesize, 'course-listing');
     if (!$issearching) {
         echo $renderer->course_listing($category, $course, $page, $perpage);
@@ -494,7 +501,7 @@ if ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'course
         echo $renderer->search_listing($courses, $coursestotal, $course, $page, $perpage);
     }
     echo $renderer->grid_column_end();
-    if (isset($courseid)) {
+    if ($displaycoursedetail) {
         echo $renderer->grid_column_start($detailssize, 'course-detail');
         echo $renderer->course_detail($course);
         echo $renderer->grid_column_end();
index d5a93f6..1001212 100644 (file)
@@ -859,7 +859,8 @@ class core_course_renderer extends plugin_renderer_base {
             }
         } else {
             // No link, so display only content.
-            $output = html_writer::tag('div', $accesstext . $content, array('class' => $textclasses));
+            $output = html_writer::tag('div', $accesstext . $content,
+                    array('class' => 'contentwithoutlink ' . $textclasses));
         }
         return $output;
     }
@@ -982,22 +983,33 @@ class core_course_renderer extends plugin_renderer_base {
             $output .= course_get_cm_move($mod, $sectionreturn);
         }
 
-        $output .= html_writer::start_tag('div', array('class' => $indentclasses));
+        $output .= html_writer::start_tag('div', array('class' => 'mod-indent-outer'));
 
-        // Start the div for the activity title, excluding the edit icons.
-        $output .= html_writer::start_tag('div', array('class' => 'activityinstance'));
+        // This div is used to indent the content.
+        $output .= html_writer::div('', $indentclasses);
+
+        // Start a wrapper for the actual content to keep the indentation consistent
+        $output .= html_writer::start_tag('div');
 
         // Display the link to the module (or do nothing if module has no url)
-        $output .= $this->course_section_cm_name($mod, $displayoptions);
-        if ($this->page->user_is_editing()) {
-            $output .= ' ' . course_get_cm_rename_action($mod, $sectionreturn);
-        }
+        $cmname = $this->course_section_cm_name($mod, $displayoptions);
+
+        if (!empty($cmname)) {
+            // Start the div for the activity title, excluding the edit icons.
+            $output .= html_writer::start_tag('div', array('class' => 'activityinstance'));
+            $output .= $cmname;
+
 
-        // Module can put text after the link (e.g. forum unread)
-        $output .= $mod->get_after_link();
+            if ($this->page->user_is_editing()) {
+                $output .= ' ' . course_get_cm_rename_action($mod, $sectionreturn);
+            }
+
+            // Module can put text after the link (e.g. forum unread)
+            $output .= $mod->get_after_link();
 
-        // Closing the tag which contains everything but edit icons. Content part of the module should not be part of this.
-        $output .= html_writer::end_tag('div'); // .activityinstance
+            // Closing the tag which contains everything but edit icons. Content part of the module should not be part of this.
+            $output .= html_writer::end_tag('div'); // .activityinstance
+        }
 
         // If there is content but NO link (eg label), then display the
         // content here (BEFORE any icons). In this case cons must be
@@ -1033,7 +1045,10 @@ class core_course_renderer extends plugin_renderer_base {
         // show availability info (if module is not available)
         $output .= $this->course_section_cm_availability($mod, $displayoptions);
 
-        $output .= html_writer::end_tag('span'); // $indentclasses
+        $output .= html_writer::end_tag('div'); // $indentclasses
+
+        // End of indentation div.
+        $output .= html_writer::end_tag('div');
 
         $output .= html_writer::end_tag('div');
         return $output;
index d0235f5..4691d95 100644 (file)
@@ -52,7 +52,6 @@ foreach ($allmodules as $key=>$module) {
 }
 
 $strresources    = get_string('resources');
-$strsectionname  = get_string('sectionname', 'format_'.$course->format);
 $strname         = get_string('name');
 $strintro        = get_string('moduleintro');
 $strlastmodified = get_string('lastmodified');
@@ -100,6 +99,7 @@ $table = new html_table();
 $table->attributes['class'] = 'generaltable mod_index';
 
 if ($usesections) {
+    $strsectionname = get_string('sectionname', 'format_'.$course->format);
     $table->head  = array ($strsectionname, $strname, $strintro);
     $table->align = array ('center', 'left', 'left');
 } else {
index fdcdb61..9006567 100644 (file)
@@ -27,8 +27,10 @@ Feature: Toggle activities visibility from the course page
     When I open "Test forum name" actions menu
     And I click on "Hide" "link" in the "Test forum name" activity
     Then "Test forum name" activity should be hidden
+    And I open "Test forum name" actions menu
     And I click on "Show" "link" in the "Test forum name" activity
     And "Test forum name" activity should be visible
+    And I open "Test forum name" actions menu
     And I click on "Hide" "link" in the "Test forum name" activity
     And "Test forum name" activity should be hidden
     And I reload the page
index c950c7e..3b32a94 100644 (file)
@@ -1394,4 +1394,15 @@ class behat_course extends behat_base {
         }
         $actionnode->click();
     }
+
+    /**
+     * Clicks on a category in the management interface.
+     *
+     * @Given /^I click on "([^"]*)" category in the management category listing$/
+     * @param string $name The name of the category to click.
+     */
+    public function i_click_on_category_in_the_management_category_listing($name) {
+        $node = $this->get_management_category_listing_node_by_name($name);
+        $node->find('css', 'a.categoryname')->click();
+    }
 }
index 163668f..5795413 100644 (file)
@@ -92,7 +92,7 @@ Feature: Test we can resort categories in the management interface.
     And I log in as "admin"
     And I go to the courses management page
     And I should see the "Course categories" management page
-    And I click on "Master cat" "link"
+    And I click on "Master cat" category in the management category listing
   # Redirect.
     And I should see the "Course categories and courses" management page
     And I click on <sortby> action for "Master cat" in management category listing
index d191791..14a6820 100644 (file)
@@ -12,7 +12,7 @@ Feature: Course category management interface performs as expected
     And I log in as "admin"
     And I go to the courses management page
     And I should see "Course and category management" in the "h2" "css_element"
-    And I should see "Viewing Course categories"
+    And I should see "Course categories" in the ".view-mode-selector" "css_element"
     And I should see "Course categories" in the "h3" "css_element"
     And I should see the "Course categories" management page
 
@@ -30,11 +30,11 @@ Feature: Course category management interface performs as expected
     And I should see the "Course categories" management page
     And I should see "Course categories" in the "#category-listing h3" "css_element"
     And I should see "Cat 1" in the "#category-listing" "css_element"
-    And I should see "Viewing Course categories" in the ".view-mode-selector" "css_element"
+    And I should see "Course categories" in the ".view-mode-selector" "css_element"
     And I should not see "Course categories and courses" in the ".view-mode-selector .menu" "css_element"
     And I should not see "Course categories" in the ".view-mode-selector .menu" "css_element"
     And I should not see "Courses" in the ".view-mode-selector .menu" "css_element"
-    When I click on "Viewing Course categories" "link" in the ".view-mode-selector" "css_element"
+    When I click on "Course categories" "link" in the ".view-mode-selector" "css_element"
     Then I should see "Course categories and courses" in the ".view-mode-selector .menu" "css_element"
     And I should see "Course categories" in the ".view-mode-selector .menu" "css_element"
     And I should see "Courses" in the ".view-mode-selector .menu" "css_element"
@@ -52,7 +52,7 @@ Feature: Course category management interface performs as expected
     And I should see "Cat 1" in the "#course-listing h3" "css_element"
     And I should see "Cat 1" in the "#category-listing" "css_element"
     And I should see "Course 1" in the "#course-listing" "css_element"
-    When I click on "Viewing Course categories" "link" in the ".view-mode-selector" "css_element"
+    When I click on "Course categories" "link" in the ".view-mode-selector" "css_element"
     Then I should see "Courses" in the ".view-mode-selector .menu" "css_element"
     And I click on "Courses" "link" in the ".view-mode-selector .menu" "css_element"
     # Redirect.
@@ -270,7 +270,7 @@ Feature: Course category management interface performs as expected
     And I log in as "admin"
     And I go to the courses management page
     And I should see the "Course categories" management page
-    And I click on "Master cat" "link"
+    And I click on "Master cat" category in the management category listing
     # Redirect.
     And I should see the "Course categories and courses" management page
     And I click on <sortby> action for "Master cat" in management category listing
@@ -727,7 +727,7 @@ Feature: Course category management interface performs as expected
     And I should see "Cat 2-1-2" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat 2-1-1-1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2-1-2-1" in the "#course-category-listings ul.ml" "css_element"
-    And I click on "Cat 1" "link"
+    And I click on "Cat 1" category in the management category listing
     # Redirect.
     And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
index 08ae96c..fe8f210 100644 (file)
@@ -90,4 +90,14 @@ Feature: We can change the visibility of courses in the management interface.
     # AJAX updates the visibility
     And category in management listing should be visible "CAT1"
     And course in management listing should be dimmed "C1"
-    And I toggle visibility of category "CAT1" in management listing
\ No newline at end of file
+    And I toggle visibility of category "CAT1" in management listing
+    And I toggle visibility of course "C1" in management listing
+    And I click on "Course categories and courses" "link" in the ".view-mode-selector" "css_element"
+    And I click on "Courses" "link"
+    # Redirect
+    And I should see "Course 1" in the "#course-listing ul.ml" "css_element"
+    And I toggle visibility of course "C1" in management listing
+    # AJAX updates the visibility
+    And course in management listing should be dimmed "C1"
+    And I toggle visibility of course "C1" in management listing
+    And course in management listing should be visible "C1"
index 135566f..1b26a57 100644 (file)
Binary files a/course/yui/build/moodle-course-management/moodle-course-management-debug.js and b/course/yui/build/moodle-course-management/moodle-course-management-debug.js differ
index 8db3ba9..6597c67 100644 (file)
Binary files a/course/yui/build/moodle-course-management/moodle-course-management-min.js and b/course/yui/build/moodle-course-management/moodle-course-management-min.js differ
index aa5f977..5d78e2d 100644 (file)
Binary files a/course/yui/build/moodle-course-management/moodle-course-management.js and b/course/yui/build/moodle-course-management/moodle-course-management.js differ
index e165d86..7d8a9dd 100644 (file)
@@ -1,9 +1,9 @@
 YUI.add('moodle-course-dragdrop', function(Y) {
 
     var CSS = {
+        ACTIONAREA: '.actions',
         ACTIVITY : 'activity',
         ACTIVITYINSTANCE : 'activityinstance',
-        COMMANDSPAN : '.commands',
         CONTENT : 'content',
         COURSECONTENT : 'course-content',
         EDITINGMOVE : 'editing_move',
@@ -375,8 +375,8 @@ YUI.add('moodle-course-dragdrop', function(Y) {
             var dropnode = e.drop.get('node');
 
             // Add spinner if it not there
-            var activityinstance = dragnode.one('.' + CSS.ACTIVITYINSTANCE);
-            var spinner = M.util.add_spinner(Y, activityinstance);
+            var actionarea = dragnode.one(CSS.ACTIONAREA);
+            var spinner = M.util.add_spinner(Y, actionarea);
 
             var params = {};
 
index 7312489..2a608c3 100644 (file)
@@ -148,13 +148,18 @@ Category.prototype = {
      */
     expand : function() {
         var node = this.get('node'),
-            action = node.one('a[data-action=expand]');
+            action = node.one('a[data-action=expand]'),
+            ul = node.one('ul[role=group]');
         node.removeClass('collapsed').setAttribute('aria-expanded', 'true');
-        action.setAttribute('data-action', 'collapse').one('img').setAttrs({
+        action.setAttribute('data-action', 'collapse').setAttrs({
+            title : M.util.get_string('collapsecategory', 'moodle', this.getName())
+        }).one('img').setAttrs({
             src : M.util.image_url('t/switch_minus', 'moodle'),
-            title : M.util.get_string('collapse', 'moodle'),
             alt : M.util.get_string('collapse', 'moodle')
         });
+        if (ul) {
+            ul.setAttribute('aria-hidden', 'false');
+        }
         this.get('console').performAjaxAction('expandcategory', {categoryid : this.get('categoryid')}, null, this);
     },
 
@@ -164,13 +169,18 @@ Category.prototype = {
      */
     collapse : function() {
         var node = this.get('node'),
-            action = node.one('a[data-action=collapse]');
+            action = node.one('a[data-action=collapse]'),
+            ul = node.one('ul[role=group]');
         node.addClass('collapsed').setAttribute('aria-expanded', 'false');
-        action.setAttribute('data-action', 'expand').one('img').setAttrs({
+        action.setAttribute('data-action', 'expand').setAttrs({
+            title : M.util.get_string('expandcategory', 'moodle', this.getName())
+        }).one('img').setAttrs({
             src : M.util.image_url('t/switch_plus', 'moodle'),
-            title : M.util.get_string('expand', 'moodle'),
             alt : M.util.get_string('expand', 'moodle')
         });
+        if (ul) {
+            ul.setAttribute('aria-hidden', 'true');
+        }
         this.get('console').performAjaxAction('collapsecategory', {categoryid : this.get('categoryid')}, null, this);
     },
 
@@ -187,7 +197,9 @@ Category.prototype = {
     loadSubcategories : function(transactionid, response, args) {
         var outcome = this.checkAjaxResponse(transactionid, response, args),
             node = this.get('node'),
-            managementconsole = this.get('console');
+            managementconsole = this.get('console'),
+            ul,
+            actionnode;
         if (outcome === false) {
             Y.log('AJAX failed to load sub categories for '+this.get('itemname'), 'warn', 'moodle-course-management');
             return false;
@@ -198,6 +210,11 @@ Category.prototype = {
         if (M.core && M.core.actionmenu && M.core.actionmenu.newDOMNode) {
             M.core.actionmenu.newDOMNode(node);
         }
+        ul = node.one('ul[role=group]');
+        actionnode = node.one('a[data-action=collapse]');
+        if (ul && actionnode) {
+            actionnode.setAttribute('aria-controls', ul.generateID());
+        }
         return true;
     },
 
@@ -295,13 +312,18 @@ Category.prototype = {
      * @returns {Boolean}
      */
     show : function(transactionid, response, args) {
-        var outcome = this.checkAjaxResponse(transactionid, response, args);
+        var outcome = this.checkAjaxResponse(transactionid, response, args),
+            hidebtn;
         if (outcome === false) {
             Y.log('AJAX request to show '+this.get('itemname')+' by outcome.', 'warn', 'moodle-course-management');
             return false;
         }
 
         this.markVisible();
+        hidebtn = this.get('node').one('a[data-action=hide]');
+        if (hidebtn) {
+            hidebtn.focus();
+        }
         if (outcome.categoryvisibility) {
             this.updateChildVisibility(outcome.categoryvisibility);
         }
@@ -322,12 +344,17 @@ Category.prototype = {
      * @returns {Boolean}
      */
     hide : function(transactionid, response, args) {
-        var outcome = this.checkAjaxResponse(transactionid, response, args);
+        var outcome = this.checkAjaxResponse(transactionid, response, args),
+            showbtn;
         if (outcome === false) {
             Y.log('AJAX request to hide '+this.get('itemname')+' by outcome.', 'warn', 'moodle-course-management');
             return false;
         }
         this.markHidden();
+        showbtn = this.get('node').one('a[data-action=show]');
+        if (showbtn) {
+            showbtn.focus();
+        }
         if (outcome.categoryvisibility) {
             this.updateChildVisibility(outcome.categoryvisibility);
         }
index 394d63c..631dfc4 100644 (file)
@@ -279,10 +279,6 @@ Console.prototype = {
         if (!listing) {
             return false;
         }
-        if (!category) {
-            Y.log('Couldn\'t find the current category object.', 'warn', 'moodle-course-management');
-            return false;
-        }
         listing.all('.listitem[data-id]').each(function(node){
             this.registerCourse(new Course({
                 node : node,
index 04220b2..5987f7a 100644 (file)
@@ -123,6 +123,17 @@ Item.prototype = {
                     nodeup.insert(previousdown, 'after');
                 }
             }
+            nodeup = node.one(' > div a.action-moveup');
+            if (nodeup) {
+                // Try to re-focus on up.
+                nodeup.focus();
+            } else {
+                // If we can't focus up we're at the bottom, try to focus on up.
+                nodedown = node.one(' > div a.action-movedown');
+                if (nodedown) {
+                    nodedown.focus();
+                }
+            }
             this.updated(true);
             Y.log('Success: '+this.get('itemname')+' moved up by AJAX.', 'info', 'moodle-course-management');
         } else {
@@ -180,6 +191,17 @@ Item.prototype = {
                     nodedown.insert(nextup, 'before');
                 }
             }
+            nodedown = node.one(' > div a.action-movedown');
+            if (nodedown) {
+                // Try to ensure the up is focused again.
+                nodedown.focus();
+            } else {
+                // If we can't focus up we're at the top, try to focus on down.
+                nodeup = node.one(' > div a.action-moveup');
+                if (nodeup) {
+                    nodeup.focus();
+                }
+            }
             this.updated(true);
             Y.log('Success: '+this.get('itemname')+' moved down by AJAX.', 'info', 'moodle-course-management');
         } else {
@@ -200,13 +222,18 @@ Item.prototype = {
      * @returns {Boolean}
      */
     show : function(transactionid, response, args) {
-        var outcome = this.checkAjaxResponse(transactionid, response, args);
+        var outcome = this.checkAjaxResponse(transactionid, response, args),
+            hidebtn;
         if (outcome === false) {
             Y.log('AJAX request to show '+this.get('itemname')+' by outcome.', 'warn', 'moodle-course-management');
             return false;
         }
 
         this.markVisible();
+        hidebtn = this.get('node').one('a[data-action=hide]');
+        if (hidebtn) {
+            hidebtn.focus();
+        }
         this.updated();
         Y.log('Success: '+this.get('itemname')+' made visible by AJAX.', 'info', 'moodle-course-management');
     },
@@ -231,12 +258,17 @@ Item.prototype = {
      * @returns {Boolean}
      */
     hide : function(transactionid, response, args) {
-        var outcome = this.checkAjaxResponse(transactionid, response, args);
+        var outcome = this.checkAjaxResponse(transactionid, response, args),
+            showbtn;
         if (outcome === false) {
             Y.log('AJAX request to hide '+this.get('itemname')+' by outcome.', 'warn', 'moodle-course-management');
             return false;
         }
         this.markHidden();
+        showbtn = this.get('node').one('a[data-action=show]');
+        if (showbtn) {
+            showbtn.focus();
+        }
         this.updated();
         Y.log('Success: '+this.get('itemname')+' made hidden by AJAX.', 'info', 'moodle-course-management');
     },
index de021b3..9ebc282 100644 (file)
@@ -9,6 +9,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
     var CSS = {
         ACTIVITYINSTANCE : 'activityinstance',
         AVAILABILITYINFODIV : 'div.availabilityinfo',
+        CONTENTWITHOUTLINK : 'contentwithoutlink',
         CONDITIONALHIDDEN : 'conditionalhidden',
         DIMCLASS : 'dimmed',
         DIMMEDTEXT : 'dimmed_text',
@@ -25,6 +26,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
     },
     // The CSS selectors we use.
     SELECTOR = {
+        ACTIONAREA: '.actions',
         ACTIONLINKTEXT : '.actionlinktext',
         ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_title',
         ACTIVITYFORM : '.' + CSS.ACTIVITYINSTANCE + ' form',
@@ -35,11 +37,13 @@ YUI.add('moodle-course-toolboxes', function(Y) {
         ACTIVITYTITLE : 'input[name=title]',
         COMMANDSPAN : '.commands',
         CONTENTAFTERLINK : 'div.contentafterlink',
+        CONTENTWITHOUTLINK : 'div.contentwithoutlink',
         EDITTITLE: 'a.editing_title',
         HIDE : 'a.editing_hide',
         HIGHLIGHT : 'a.editing_highlight',
         INSTANCENAME : 'span.instancename',
         MODINDENTDIV : '.mod-indent',
+        MODINDENTOUTER : '.mod-indent-outer',
         PAGECONTENT : 'div#page-content',
         SECTIONLI : 'li.section',
         SHOW : 'a.'+CSS.SHOW,
@@ -218,8 +222,9 @@ YUI.add('moodle-course-toolboxes', function(Y) {
          *
          * @method initializer
          */
-        initializer : function(config) {
+        initializer : function() {
             M.course.coursebase.register_module(this);
+            BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this);
             Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
         },
 
@@ -249,6 +254,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
                 // It wasn't a valid action node.
                 return;
             }
+            Y.log(ev.type);
 
             // Switch based upon the action and do the desired thing.
             switch (action) {
@@ -291,13 +297,8 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             }
         },
         add_spinner: function(activity) {
-            var instance = activity.one(SELECTOR.ACTIVITYINSTANCE);
-
-            if (instance) {
-                return M.util.add_spinner(Y, instance);
-            } else {
-                return M.util.add_spinner(Y, activity);
-            }
+            var actionarea = activity.one(SELECTOR.ACTIONAREA);
+            return M.util.add_spinner(Y, actionarea);
         },
 
         /**
@@ -346,15 +347,19 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             var spinner = this.add_spinner(activity);
             this.send_request(data, spinner);
 
+            var remainingmove;
+
             // Handle removal/addition of the moveleft button.
             if (newindent === INDENTLIMITS.MIN) {
                 button.addClass('hidden');
+                remainingmove = activity.one('.editing_moveright');
             } else if (newindent > INDENTLIMITS.MIN && oldindent === INDENTLIMITS.MIN) {
                 button.ancestor('.menu').one('[data-action=moveleft]').removeClass('hidden');
             }
 
             if (newindent === INDENTLIMITS.MAX) {
                 button.addClass('hidden');
+                remainingmove = activity.one('.editing_moveleft');
             } else if (newindent < INDENTLIMITS.MAX && oldindent === INDENTLIMITS.MAX) {
                 button.ancestor('.menu').one('[data-action=moveright]').removeClass('hidden');
             }
@@ -366,6 +371,10 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             } else if (newindent <= 15 && hashugeclass) {
                 indentdiv.removeClass(CSS.MODINDENTHUGE);
             }
+
+            if (ev.type && ev.type === "key" && remainingmove) {
+                remainingmove.focus();
+            }
         },
 
         /**
@@ -506,29 +515,41 @@ YUI.add('moodle-course-toolboxes', function(Y) {
          */
         handle_resource_dim : function(button, activity, action) {
             var toggleclass = CSS.DIMCLASS,
-                dimarea = activity.one('a'),
+                dimarea = activity.one([
+                        SELECTOR.ACTIVITYLINK,
+                        SELECTOR.CONTENTWITHOUTLINK
+                    ].join(', ')),
                 availabilityinfo = activity.one(CSS.AVAILABILITYINFODIV),
                 nextaction = (action === 'hide') ? 'show' : 'hide',
                 buttontext = button.one('span'),
-                newstring = M.util.get_string(nextaction, 'moodle');
+                newstring = M.util.get_string(nextaction, 'moodle'),
+                buttonimg = button.one('img');
 
             // Update button info.
-            button.one('img').setAttrs({
-                'alt' : newstring,
+            buttonimg.setAttrs({
                 'src'   : M.util.image_url('t/' + nextaction)
             });
-            button.set('title', newstring);
+
+            if (Y.Lang.trim(button.getAttribute('title'))) {
+                button.setAttribute('title', newstring);
+            }
+
+            if (Y.Lang.trim(buttonimg.getAttribute('alt'))) {
+                buttonimg.setAttribute('alt', newstring);
+            }
+
             button.replaceClass('editing_'+action, 'editing_'+nextaction);
             button.setData('action', nextaction);
             if (buttontext) {
                 buttontext.set('text', newstring);
             }
 
-            // If activity is conditionally hidden, then don't toggle.
-            if (Y.Moodle.core_course.util.cm.getName(activity) === null) {
+            if (activity.one(SELECTOR.CONTENTWITHOUTLINK)) {
+                dimarea = activity.one(SELECTOR.CONTENTWITHOUTLINK);
                 toggleclass = CSS.DIMMEDTEXT;
-                dimarea = activity.all(SELECTOR.MODINDENTDIV + ' > div').item(1);
             }
+
+            // If activity is conditionally hidden, then don't toggle.
             if (!dimarea.hasClass(CSS.CONDITIONALHIDDEN)) {
                 // Change the UI.
                 dimarea.toggleClass(toggleclass);
@@ -564,7 +585,8 @@ YUI.add('moodle-course-toolboxes', function(Y) {
                 newtitlestr,
                 data,
                 spinner,
-                nextgroupmode = groupmode + 1;
+                nextgroupmode = groupmode + 1,
+                buttonimg = button.one('img');
 
             if (nextgroupmode > 2) {
                 nextgroupmode = 0;
@@ -584,11 +606,16 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             newtitlestr = M.util.get_string('clicktochangeinbrackets', 'moodle', newtitlestr);
 
             // Change the UI
-            button.one('img').setAttrs({
-                'alt' : newtitlestr,
+            buttonimg.setAttrs({
                 'src' : iconsrc
             });
-            button.setAttribute('title', newtitlestr).setData('action', newtitle).setData('nextgroupmode', nextgroupmode);
+            if (Y.Lang.trim(button.getAttribute('title'))) {
+                button.setAttribute('title', newtitlestr).setData('action', newtitle).setData('nextgroupmode', nextgroupmode);
+            }
+
+            if (Y.Lang.trim(buttonimg.getAttribute('alt'))) {
+                buttonimg.setAttribute('alt', newtitlestr);
+            }
 
             // And send the request
             data = {
@@ -618,6 +645,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             // Get the element we're working on
             var activityid = Y.Moodle.core_course.util.cm.getId(activity),
                 instancename  = activity.one(SELECTOR.INSTANCENAME),
+                instance = activity.one(SELECTOR.ACTIVITYINSTANCE),
                 currenttitle = instancename.get('firstChild'),
                 oldtitle = currenttitle.get('data'),
                 titletext = oldtitle,
@@ -657,18 +685,14 @@ YUI.add('moodle-course-toolboxes', function(Y) {
                 editform.appendChild(activity.one(SELECTOR.ACTIVITYICON).cloneNode());
                 editform.appendChild(editor);
                 editform.setData('anchor', anchor);
+                instance.insert(editinstructions, 'before');
                 anchor.replace(editform);
-                activity.one('div').appendChild(editinstructions);
 
                 // Force the editing instruction to match the mod-indent position.
                 var padside = 'left';
                 if (right_to_left()) {
                     padside = 'right';
                 }
-                var mi = activity.one('.mod-indent'),
-                    instructionpad = parseInt(mi.getStyle('padding-' + padside), 10) +
-                            parseInt(mi.getStyle('margin-' + padside), 10);
-                editinstructions.setStyle('margin-' + padside, instructionpad + 'px');
 
                 // We hide various components whilst editing:
                 activity.addClass(CSS.EDITINGTITLE);
@@ -977,6 +1001,6 @@ YUI.add('moodle-course-toolboxes', function(Y) {
 
 },
 '@VERSION@', {
-    requires : ['base', 'node', 'io', 'moodle-course-coursebase', 'moodle-course-util']
+    requires : ['base', 'event-key', 'node', 'io', 'moodle-course-coursebase', 'moodle-course-util']
 }
 );
index 134261e..b3ae333 100644 (file)
@@ -102,14 +102,7 @@ $sql = "SELECT g.id AS groupid, gg.groupingid, u.id AS userid, $allnames, u.idnu
 $rs = $DB->get_recordset_sql($sql, array_merge($params, $sortparams));
 foreach ($rs as $row) {
     $user = new stdClass();
-    $user->id        = $row->userid;
-    $user->firstname = $row->firstname;
-    $user->lastname  = $row->lastname;
-    $user->username  = $row->username;
-    $user->idnumber  = $row->idnumber;
-    foreach (get_all_user_name_fields() as $addname) {
-        $user->$addname = $row->$addname;
-    }
+    $user = username_load_fields_from_object($user, $row, null, array('id' => 'userid', 'username', 'idnumber'));
     if (!$row->groupingid) {
         $row->groupingid = -1;
     }
index 160d712..c6e3f84 100644 (file)
@@ -58,3 +58,36 @@ Feature: Organize students into groups
     And I should see "Student 2"
     And I should see "Student 3"
     And I should not see "Student 0"
+
+  @javascript
+  Scenario: Create groups and groupings without the 'moodle/course:changeidnumber' capability
+    Given the following "courses" exists:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exists:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+    And the following "course enrolments" exists:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "admin"
+    And I set the following system permissions of "Teacher" role:
+      | moodle/course:changeidnumber | Prevent |
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I expand "Users" node
+    And I follow "Groups"
+    When I press "Create group"
+    Then the "idnumber" "field" should be readonly
+    And I fill the moodle form with:
+      | Group name | The greatest group that never existed |
+    And I press "Save changes"
+    And I should see "The greatest group that never existed"
+    And I follow "Groupings"
+    And I press "Create grouping"
+    And the "idnumber" "field" should be readonly
+    And I fill the moodle form with:
+      | Grouping name | Not the greatest grouping, but it's ok! |
+    And I press "Save changes"
+    And I should see "Not the greatest grouping, but it's ok!"
diff --git a/group/tests/behat/delete_groups.feature b/group/tests/behat/delete_groups.feature
new file mode 100644 (file)
index 0000000..239e577
--- /dev/null
@@ -0,0 +1,81 @@
+@core @core_group
+Feature: Automatic deletion of groups and groupings
+  In order to check the expected results occur when deleting groups and groupings in different scenarios
+  As a teacher
+  I need to create groups and groupings under different scenarios and check that the expected result occurs when attempting to delete them.
+
+  Background:
+    Given the following "courses" exists:
+      | fullname | shortname | format |
+      | Course 1 | C1 | topics |
+    And the following "users" exists:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+    And the following "course enrolments" exists:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I expand "Users" node
+    And I follow "Groups"
+    And I press "Create group"
+    And I fill the moodle form with:
+      | Group name | Group (without ID) |
+    And I press "Save changes"
+    And I press "Create group"
+    And I fill the moodle form with:
+      | Group name | Group (with ID) |
+      | Group ID number | An ID |
+    And I press "Save changes"
+    And I follow "Groupings"
+    And I press "Create grouping"
+    And I fill the moodle form with:
+      | Grouping name | Grouping (without ID) |
+    And I press "Save changes"
+    And I press "Create grouping"
+    And I fill the moodle form with:
+      | Grouping name | Grouping (with ID) |
+      | Grouping ID number | An ID |
+    And I press "Save changes"
+    And I follow "Groups"
+
+  @javascript
+  Scenario: Delete groups and groupings with and without ID numbers
+    Given I select "Group (without ID) (0)" from "groups"
+    And I press "Delete selected group"
+    And I press "Yes"
+    Then the "groups" select box should not contain "Group (without ID) (0)"
+    And I select "Group (with ID) (0)" from "groups"
+    And I press "Delete selected group"
+    And I press "Yes"
+    And the "groups" select box should not contain "Group (with ID) (0)"
+    And I follow "Groupings"
+    And I click on "Delete" "link" in the "Grouping (without ID)" "table_row"
+    And I press "Yes"
+    And I should not see "Grouping (without ID)"
+    And I click on "Delete" "link" in the "Grouping (with ID)" "table_row"
+    And I press "Yes"
+    And I should not see "Grouping (with ID)"
+
+  @javascript
+  Scenario: Delete groups and groupings with and without ID numbers without the 'moodle/course:changeidnumber' capability
+    Given I log out
+    And I log in as "admin"
+    And I set the following system permissions of "Teacher" role:
+     | moodle/course:changeidnumber | Prevent |
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I expand "Users" node
+    And I follow "Groups"
+    When I select "Group (with ID) (0)" from "groups"
+    Then the "Delete selected group" "button" should be disabled
+    And I select "Group (without ID) (0)" from "groups"
+    And I press "Delete selected group"
+    And I press "Yes"
+    And I should not see "Group (without ID)"
+    And I follow "Groupings"
+    And "Delete" "link" should not exist in the "Grouping (with ID)" "table_row"
+    And I click on "Delete" "link" in the "Grouping (without ID)" "table_row"
+    And I press "Yes"
+    And I should not see "Grouping (without ID)"
diff --git a/group/tests/behat/update_groups.feature b/group/tests/behat/update_groups.feature
new file mode 100644 (file)
index 0000000..eb92743
--- /dev/null
@@ -0,0 +1,102 @@
+@core @core_group
+Feature: Automatic updating of groups and groupings
+  In order to check the expected results occur when updating groups and groupings in different scenarios
+  As a teacher
+  I need to create groups and groupings under different scenarios and check that the expected result occurs when attempting to update them.
+
+  Background:
+    Given the following "courses" exists:
+      | fullname | shortname | format |
+      | Course 1 | C1 | topics |
+    And the following "users" exists:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+    And the following "course enrolments" exists:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I expand "Users" node
+    And I follow "Groups"
+    And I press "Create group"
+    And I fill the moodle form with:
+      | Group name | Group (without ID) |
+    And I press "Save changes"
+    And I press "Create group"
+    And I fill the moodle form with:
+      | Group name | Group (with ID) |
+      | Group ID number | An ID |
+    And I press "Save changes"
+    And I follow "Groupings"
+    And I press "Create grouping"
+    And I fill the moodle form with:
+      | Grouping name | Grouping (without ID) |
+    And I press "Save changes"
+    And I press "Create grouping"
+    And I fill the moodle form with:
+      | Grouping name | Grouping (with ID) |
+      | Grouping ID number | An ID |
+    And I press "Save changes"
+    And I follow "Groups"
+
+  @javascript
+  Scenario: Update groups and groupings with ID numbers
+    Given I select "Group (with ID)" from "groups"
+    And I press "Edit group settings"
+    And the "idnumber" field should match "An ID" value
+    And I fill the moodle form with:
+      | Group name | Group (with ID) (updated) |
+      | Group ID number | An ID (updated) |
+    When I press "Save changes"
+    Then I should see "Group (with ID) (updated)"
+    And I select "Group (with ID) (updated)" from "groups"
+    And I press "Edit group settings"
+    And the "idnumber" field should match "An ID (updated)" value
+    And I press "Save changes"
+    And I follow "Groupings"
+    And I click on "Edit" "link" in the "Grouping (with ID)" "table_row"
+    And the "idnumber" field should match "An ID" value
+    And I fill the moodle form with:
+      | Grouping name | Grouping (with ID) (updated) |
+      | Grouping ID number | An ID (updated) |
+    And I press "Save changes"
+    And I should see "Grouping (with ID) (updated)"
+    And I click on "Edit" "link" in the "Grouping (with ID) (updated)" "table_row"
+    And the "idnumber" field should match "An ID (updated)" value
+
+  @javascript
+  Scenario: Update groups and groupings with ID numbers without the 'moodle/course:changeidnumber' capability
+    Given I log out
+    And I log in as "admin"
+    And I set the following system permissions of "Teacher" role:
+      | moodle/course:changeidnumber | Prevent |
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I expand "Users" node
+    And I follow "Groups"
+    And I select "Group (with ID)" from "groups"
+    When I press "Edit group settings"
+    Then the "idnumber" "field" should be readonly
+    And the "idnumber" field should match "An ID" value
+    And I fill the moodle form with:
+      | Group name | Group (with ID) (updated) |
+    And I press "Save changes"
+    And I should see "Group (with ID) (updated)"
+    And I select "Group (with ID) (updated)" from "groups"
+    And I press "Edit group settings"
+    And the "idnumber" "field" should be readonly
+    And the "idnumber" field should match "An ID" value
+    And I press "Save changes"
+    And I follow "Groupings"
+    And I click on "Edit" "link" in the "Grouping (with ID)" "table_row"
+    And the "idnumber" "field" should be readonly
+    And the "idnumber" field should match "An ID" value
+    And I fill the moodle form with:
+      | Grouping name | Grouping (with ID) (updated) |
+    And I press "Save changes"
+    And I should see "Grouping (with ID) (updated)"
+    And I click on "Edit" "link" in the "Grouping (with ID) (updated)" "table_row"
+    And the "idnumber" "field" should be readonly
+    And the "idnumber" field should match "An ID" value
+
index 1d06ba4..4c468c2 100644 (file)
@@ -168,15 +168,12 @@ $string['configcronremotepassword'] = 'This means that the cron.php script canno
 $string['configcurlcache'] = 'Time-to-live for cURL cache, in seconds.';
 $string['configcustommenuitems'] = 'You can configure a custom menu here to be shown by themes. Each line consists of some menu text, a link URL (optional), a tooltip title (optional) and a language code or comma-separated list of codes (optional, for displaying the line to users of the specified language only), separated by pipe characters. You can specify a structure using hyphens. For example:
 <pre>
-Moodle community|http://moodle.org
--Moodle free support|http://moodle.org/support
--Moodle development|http://moodle.org/development
---Moodle Tracker|http://tracker.moodle.org
---Moodle Docs|http://docs.moodle.org|Moodle Docs in German
+Moodle community|https://moodle.org
+-Moodle free support|https://moodle.org/support
+-Moodle development|https://moodle.org/development
+--Moodle Docs|http://docs.moodle.org|Moodle Docs
 --German Moodle Docs|http://docs.moodle.org/de|Documentation in German|de
--Moodle News|http://moodle.org/news Moodle company
--Moodle commercial hosting|http://moodle.com/hosting
--Moodle commercial support|http://moodle.com/support
+Moodle.com|http://moodle.com/
 </pre>';
 $string['configdbsessions'] = 'If enabled, this setting will use the database to store information about current sessions.  This is especially useful for large/busy sites or sites built on cluster of servers.  For most sites this should probably be left disabled so that the server disk is used instead.  Note that changing this setting now will log out all current users (including you). If you are using MySQL please make sure that \'max_allowed_packet\' in my.cnf (or my.ini) is at least 4M.';
 $string['configdebug'] = 'If you turn this on, then PHP\'s error_reporting will be increased so that more warnings are printed.  This is only useful for developers.';
@@ -230,7 +227,9 @@ $string['configfrontpage'] = 'The items selected above will be displayed on the
 $string['configfrontpagecourselimit'] = 'Maximum number of courses';
 $string['configfrontpagecourselimithelp'] = 'Maximum number of courses to be displayed on the site\'s front page in course listings.';
 $string['configfrontpageloggedin'] = 'The items selected above will be displayed on the site\'s front page when a user is logged in.';
-$string['configfullnamedisplay'] = 'This defines how names are shown when they are displayed in full. For most mono-lingual sites the most efficient setting is "firstname lastname", but you may choose to hide surnames altogether, or to leave it up to the current language pack to decide (some languages have different conventions). Placeholders that can be used are: firstname, lastname, firstnamephonetic, lastnamephonetic, middlename, and alternatename.';
+$string['configfullnamedisplay'] = 'This defines how names are shown when they are displayed in full. The default value, "language", leaves it to the string "fullnamedisplay" in the current language pack to decide. Some languages have different name display conventions.
+
+For most mono-lingual sites the most efficient setting is "firstname lastname", but you may choose to hide surnames altogether. Placeholders that can be used are: firstname, lastname, firstnamephonetic, lastnamephonetic, middlename, and alternatename.';
 $string['configgeoipfile'] = 'Location of GeoIP City binary data file. This file is not part of Moodle distribution and must be obtained separately from <a href="http://www.maxmind.com/">MaxMind</a>. You can either buy a commercial version or use the free version.<br />Simply download <a href="http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz" >http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz</a> and extract it into "{$a}" directory on your server.';
 $string['configgetremoteaddrconf'] = 'If your server is behind a reverse proxy, you can use this setting to specify which HTTP headers can be trusted to contain the remote IP address. The headers are read in order, using the first one that is available.';
 $string['configgradebookroles'] = 'This setting allows you to control who appears on the gradebook.  Users need to have at least one of these roles in a course to be shown in the gradebook for that course.';
@@ -491,6 +490,7 @@ $string['enablesafebrowserintegration'] = 'Enable Safe Exam Browser integration'
 $string['enablestats'] = 'Enable statistics';
 $string['enabletgzbackups'] = 'Enable new backup format';
 $string['enabletgzbackups_desc'] = 'If enabled, future backups will be created in a new compression format for .mbz files (internally stored as a .tar.gz file). This removes the 4GB backup size restriction and may improve performance. Restore supports both formats and the difference should be transparent to users.';
+$string['enabletgzbackups_nozlib'] = 'PHP extension &lsquo;zlib&rsquo; is not available. The new backup format relies on this extension and will be disabled until zlib is installed and enabled.';
 $string['enabletrusttext'] = 'Enable trusted content';
 $string['enablewebservices'] = 'Enable web services';
 $string['enablewsdocumentation'] = 'Web services documentation';
@@ -537,7 +537,7 @@ $string['forcelogin'] = 'Force users to log in';
 $string['forceloginforprofileimage'] = 'Force users to log in to view user pictures';
 $string['forceloginforprofileimage_help'] = 'If enabled, users must login in order to view user profile pictures and the default user picture will be used in all notification emails.';
 $string['forceloginforprofiles'] = 'Force users to log in for profiles';
-$string['forcetimezone'] = 'Force default timezone';
+$string['forcetimezone'] = 'Force timezone';
 $string['formatuninstallwithcourses'] = 'There are {$a->count} courses using {$a->format}. Their format will be changed to {$a->defaultformat} (default format for this site). Some format-specific data may be lost. Are you sure you want to proceed?';
 $string['frontpage'] = 'Front page';
 $string['frontpagebackup'] = 'Front page backup';
@@ -731,7 +731,7 @@ $string['mobile'] = 'Mobile';
 $string['mobilecssurl'] = 'CSS';
 $string['modchooserdefault'] = 'Activity chooser default';
 $string['modeditdefaults'] = 'Default values for activity settings';
-$string['modeditingmenu'] = 'Activitiy editing menus';
+$string['modeditingmenu'] = 'Activity editing menus';
 $string['modeditingmenu_desc'] = 'If enabled many of the activity editing icons shown when viewing a course with editing on will be displayed within a drop-down menu. This reduces the content on screen when editing a course by hiding the icons until they are needed.';
 $string['modsettings'] = 'Manage activities';
 $string['modulesecurity'] = 'Module security';
@@ -1130,6 +1130,7 @@ $string['webproxyinfo'] = 'Fill in following options if your Moodle server can n
 $string['xmlrpcrecommended'] = 'The xmlrpc extension is needed for hub communication, and useful for web services and Moodle networking';
 $string['yuicomboloading'] = 'YUI combo loading';
 $string['ziprequired'] = 'The Zip PHP extension is now required by Moodle, info-ZIP binaries or PclZip library are not used anymore.';
+$string['zlibenabled'] = 'zlib enabled';
 
 
 $string['caching'] = 'Caching';
index 18a34ff..c7d814f 100644 (file)
@@ -87,7 +87,7 @@ $string['configgeneralcomments'] = 'Sets the default for including comments in a
 $string['configgeneralfilters'] = 'Sets the default for including filters in a backup.';
 $string['configgeneralhistories'] = 'Sets the default for including user history within a backup.';
 $string['configgenerallogs'] = 'If enabled logs will be included in backups by default.';
-$string['configgeneralquestionbank'] = 'If enabled the question bank will be included in backups by default. PLEASE NOTE: Disabling this setting with disable the backup of activities which use the question bank, such as the quiz.';
+$string['configgeneralquestionbank'] = 'If enabled the question bank will be included in backups by default. PLEASE NOTE: Disabling this setting will disable the backup of activities which use the question bank, such as the quiz.';
 $string['configgeneralroleassignments'] = 'If enabled by default roles assignments will also be backed up.';
 $string['configgeneraluserscompletion'] = 'If enabled user completion information will be included in backups by default.';
 $string['configgeneralusers'] = 'Sets the default for whether to include users in backups.';
@@ -117,6 +117,7 @@ $string['errorminbackup20version'] = 'This backup file has been created with one
 $string['errorrestorefrontpage'] = 'Restoring over front page is not allowed.';
 $string['errorinvalidformat'] = 'Unknown backup format';
 $string['errorinvalidformatinfo'] = 'The selected file is not a valid Moodle backup file and can\'t be restored.';
+$string['errortgznozlib'] = 'The selected file is in the new backup format and cannot be restored because the zlib PHP extension is not available on this system.';
 $string['executionsuccess'] = 'The backup file was successfully created.';
 $string['filename'] = 'Filename';
 $string['filealiasesrestorefailures'] = 'Aliases restore failures';
index 3065685..14cd22b 100644 (file)
@@ -56,7 +56,7 @@ $string['anymethodcourseset'] = 'Any of the selected courses is complete';
 $string['anymethodmanual'] = 'Any of the selected roles awards the badge';
 $string['anymethodprofile'] = 'Any of the selected profile fields has been completed';
 $string['attachment'] = 'Attach badge to message';
-$string['attachment_help'] = 'If checked, an issued badge will be attached to the recepient\'s email for download';
+$string['attachment_help'] = 'If checked, an issued badge file will be attached to the recepient\'s email for download. Email attachments must be enabled in site settings to use this option.';
 $string['award'] = 'Award badge';
 $string['awardedtoyou'] = 'Issued to me';
 $string['awardoncron'] = 'Access to the badges was successfully enabled. Too many users can instantly earn this badge. To ensure site performance, this action will take some time to process.';
@@ -367,16 +367,16 @@ Once a badge has been issued to at least one user, it automatically becomes **LO
 We want to make sure that all users complete the same requirements to earn a badge. Currently, it is not possible to revoke badges. If we allowed badges requirements to be modified all the time, we would most likely end up with users having the same badge for meeting completely different requirements.';
 $string['subject'] = 'Message subject';
 $string['variablesubstitution'] = 'Variable substitution in messages.';
-$string['variablesubstitution_help'] = 'In a badge message, certain variables can be inserted into the subject and/or body of a message so that they will be replaced with real values when the message is sent. The variables should be inserted into the the text exactly as they are shown below. The following variables can be used:
+$string['variablesubstitution_help'] = 'In a badge message, certain variables can be inserted into the subject and/or body of a message so that they will be replaced with real values when the message is sent. The variables should be inserted into the text exactly as they are shown below. The following variables can be used:
 
 %badgename%
-:   This will be replaced by the badge\'s full name.
+: This will be replaced by the badge\'s full name.
 
 %username%
-:   This will be replaced by the recipient\'s full name.
+: This will be replaced by the recipient\'s full name.
 
 %badgelink%
-:   This will be replaced by the public URL with information about the issued badge.';
+: This will be replaced by the public URL with information about the issued badge.';
 $string['viewbadge'] = 'View issued badge';
 $string['visible'] = 'Visible';
 $string['warnexpired'] = ' (This badge has expired!)';
index b259c16..feb6833 100644 (file)
@@ -23,7 +23,7 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['addfields'] = 'Add {$a} fields to form';
+$string['addfields'] = 'Add {$a} field(s) to form';
 $string['advancedelement'] = 'Advanced element';
 $string['close'] = 'Close';
 $string['day'] = 'Day';
index a95d2e7..95a7f2e 100644 (file)
@@ -210,6 +210,7 @@ $string['blocksetup'] = 'Setting up block tables';
 $string['blocksuccess'] = '{$a} tables have been set up correctly';
 $string['brief'] = 'Brief';
 $string['bulkactions'] = 'Bulk actions';
+$string['bulkactionselect'] = '{$a} bulk action selection';
 $string['bulkmovecoursessuccess'] = 'Successfully moved {$a->courses} courses into {$a->category}';
 $string['bycourseorder'] = 'By course order';
 $string['byname'] = 'by {$a}';
@@ -221,12 +222,14 @@ $string['categories'] = 'Course categories';
 $string['categoriesandcoures'] = 'Course categories and courses';
 $string['category'] = 'Category';
 $string['categoryadded'] = 'The category \'{$a}\' was added';
+$string['categorybulkaction'] = 'Bulk actions for selected categories';
 $string['categorycontents'] = 'Subcategories and courses';
 $string['categorycurrentcontents'] = 'Contents of {$a}';
 $string['categorydeleted'] = 'The category \'{$a}\' was deleted';
 $string['categoryduplicate'] = 'A category named \'{$a}\' already exists!';
 $string['categorymodifiedcancel'] = 'Category was modified! Please cancel and try again.';
 $string['categoryname'] = 'Category name';
+$string['categorysubcategoryof'] = '{$a->category} - subcategory of {$a->parentcategory}';
 $string['idnumbercoursecategory'] = 'Category ID number';
 $string['idnumbercoursecategory_help'] = 'The ID number of a course category  is only used when matching the category against external systems and is not displayed anywhere on the site. If the category has an official code name it may be entered, otherwise the field can be left blank.';
 $string['categoryupdated'] = 'The category \'{$a}\' was updated';
@@ -252,6 +255,7 @@ $string['clicktochangeinbrackets'] = '{$a} (Click to change)';
 $string['closewindow'] = 'Close this window';
 $string['collapse'] = 'Collapse';
 $string['collapseall'] = 'Collapse all';
+$string['collapsecategory'] = 'Collapse {$a}';
 $string['commentincontext'] = 'Find this comment in context';
 $string['comments'] = 'Comments';
 $string['commentsnotenabled'] = 'Comments feature is not enabled';
@@ -297,6 +301,7 @@ $string['courseapprovedsubject'] = 'Your course has been approved!';
 $string['courseavailable'] = 'This course is available to students';
 $string['courseavailablenot'] = 'This course is not available to students';
 $string['coursebackup'] = 'Course backup';
+$string['coursebulkaction'] = 'Bulk actions for selected courses';
 $string['coursecategories'] = 'Course categories';
 $string['coursecategory'] = 'Course category';
 $string['coursecategory_help'] = 'This setting determines the category in which the course will appear in the list of courses.';
@@ -735,6 +740,7 @@ $string['existingstudents'] = 'Enrolled students';
 $string['existingteachers'] = 'Existing teachers';
 $string['expand'] = 'Expand';
 $string['expandall'] = 'Expand all';
+$string['expandcategory'] = 'Expand {$a}';
 $string['explanation'] = 'Explanation';
 $string['extendenrol'] = 'Extend enrolment (individual)';
 $string['extendperiod'] = 'Extended period';
@@ -1087,6 +1093,8 @@ $string['messagedselectedusers'] = 'Selected users have been messaged and the re
 $string['messagedselectedusersfailed'] = 'Something went wrong while messaging selected users.  Some may have received the email.';
 $string['messageprovider:availableupdate'] = 'Available update notifications';
 $string['messageprovider:backup'] = 'Backup notifications';
+$string['messageprovider:badgecreatornotice'] = 'Badge creator notifications';
+$string['messageprovider:badgerecipientnotice'] = 'Badge recipient notifications';
 $string['messageprovider:courserequestapproved'] = 'Course creation request approval notification';
 $string['messageprovider:courserequested'] = 'Course creation request notification';
 $string['messageprovider:courserequestrejected'] = 'Course creation request rejection notification';
@@ -1600,6 +1608,9 @@ $string['selectmoduletoviewhelp'] = 'Select an activity or resource to view its
 Double-click on an activity or resource name to quickly add it.';
 $string['selectnos'] = 'Select all \'No\'';
 $string['selectperiod'] = 'Select period';
+$string['selectcategorysort'] = 'Which categories would you like to sort';
+$string['selectcategorysortby'] = 'Select how you would like to sort categories';
+$string['selectcoursesortby'] = 'Select how you would like to sort courses';
 $string['senddetails'] = 'Send my details via email';
 $string['separate'] = 'Separate';
 $string['separateandconnected'] = 'Separate and Connected ways of knowing';
@@ -1624,6 +1635,7 @@ $string['showall'] = 'Show all {$a}';
 $string['showallcourses'] = 'Show all courses';
 $string['showallusers'] = 'Show all users';
 $string['showblockcourse'] = 'Show list of courses containing block';
+$string['showcategory'] = 'Show {$a}';
 $string['showcomments'] = 'Show/hide comments';
 $string['showcommentsnonjs'] = 'Show comments';
 $string['showdescription'] = 'Display description on course page';
@@ -1668,6 +1680,9 @@ $string['sizegb'] = 'GB';
 $string['sizekb'] = 'KB';
 $string['sizemb'] = 'MB';
 $string['skipped'] = 'Skipped';
+$string['skiptocategorylisting'] = 'Skip to the category listings';
+$string['skiptocourselisting'] = 'Skip to the course listings';
+$string['skiptocoursedetails'] = 'Skip to the detailed course information';
 $string['skypeid'] = 'Skype ID';
 $string['socialheadline'] = 'Social forum - latest topics';
 $string['someallowguest'] = 'Some courses may allow guest access';
@@ -1880,7 +1895,7 @@ $string['usingexistingcourse'] = 'Using existing course';
 $string['valuealreadyused'] = 'This value has already been used.';
 $string['version'] = 'Version';
 $string['view'] = 'View';
-$string['viewing'] = 'Viewing {$a}';
+$string['viewing'] = 'Viewing:';
 $string['viewallcourses'] = 'View all courses';
 $string['viewallcoursescategories'] = 'View all courses and categories';
 $string['viewmore'] = 'View more';
index 60f2cd8..27b569d 100644 (file)
@@ -339,7 +339,7 @@ Alternatively, you may wish for students to submit each question as they go alon
 Those are probably the two most commonly used modes of behaviour. ';
 $string['importfromcoursefiles'] = '... or choose a course file to import.';
 $string['importfromupload'] = 'Select a file to upload ...';
-$string['includesubcategories'] = 'Also show questions from sub-categories';
+$string['includesubcategories'] = 'Also show questions from subcategories';
 $string['incorrect'] = 'Incorrect';
 $string['incorrectfeedback'] = 'For any incorrect response';
 $string['incorrectfeedbackdefault'] = 'Your answer is incorrect.';
@@ -367,7 +367,7 @@ $string['partiallycorrect'] = 'Partially correct';
 $string['partiallycorrectfeedback'] = 'For any partially correct response';
 $string['partiallycorrectfeedbackdefault'] = 'Your answer is partially correct.';
 $string['penaltyforeachincorrecttry'] = 'Penalty for each incorrect try';
-$string['penaltyforeachincorrecttry_help'] = 'When you run your questions using the \'Interactive with multiple tries\' or \'Adaptive mode\' behaviour, so that the the student will have several tries to get the question right, then this option controls how much they are penalised for each incorrect try.
+$string['penaltyforeachincorrecttry_help'] = 'When questions are run using the \'Interactive with multiple tries\' or \'Adaptive mode\' behaviour, so that the student will have several tries to get the question right, then this option controls how much they are penalised for each incorrect try.
 
 The penalty is a proportion of the total question grade, so if the question is worth three marks, and the penalty is 0.3333333, then the student will score 3 if they get the question right first time, 2 if they get it right second try, and 1 of they get it right on the third try.';
 $string['previewquestion'] = 'Preview question: {$a}';
index 26d4447..8219cdb 100644 (file)
@@ -632,27 +632,36 @@ function badges_notify_badge_award(badge $badge, $userid, $issued, $filepathhash
     $message = badge_message_from_template($badge->message, $params);
     $plaintext = format_text_email($message, FORMAT_HTML);
 
-    // TODO: $filepathhash may be moodle_url instance too...
-
-    if ($badge->attachment && is_string($filepathhash)) {
+    // Notify recipient.
+    $eventdata = new stdClass();
+    $eventdata->component         = 'moodle';
+    $eventdata->name              = 'badgerecipientnotice';
+    $eventdata->userfrom          = $userfrom;
+    $eventdata->userto            = $userto;
+    $eventdata->notification      = 1;
+    $eventdata->subject           = $badge->messagesubject;
+    $eventdata->fullmessage       = $plaintext;
+    $eventdata->fullmessageformat = FORMAT_PLAIN;
+    $eventdata->fullmessagehtml   = $message;
+    $eventdata->smallmessage      = $plaintext;
+
+    // Attach badge image if possible.
+    if (!empty($CFG->allowattachments) && $badge->attachment && is_string($filepathhash)) {
         $fs = get_file_storage();
         $file = $fs->get_file_by_hash($filepathhash);
-        $attachment = $file->copy_content_to_temp();
-        email_to_user($userto,
-            $userfrom,
-            $badge->messagesubject,
-            $plaintext,
-            $message,
-            str_replace($CFG->dataroot, '', $attachment),
-            str_replace(' ', '_', $badge->name) . ".png"
-        );
-        @unlink($attachment);
+        $eventdata->attachment = $file;
+        $eventdata->attachname = str_replace(' ', '_', $badge->name) . ".png";
+
+        message_send($eventdata);
     } else {
-        email_to_user($userto, $userfrom, $badge->messagesubject, $plaintext, $message);
+        message_send($eventdata);
     }
 
     // Notify badge creator about the award if they receive notifications every time.
     if ($badge->notification == 1) {
+        $userfrom = core_user::get_noreply_user();
+        $userfrom->maildisplay = true;
+
         $creator = $DB->get_record('user', array('id' => $badge->usercreated), '*', MUST_EXIST);
         $a = new stdClass();
         $a->user = fullname($userto);
@@ -662,15 +671,15 @@ function badges_notify_badge_award(badge $badge, $userid, $issued, $filepathhash
 
         $eventdata = new stdClass();
         $eventdata->component         = 'moodle';
-        $eventdata->name              = 'instantmessage';
+        $eventdata->name              = 'badgecreatornotice';
         $eventdata->userfrom          = $userfrom;
         $eventdata->userto            = $creator;
         $eventdata->notification      = 1;
         $eventdata->subject           = $creatorsubject;
-        $eventdata->fullmessage       = $creatormessage;
+        $eventdata->fullmessage       = format_text_email($creatormessage, FORMAT_HTML);
         $eventdata->fullmessageformat = FORMAT_PLAIN;
-        $eventdata->fullmessagehtml   = format_text($creatormessage, FORMAT_HTML);
-        $eventdata->smallmessage      = '';
+        $eventdata->fullmessagehtml   = $creatormessage;
+        $eventdata->smallmessage      = $creatorsubject;
 
         message_send($eventdata);
         $DB->set_field('badge_issued', 'issuernotified', time(), array('badgeid' => $badge->id, 'userid' => $userid));
@@ -969,10 +978,17 @@ function badges_bake($hash, $badgeid, $userid = 0, $pathhash = false) {
                 }
             }
         } else {
-            debugging('Error baking badge image!');
+            debugging('Error baking badge image!', DEBUG_DEVELOPER);
+            return;
         }
     }
 
+    // If file exists and we just need its path hash, return it.
+    if ($pathhash) {
+        $file = $fs->get_file($user_context->id, 'badges', 'userbadge', $badge->id, '/', $hash . '.png');
+        return $file->get_pathnamehash();
+    }
+
     $fileurl = moodle_url::make_pluginfile_url($user_context->id, 'badges', 'userbadge', $badge->id, '/', $hash, true);
     return $fileurl;
 }
index 974124f..79e62c1 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Box REST Client Library for PHP5 Developers
+ * Box.net client.
  *
- *
- * @package moodlecore
+ * @package core
  * @author James Levy <james@box.net>
  * @link http://enabled.box.net
  * @access public
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->libdir . '/oauthlib.php');
+
+/**
+ * Box.net client class.
+ *
+ * @package    core
+ * @copyright  2013 Frédéric Massart
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class boxnet_client extends oauth2_client {
+
+    /** @const API URL */
+    const API = 'https://api.box.com/2.0';
+
+    /** @const UPLOAD_API URL */
+    const UPLOAD_API = 'https://upload.box.com/api/2.0';
+
+    /**
+     * Return authorize URL.
+     *
+     * @return string
+     */
+    protected function auth_url() {
+        return 'https://www.box.com/api/oauth2/authorize';
+    }
+
+    /**
+     * Create a folder.
+     *
+     * @param string $foldername The folder name.
+     * @param int $parentid The ID of the parent folder.
+     * @return array Information about the new folder.
+     */
+    public function create_folder($foldername, $parentid = 0) {
+        $params = array('name' => $foldername, 'parent' => array('id' => (string) $parentid));
+        $this->reset_state();
+        $result = $this->post($this->make_url("/folders"), json_encode($params));
+        $result = json_decode($result);
+        return $result;
+    }
+
+    /**
+     * Download the file.
+     *
+     * @param int $fileid File ID.
+     * @param string $path Path to download the file to.
+     * @return bool Success or not.
+     */
+    public function download_file($fileid, $path) {
+        $this->reset_state();
+        $result = $this->download_one($this->make_url("/files/$fileid/content"), array(),
+            array('filepath' => $path, 'CURLOPT_FOLLOWLOCATION' => true));
+        return ($result === true && $this->info['http_code'] === 200);
+    }
+
+    /**
+     * Get info of a file.
+     *
+     * @param int $fileid File ID.
+     * @return object
+     */
+    public function get_file_info($fileid) {
+        $this->reset_state();
+        $result = $this->request($this->make_url("/files/$fileid"));
+        return json_decode($result);
+    }
+
+    /**
+     * Get a folder content.
+     *
+     * @param int $folderid Folder ID.
+     * @return object
+     */
+    public function get_folder_items($folderid = 0) {
+        $this->reset_state();
+        $result = $this->request($this->make_url("/folders/$folderid/items",
+            array('fields' => 'id,name,type,modified_at,size,owned_by')));
+        return json_decode($result);
+    }
+
+    /**
+     * Log out.
+     *
+     * @return void
+     */
+    public function log_out() {
+        if ($accesstoken = $this->get_accesstoken()) {
+            $params = array(
+                'client_id' => $this->get_clientid(),
+                'client_secret' => $this->get_clientsecret(),
+                'token' => $accesstoken->token
+            );
+            $this->reset_state();
+            $this->post($this->revoke_url(), $params);
+        }
+        parent::log_out();
+    }
+
+    /**
+     * Build a request URL.
+     *
+     * @param string $uri The URI to request.
+     * @param array $params Query string parameters.
+     * @param bool $uploadapi Whether this works with the upload API or not.
+     * @return string
+     */
+    protected function make_url($uri, $params = array(), $uploadapi = false) {
+        $api = $uploadapi ? self::UPLOAD_API : self::API;
+        $url = new moodle_url($api . '/' . ltrim($uri, '/'), $params);
+        return $url->out(false);
+    }
+
+    /**
+     * Rename a file.
+     *
+     * @param int $fileid The file ID.
+     * @param string $newname The new file name.
+     * @return object Box.net file object.
+     */
+    public function rename_file($fileid, $newname) {
+        // This requires a PUT request with data within it. We cannot use
+        // the standard PUT request 'CURLOPT_PUT' because it expects a file.
+        $data = array('name' => $newname);
+        $options = array(
+            'CURLOPT_CUSTOMREQUEST' => 'PUT',
+            'CURLOPT_POSTFIELDS' => json_encode($data)
+        );
+        $url = $this->make_url("/files/$fileid");
+        $this->reset_state();
+        $result = $this->request($url, $options);
+        $result = json_decode($result);
+        return $result;
+    }
+
+    /**
+     * Resets curl for multiple requests.
+     *
+     * @return void
+     */
+    public function reset_state() {
+        $this->cleanopt();
+        $this->resetHeader();
+    }
+
+    /**
+     * Return the revoke URL.
+     *
+     * @return string
+     */
+    protected function revoke_url() {
+        return 'https://www.box.com/api/oauth2/revoke';
+    }
+
+    /**
+     * Share a file and return the link to it.
+     *
+     * @param string $fileid The file ID.
+     * @param bool $businesscheck Whether or not to check if the user can share files, has a business account.
+     * @return object
+     */
+    public function share_file($fileid, $businesscheck = true) {
+        // Sharing the file, this requires a PUT request with data within it. We cannot use
+        // the standard PUT request 'CURLOPT_PUT' because it expects a file.
+        $data = array('shared_link' => array('access' => 'open', 'permissions' =>
+            array('can_download' => true, 'can_preview' => true)));
+        $options = array(
+            'CURLOPT_CUSTOMREQUEST' => 'PUT',
+            'CURLOPT_POSTFIELDS' => json_encode($data)
+        );
+        $this->reset_state();
+        $result = $this->request($this->make_url("/files/$fileid"), $options);
+        $result = json_decode($result);
+
+        if ($businesscheck) {
+            // Checks that the user has the right to share the file. If not, throw an exception.
+            $this->reset_state();
+            $this->head($result->shared_link->download_url);
+            $info = $this->get_info();
+            if ($info['http_code'] == 403) {
+                throw new moodle_exception('No permission to share the file');
+            }
+        }
+
+        return $result->shared_link;
+    }
+
+    /**
+     * Search.
+     *
+     * @return object
+     */
+    public function search($query) {
+        $this->reset_state();
+        $result = $this->request($this->make_url('/search', array('query' => $query, 'limit' => 50, 'offset' => 0)));
+        return json_decode($result);
+    }
+
+    /**
+     * Return token URL.
+     *
+     * @return string
+     */
+    protected function token_url() {
+        return 'https://www.box.com/api/oauth2/token';
+    }
+
+    /**
+     * Upload a file.
+     *
+     * Please note that the file is named on Box.net using the path we are providing, and so
+     * the file has the name of the stored_file hash.
+     *
+     * @param stored_file $storedfile A stored_file.
+     * @param integer $parentid The ID of the parent folder.
+     * @return object Box.net file object.
+     */
+    public function upload_file(stored_file $storedfile, $parentid = 0) {
+        $url = $this->make_url('/files/content', array(), true);
+        $options = array(
+            'filename' => $storedfile,
+            'parent_id' => $parentid
+        );
+        $this->reset_state();
+        $result = $this->post($url, $options);
+        $result = json_decode($result);
+        return $result;
+    }
+
+}
+
 /**
- * @package moodlecore
+ * Box REST Client Library for PHP5 Developers.
+ *
+ * Deprecation note: As of the 14th of December 2013 Box.net APIv1, used by this class,
+ * is reaching its end of life. Please use boxnet_client() instead.
+ *
+ * @package core
+ * @author James Levy <james@box.net>
+ * @link http://enabled.box.net
+ * @access public
+ * @version 1.0
  * @copyright copyright Box.net 2007
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @deprecated since 2.6, 2.5.3, 2.4.7
  */
 class boxclient {
     /** @var string */
index fa1931c..7f5f216 100644 (file)
@@ -83,19 +83,26 @@ class core_collator {
                     if ($errorcode !== 0) {
                         // Get the actual locale being used, e.g. en, he, zh
                         $localeinuse = $collator->getLocale(Locale::ACTUAL_LOCALE);
-                        // Check for the common fallback warning error codes. If this occurred
-                        // there is normally little to worry about:
-                        // - U_USING_DEFAULT_WARNING (127)  - default fallback locale used (pt => UCA)
-                        // - U_USING_FALLBACK_WARNING (128) - fallback locale used (de_CH => de)
-                        // (UCA: Unicode Collation Algorithm http://unicode.org/reports/tr10/)
+                        // Check for the common fallback warning error codes. If any of the two
+                        // following errors occurred, there is normally little to worry about:
+                        // * U_USING_FALLBACK_WARNING (-128) indicates that a fall back locale was
+                        //   used. For example, 'de_CH' was requested, but nothing was found
+                        //   there, so 'de' was used.
+                        // * U_USING_DEFAULT_WARNING (-127) indicates that the default locale
+                        //   data was used; neither the requested locale nor any of its fall
+                        //   back locales could be found. For example, 'pt' was requested, but
+                        //   UCA was used (Unicode Collation Algorithm http://unicode.org/reports/tr10/).
+                        // See http://www.icu-project.org/apiref/icu4c/classicu_1_1ResourceBundle.html
                         if ($errorcode === -127 || $errorcode === -128) {
                             // Check if the locale in use is UCA default one ('root') or
                             // if it is anything like the locale we asked for
                             if ($localeinuse !== 'root' && strpos($locale, $localeinuse) !== 0) {
                                 // The locale we asked for is completely different to the locale
                                 // we have received, let the user know via debugging
-                                debugging('Invalid locale: "' . $locale . '", with warning (not fatal) "' . $errormessage .
-                                '", falling back to "' . $collator->getLocale(Locale::VALID_LOCALE) . '"');
+                                debugging('Locale warning (not fatal) '.$errormessage.': '.
+                                    'Requested locale "'.$locale.'" not found, locale "'.$localeinuse.'" used instead. '.
+                                    'The most specific locale supported by ICU relatively to the requested locale is "'.
+                                    $collator->getLocale(Locale::VALID_LOCALE).'".');
                             } else {
                                 // Nothing to do here, this is expected!
                                 // The Moodle locale setting isn't what the collator expected but
@@ -105,8 +112,10 @@ class core_collator {
                         } else {
                             // We've received some other sort of non fatal warning - let the
                             // user know about it via debugging.
-                            debugging('Problem with locale: "' . $locale . '", with message "' . $errormessage .
-                            '", falling back to "' . $collator->getLocale(Locale::VALID_LOCALE) . '"');
+                            debugging('Problem with locale: '.$errormessage.'. '.
+                                'Requested locale: "'.$locale.'", actual locale "'.$localeinuse.'". '.
+                                'The most specific locale supported by ICU relatively to the requested locale is "'.
+                                $collator->getLocale(Locale::VALID_LOCALE).'".');
                         }
                     }
                     // Store the collator object now that we can be sure it is in a workable condition
index b5ca099..40bd6b3 100644 (file)
@@ -904,6 +904,7 @@ class core_plugin_manager {
             'qformat' => array('blackboard'),
             'enrol' => array('authorize'),
             'tool' => array('bloglevelupgrade'),
+            'theme' => array('mymobile'),
         );
 
         if (!isset($plugins[$type])) {
index badf854..7bcee34 100644 (file)
@@ -128,6 +128,7 @@ class core_user {
 
         if (empty(self::$noreplyuser)) {
             self::$noreplyuser = self::get_dummy_user_record();
+            self::$noreplyuser->maildisplay = '1'; // Show to all.
         }
         self::$noreplyuser->emailstop = 1; // Force msg stop for this user.
         return self::$noreplyuser;
index ce39f5e..9323ea5 100644 (file)
@@ -2716,11 +2716,7 @@ class course_in_list implements IteratorAggregate {
                     continue;
                 }
                 $user = new stdClass();
-                $user->id = $ruser->id;
-                $user->username = $ruser->username;
-                foreach (get_all_user_name_fields() as $addname) {
-                    $user->$addname = $ruser->$addname;
-                }
+                $user = username_load_fields_from_object($user, $ruser, null, array('id', 'username'));
                 $role = new stdClass();
                 $role->id = $ruser->roleid;
                 $role->name = $ruser->rolename;
index 88e99c5..75afbb7 100644 (file)
@@ -78,6 +78,21 @@ $messageproviders = array (
     // Course request rejection notification
     'courserequestrejected' => array (
         'capability'  => 'moodle/course:request'
-    )
+    ),
 
+    // Badge award notification to a badge recipient.
+    'badgerecipientnotice' => array (
+        'defaults' => array(
+            'popup' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
+            'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDOFF,
+        ),
+        'capability'  => 'moodle/badges:earnbadge'
+    ),
+
+    // Badge award notification to a badge creator (mostly cron-based).
+    'badgecreatornotice' => array (
+        'defaults' => array(
+            'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDOFF,
+        )
+    )
 );
index 2f14254..bd6e18d 100644 (file)
@@ -2780,14 +2780,29 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2013102500.01);
     }
 
-    if ($oldversion < 2013110400.00) {
+    if ($oldversion < 2013110500.01) {
+        // MDL-38228. Corrected course_modules upgrade script instead of 2013021801.01.
+
+        // This upgrade script fixes the mismatches between DB fields course_modules.section
+        // and course_sections.sequence. It makes sure that each module is included
+        // in the sequence of at least one section.
+        // There is also a separate script for admins: admin/cli/fix_course_sortorder.php
+
+        // This script in included in each major version upgrade process so make sure we don't run it twice.
+        if (empty($CFG->movingmoduleupgradescriptwasrun)) {
+            upgrade_course_modules_sequences();
 
-        if (!check_dir_exists($CFG->dirroot . '/theme/mymobile', false)) {
-            // Delete from config_plugins.
-            $DB->delete_records('config_plugins', array('plugin' => 'theme_mymobile'));
-            // Delete the config logs.
-            $DB->delete_records('config_log', array('plugin' => 'theme_mymobile'));
+            // To skip running the same script on the upgrade to the next major release.
+            set_config('movingmoduleupgradescriptwasrun', 1);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2013110500.01);
+    }
 
+    if ($oldversion < 2013110600.01) {
+
+        if (!file_exists($CFG->dirroot . '/theme/mymobile')) {
             // Replace the mymobile settings.
             $DB->set_field('course', 'theme', 'clean', array('theme' => 'mymobile'));
             $DB->set_field('course_categories', 'theme', 'clean', array('theme' => 'mymobile'));
@@ -2795,42 +2810,41 @@ function xmldb_main_upgrade($oldversion) {
             $DB->set_field('mnet_host', 'theme', 'clean', array('theme' => 'mymobile'));
 
             // Replace the theme configs.
-            if (get_config('core', 'theme') == 'mymobile') {
+            if (get_config('core', 'theme') === 'mymobile') {
                 set_config('theme', 'clean');
             }
-            if (get_config('core', 'thememobile') == 'mymobile') {
+            if (get_config('core', 'thememobile') === 'mymobile') {
                 set_config('thememobile', 'clean');
             }
-            if (get_config('core', 'themelegacy') == 'mymobile') {
+            if (get_config('core', 'themelegacy') === 'mymobile') {
                 set_config('themelegacy', 'clean');
             }
-            if (get_config('core', 'themetablet') == 'mymobile') {
+            if (get_config('core', 'themetablet') === 'mymobile') {
                 set_config('themetablet', 'clean');
             }
+
+            // Hacky emulation of plugin uninstallation.
+            unset_all_config_for_plugin('theme_mymobile');
         }
 
         // Main savepoint reached.
-        upgrade_main_savepoint(true, 2013110400.00);
+        upgrade_main_savepoint(true, 2013110600.01);
     }
 
-    if ($oldversion < 2013110500.01) {
-        // MDL-38228. Corrected course_modules upgrade script instead of 2013021801.01.
-
-        // This upgrade script fixes the mismatches between DB fields course_modules.section
-        // and course_sections.sequence. It makes sure that each module is included
-        // in the sequence of at least one section.
-        // There is also a separate script for admins: admin/cli/fix_course_sortorder.php
+    if ($oldversion < 2013110600.02) {
 
-        // This script in included in each major version upgrade process so make sure we don't run it twice.
-        if (empty($CFG->movingmoduleupgradescriptwasrun)) {
-            upgrade_course_modules_sequences();
-
-            // To skip running the same script on the upgrade to the next major release.
-            set_config('movingmoduleupgradescriptwasrun', 1);
+        // If the user is logged in, we ensure that the alternate name fields are present
+        // in the session. It will not be the case when upgrading from 2.5 downwards.
+        if (!empty($USER->id)) {
+            $refreshuser = $DB->get_record('user', array('id' => $USER->id));
+            $fields = array('firstnamephonetic', 'lastnamephonetic', 'middlename', 'alternatename', 'firstname', 'lastname');
+            foreach ($fields as $field) {
+                $USER->{$field} = $refreshuser->{$field};
+            }
         }
 
         // Main savepoint reached.
-        upgrade_main_savepoint(true, 2013110500.01);
+        upgrade_main_savepoint(true, 2013110600.02);
     }
 
     return true;
index 0555bf3..dc6e1b7 100644 (file)
@@ -1086,40 +1086,19 @@ abstract class sql_generator {
             $name .= substr(trim($field),0,3);
         }
         // Prepend the prefix
-        $name = $this->prefix . $name;
+        $name = trim($this->prefix . $name);
 
-        $name = substr(trim($name), 0, $this->names_max_length - 1 - strlen($suffix)); //Max names_max_length
-
-        // Add the suffix
-        $namewithsuffix = $name;
-        if ($suffix) {
-            $namewithsuffix = $namewithsuffix . '_' . $suffix;
-        }
+        // Make sure name does not exceed the maximum name length and add suffix.
+        $maxlengthwithoutsuffix = $this->names_max_length - strlen($suffix) - ($suffix ? 1 : 0);
+        $namewithsuffix = substr($name, 0, $maxlengthwithoutsuffix) . ($suffix ? ('_' . $suffix) : '');
 
         // If the calculated name is in the cache, or if we detect it by introspecting the DB let's modify if
-        if (in_array($namewithsuffix, $used_names) || $this->isNameInUse($namewithsuffix, $suffix, $tablename)) {
-            $counter = 2;
-            // If have free space, we add 2
-            if (strlen($namewithsuffix) < $this->names_max_length) {
-                $newname = $name . $counter;
-            // Else replace the last char by 2
-            } else {
-                $newname = substr($name, 0, strlen($name)-1) . $counter;
-            }
-            $newnamewithsuffix = $newname;
-            if ($suffix) {
-                $newnamewithsuffix = $newnamewithsuffix . '_' . $suffix;
-            }
+        $counter = 1;
+        while (in_array($namewithsuffix, $used_names) || $this->isNameInUse($namewithsuffix, $suffix, $tablename)) {
             // Now iterate until not used name is found, incrementing the counter
-            while (in_array($newnamewithsuffix, $used_names) || $this->isNameInUse($newnamewithsuffix, $suffix, $tablename)) {
-                $counter++;
-                $newname = substr($name, 0, strlen($newname)-1) . $counter;
-                $newnamewithsuffix = $newname;
-                if ($suffix) {
-                    $newnamewithsuffix = $newnamewithsuffix . '_' . $suffix;
-                }
-            }
-            $namewithsuffix = $newnamewithsuffix;
+            $counter++;
+            $namewithsuffix = substr($name, 0, $maxlengthwithoutsuffix - strlen($counter)) .
+                    $counter . ($suffix ? ('_' . $suffix) : '');
         }
 
         // Add the name to the cache
index 07a4a3f..7644a23 100644 (file)
@@ -698,6 +698,16 @@ class core_ddl_testcase extends database_driver_testcase {
         $this->assertSame('N', $columns['onenumber']->meta_type);
         $this->assertEquals(2.550, $DB->get_field('test_table1', 'onenumber', array(), IGNORE_MULTIPLE)); // Check default has been applied.
 
+        // Add one numeric field with scale of 0 and check it.
+        $field = new xmldb_field('onenumberwith0scale');
+        $field->set_attributes(XMLDB_TYPE_NUMBER, '6,0', null, XMLDB_NOTNULL, null, 2);
+        $dbman->add_field($table, $field);
+        $this->assertTrue($dbman->field_exists($table, 'onenumberwith0scale'));
+        $columns = $DB->get_columns('test_table1');
+        $this->assertEquals(6, $columns['onenumberwith0scale']->max_length);
+        // We can not use assertEquals as that accepts null/false as a valid value.
+        $this->assertSame('0', strval($columns['onenumberwith0scale']->scale));
+
         // Add one float field and check it (not official type - must work as number).
         $field = new xmldb_field('onefloat');
         $field->set_attributes(XMLDB_TYPE_FLOAT, '6,3', null, XMLDB_NOTNULL, null, 3.550);
@@ -1879,6 +1889,59 @@ class core_ddl_testcase extends database_driver_testcase {
         }
     }
 
+    public function test_object_name() {
+        $gen = $this->tdb->get_manager()->generator;
+
+        // This will form short object name and max length should not be exceeded.
+        $table = 'tablename';
+        $fields = 'id';
+        $suffix = 'pk';
+        for ($i=0; $i<12; $i++) {
+            $this->assertLessThanOrEqual($gen->names_max_length,
+                    strlen($gen->getNameForObject($table, $fields, $suffix)),
+                    'Generated object name is too long. $i = '.$i);
+        }
+
+        // This will form too long object name always and it must be trimmed to exactly 30 chars.
+        $table = 'aaaa_bbbb_cccc_dddd_eeee_ffff_gggg';
+        $fields = 'aaaaa,bbbbb,ccccc,ddddd';
+        $suffix = 'idx';
+        for ($i=0; $i<12; $i++) {
+            $this->assertEquals($gen->names_max_length,
+                    strlen($gen->getNameForObject($table, $fields, $suffix)),
+                    'Generated object name is too long. $i = '.$i);
+        }
+
+        // Same test without suffix.
+        $table = 'bbbb_cccc_dddd_eeee_ffff_gggg_hhhh';
+        $fields = 'aaaaa,bbbbb,ccccc,ddddd';
+        $suffix = '';
+        for ($i=0; $i<12; $i++) {
+            $this->assertEquals($gen->names_max_length,
+                    strlen($gen->getNameForObject($table, $fields, $suffix)),
+                    'Generated object name is too long. $i = '.$i);
+        }
+
+        // This must only trim name when counter is 10 or more.
+        $table = 'cccc_dddd_eeee_ffff_gggg_hhhh_iiii';
+        $fields = 'id';
+        $suffix = 'idx';
+        // Since we don't know how long prefix is, loop to generate tablename that gives exactly maxlengh-1 length.
+        // Skip this test if prefix is too long.
+        while (strlen($table) && strlen($gen->prefix.preg_replace('/_/','',$table).'_id_'.$suffix) >= $gen->names_max_length) {
+            $table = rtrim(substr($table, 0, strlen($table) - 1), '_');
+        }
+        if (strlen($table)) {
+            $this->assertEquals($gen->names_max_length - 1,
+                        strlen($gen->getNameForObject($table, $fields, $suffix)));
+            for ($i=0; $i<12; $i++) {
+                $this->assertEquals($gen->names_max_length,
+                        strlen($gen->getNameForObject($table, $fields, $suffix)),
+                        'Generated object name is too long. $i = '.$i);
+            }
+        }
+    }
+
     // Following methods are not supported == Do not test.
     /*
         public function testRenameIndex() {
index 2ad78aa..ec4e983 100644 (file)
@@ -475,7 +475,7 @@ class mssql_native_moodle_database extends moodle_database {
             }
 
             // Scale
-            $info->scale = $rawcolumn->scale ? $rawcolumn->scale : false;
+            $info->scale = $rawcolumn->scale;
 
             // Prepare not_null info
             $info->not_null = $rawcolumn->is_nullable == 'NO'  ? true : false;
index 95985f9..502a266 100644 (file)
@@ -565,7 +565,7 @@ class oci_native_moodle_database extends moodle_database {
                         $info->meta_type     = 'I';
                         $info->unique        = null;
                     }
-                    $info->scale = null;
+                    $info->scale = 0;
 
                 } else {
                     //float
index e8d8281..6b9bce2 100644 (file)
@@ -539,7 +539,7 @@ class sqlsrv_native_moodle_database extends moodle_database {
             }
 
             // Scale
-            $info->scale = $rawcolumn->scale ? $rawcolumn->scale : false;
+            $info->scale = $rawcolumn->scale;
 
             // Prepare not_null info
             $info->not_null = $rawcolumn->is_nullable == 'NO' ? true : false;
index e0858d0..146f615 100644 (file)
@@ -3781,8 +3781,6 @@ class core_dml_testcase extends database_driver_testcase {
         $this->assertNull($DB->get_field_sql($sql, array('paramvalue' => null)));
 
         // Check there are not problems with whitespace strings.
-        $sql = "SELECT COALESCE(null, '', null) AS test" . $DB->sql_null_from_clause();
-        $this->assertSame('', $DB->get_field_sql($sql, array()));
         $sql = "SELECT COALESCE(null, :paramvalue, null) AS test" . $DB->sql_null_from_clause();
         $this->assertSame('', $DB->get_field_sql($sql, array('paramvalue' => '')));
     }
index 7147da3..bc7bd99 100644 (file)
@@ -135,5 +135,25 @@ fontselect,fontsizeselect,wrap,code,search,replace,|,cleanup,removeformat,pastet
         upgrade_plugin_savepoint(true, 2013102900, 'editor', 'tinymce');
     }
 
+    if ($oldversion < 2013110600) {
+        // Reset redesigned editor toolbar setting.
+        $currentorder = get_config('editor_tinymce', 'customtoolbar');
+        $olddefaultorder = "wrap,formatselect,wrap,bold,italic,wrap,bullist,numlist,wrap,link,unlink,wrap,image
+
+undo,redo,wrap,underline,strikethrough,sub,sup,wrap,justifyleft,justifycenter,justifyright,wrap,outdent,indent,wrap,forecolor,backcolor,wrap,ltr,rtl,wrap,nonbreaking,charmap,table
+
+fontselect,fontsizeselect,wrap,code,search,replace,wrap,cleanup,removeformat,pastetext,pasteword,wrap,fullscreen";
+        $neworder = "wrap,formatselect,wrap,bold,italic,wrap,bullist,numlist,wrap,link,unlink,wrap,image
+
+undo,redo,wrap,underline,strikethrough,sub,sup,wrap,justifyleft,justifycenter,justifyright,wrap,outdent,indent,wrap,forecolor,backcolor,wrap,ltr,rtl
+
+fontselect,fontsizeselect,wrap,code,search,replace,wrap,nonbreaking,charmap,table,wrap,cleanup,removeformat,pastetext,pasteword,wrap,fullscreen";
+        if ($currentorder == $olddefaultorder) {
+            set_config('customtoolbar', $neworder, 'editor_tinymce');
+        }
+
+        upgrade_plugin_savepoint(true, 2013110600, 'editor', 'tinymce');
+    }
+
     return true;
 }
index 17f7516..4bd5162 100644 (file)
@@ -16,3 +16,4 @@ Upgrade procedure:
 6/ add in "DOM.setStyle(ifr, 'width',DOM.getSize(ifrcon).w); // Resize iframe" (without quotes)
    after "DOM.setStyle(ifr, 'height',DOM.getSize(ifr).h + dy); // Resize iframe"
 7/ reimplement patch in MDL-42481
+8/ reimplement patch in MDL-42684
index 22e42b7..90d5681 100644 (file)
                // Resizes the iframe by a relative height value\r
                _resizeIframe : function(ed, tb_id, dy) {\r
                    var ifr = ed.getContentAreaContainer().firstChild;\r
-                   var ifrcon = ed.getContentAreaContainer();\r
+                   var ultcon = ed.getContainer().parentNode; //Ultimate parent container\r
+            var parcon = ed.getContainer(); //Parent container\r
+            var tablecon = ed.getContainer().childNodes[1]; // Table container\r
             var textarea = DOM.get(ed.id);\r
             var rows = textarea ? textarea.getAttribute('rows') : 3;\r
 \r
+            // This set of changes addresses MDL-42481.\r
+            // Moodle collapses form sections by setting display to none on the fcontainer element.\r
+            // In order to calculate the offsetWidth, the iframe must be visible within the DOM, otherwise it's offsetWidth is\r
+            // calculate as 0px.\r
+            // We attempt to find any collapsed element, uncollapse them, then calculate the width and height, and finally\r
+            // collapse them again.\r
+            var collapsedContainer = DOM.getParent(ifr, 'fieldset.collapsed');\r
+            if (collapsedContainer) {\r
+                DOM.removeClass(collapsedContainer, 'collapsed');\r
+            }\r
             // For very small text areas - allow the editable region to be smaller than the size of the toolbars.\r
             if (rows >= 3) {\r
-                // This set of changes addresses MDL-42481.\r
-                // Moodle collapses form sections by setting display to none on the fcontainer element.\r
-                // In order to calculate the offsetWidth, the iframe must be visible within the DOM, otherwise it's offsetWidth is\r
-                // calculate as 0px.\r
-                // We attempt to find any collapsed element, uncollapse them, then calculate the width and height, and finally\r
-                // collapse them again.\r
-                var collapsedContainer = DOM.getParent(ifr, 'fieldset.collapsed');\r
-                if (collapsedContainer) {\r
-                    DOM.removeClass(collapsedContainer, 'collapsed');\r
-                }\r
-\r
                 DOM.setStyle(ifr, 'height',DOM.getSize(ifr).h + dy); // Resize iframe\r
-                DOM.setStyle(ifr, 'width',DOM.getSize(ifrcon).w); // Resize iframe\r
                 ed.theme.deltaHeight += dy; // For resize cookie\r
+            }\r
 \r
-                if (collapsedContainer) {\r
-                    // We have a collapsedContainer, so collapse it again.\r
-                    DOM.addClass(collapsedContainer, 'collapsed');\r
-                }\r
+            // Set all the containers to the same width\r
+            DOM.setStyle(textarea, 'width',DOM.getSize(ultcon).w);\r
+            DOM.setStyle(parcon, 'width',DOM.getSize(ultcon).w);\r
+            DOM.setStyle(tablecon, 'width',DOM.getSize(ultcon).w);\r
+            DOM.setStyle(ifr, 'width',DOM.getSize(ultcon).w);\r
+\r
+\r
+            if (collapsedContainer) {\r
+                // We have a collapsedContainer, so collapse it again.\r
+                DOM.addClass(collapsedContainer, 'collapsed');\r
             }\r
 \r
                },\r
        // Register plugin\r
        tinymce.PluginManager.add('pdw', tinymce.plugins.pdw);\r
 })();\r
+\r
index b8ca304..b086d40 100644 (file)
@@ -33,9 +33,9 @@ if ($ADMIN->fulltree) {
     $settings->add(new admin_setting_heading('tinymcegeneralheader', new lang_string('settings'), ''));
     $default = "wrap,formatselect,wrap,bold,italic,wrap,bullist,numlist,wrap,link,unlink,wrap,image
 
-undo,redo,wrap,underline,strikethrough,sub,sup,wrap,justifyleft,justifycenter,justifyright,wrap,outdent,indent,wrap,forecolor,backcolor,wrap,ltr,rtl,wrap,nonbreaking,charmap,table
+undo,redo,wrap,underline,strikethrough,sub,sup,wrap,justifyleft,justifycenter,justifyright,wrap,outdent,indent,wrap,forecolor,backcolor,wrap,ltr,rtl
 
-fontselect,fontsizeselect,wrap,code,search,replace,wrap,cleanup,removeformat,pastetext,pasteword,wrap,fullscreen";
+fontselect,fontsizeselect,wrap,code,search,replace,wrap,nonbreaking,charmap,table,wrap,cleanup,removeformat,pastetext,pasteword,wrap,fullscreen";
     $settings->add(new admin_setting_configtextarea('editor_tinymce/customtoolbar',
         get_string('customtoolbar', 'editor_tinymce'), get_string('customtoolbar_desc', 'editor_tinymce', 'http://www.tinymce.com/wiki.php/TinyMCE3x:Buttons/controls'), $default, PARAM_RAW, 100, 8));
     $settings->add(new admin_setting_configtextarea('editor_tinymce/fontselectlist',
index 86703ef..44f3a92 100644 (file)
@@ -6,7 +6,7 @@
     .moodleSkin .mceLayout .mceToolbar .mceWrap {
         clear: left;
         width: 100%;
-        height: 8px;
+        height: 4px;
     }
     .moodleSkin .mceLayout .mceToolbar .mceNoWrap {
         clear: none;
index 63a7ac1..7be7506 100644 (file)
Binary files a/lib/editor/tinymce/tiny_mce/3.5.8/themes/advanced/skins/moodle/img/button_bg.png and b/lib/editor/tinymce/tiny_mce/3.5.8/themes/advanced/skins/moodle/img/button_bg.png differ
index c528a6c..f7e0741 100644 (file)
@@ -6,12 +6,13 @@
 /* New Theme */
 .moodleSkin table {background:transparent; border-spacing: 0; }
 .moodleSkin iframe {display:block;}
-.moodleSkin .mceToolbar {min-height:30px; margin: 9px;}
+.moodleSkin .mceToolbar {min-height:30px; margin: 4px;}
 .moodleSkin .mceToolbar td + td { background: #fff; border: 1px solid #CCCCCC; border-right: none; border-bottom: 1px solid #b3b3b3; }
 .moodleSkin .mceToolbar td + td:empty { display: none;}
-.moodleSkin .mceToolbar a.mceButton { display:block; height: 30px; width: 40px; margin: 0; padding: 0; }
-.moodleSkin .mceToolbar a span.mceIcon { height: 30px; width: 40px; margin: 0; }
-.moodleSkin .mceToolbar a span.mceIcon img { padding: 7px 13px; }
+.dir-rtl .moodleSkin .mceToolbar select,
+.moodleSkin .mceToolbar a.mceButton { display:block; height: 30px; width: 30px; margin: 0; padding: 0; }
+.moodleSkin .mceToolbar a span.mceIcon { height: 30px; width: 30px; margin: 0; }
+.moodleSkin .mceToolbar a span.mceIcon img { padding: 7px; }
 .moodleSkin .mceToolbar .mceGroupStart + td,
 .moodleSkin .mceToolbar .mceToolbarStart + td,
 .moodleSkin .mceToolbar .mceGroupStart + td a,
 .moodleSkin .mceToolbar .mceToolbarStart + td.mceGroupEnd,
 .moodleSkin .mceToolbar .mceToolbarStart + td.mceGroupEnd a { border-radius: 4px; }
 .moodleSkin .mceToolbar .mceLast { border: none; }
-.moodleSkin .mceToolbar .mceGroupStart { border: none; width: 9px; background: transparent;}
-.moodleSkin .mceToolbar .mceGroupEnd { border-right: 1px solid #CCCCCC; border-radius: 0 4px 4px 0; border-left: 1px solid #CCCCCC; margin-right: 9px; }
+.moodleSkin .mceToolbar .mceGroupStart { border: none; width: 4px; background: transparent;}
+.moodleSkin .mceToolbar .mceGroupEnd { border-right: 1px solid #CCCCCC; border-radius: 0 4px 4px 0; border-left: 1px solid #CCCCCC; margin-right: 4px; }
 .moodleSkin .mceToolbar .mceGroupEnd a { border-radius: 0 4px 4px 0; }
 .moodleSkin .mceToolbar td div .mceGroupEnd { border: none;}
+.moodleSkin .mceToolbar .mceListBox { height: 30px; }
 .moodleSkin .mceToolbar .mceListBox td { border: none; border-radius: 0; }
 .moodleSkin .mceToolbar .mceListBox td a { border: none; border-radius: 0; }
 .moodleSkin .mceToolbar .mceToolbarEndPlaceholder { display: none; }
 .moodleSkin .mceToolbar .mceToolbarEnd { background: transparent; border: 0; }
-.moodleSkin.mceListBoxMenu { margin-left: -4px; margin-top: 2px; }
+.moodleSkin.mceListBoxMenu { margin-left: -4px; margin-top: 0px; }
+
+
+.moodleSkin .mceToolbar .mceGroupStart + td .mceLast a { border-radius: 0;}
+
 
 .dir-rtl .moodleSkin table {background:transparent; border-spacing: 0; }
 .dir-rtl .moodleSkin iframe {display:block;}
-.dir-rtl .moodleSkin .mceToolbar {min-height:30px; margin: 9px;}
+.dir-rtl .moodleSkin .mceToolbar {min-height:30px; margin: 4px;}
 .dir-rtl .moodleSkin .mceToolbar td + td { background: #fff; border: 1px solid #CCCCCC; border-left: none; border-bottom: 1px solid #b3b3b3; }
 .dir-rtl .moodleSkin .mceToolbar td + td:empty { display: none;}
-.dir-rtl .moodleSkin .mceToolbar a.mceButton { display:block; height: 30px; width: 40px; margin: 0; padding: 0; }
-.dir-rtl .moodleSkin .mceToolbar a span.mceIcon { height: 30px; width: 40px; margin: 0; }
-.dir-rtl .moodleSkin .mceToolbar a span.mceIcon img { padding: 7px 13px; }
+.dir-rtl .moodleSkin .mceToolbar select,
+.dir-rtl .moodleSkin .mceToolbar a.mceButton { display:block; height: 30px; width: 30px; margin: 0; padding: 0; }
+.dir-rtl .moodleSkin .mceToolbar a span.mceIcon { height: 30px; width: 30px; margin: 0; }
+.dir-rtl .moodleSkin .mceToolbar a span.mceIcon img { padding: 7px 7px; }
 .dir-rtl .moodleSkin .mceToolbar .mceGroupStart + td,
 .dir-rtl .moodleSkin .mceToolbar .mceToolbarStart + td,
 .dir-rtl .moodleSkin .mceToolbar .mceGroupStart + td a,
@@ -48,8 +55,8 @@
 .dir-rtl .moodleSkin .mceToolbar .mceToolbarStart + td.mceGroupEnd,
 .dir-rtl .moodleSkin .mceToolbar .mceToolbarStart + td.mceGroupEnd a { border-radius: 4px; }
 .dir-rtl .moodleSkin .mceToolbar .mceLast { border: none; }
-.dir-rtl .moodleSkin .mceToolbar .mceGroupStart { border: none; width: 9px; background: transparent;}
-.dir-rtl .moodleSkin .mceToolbar .mceGroupEnd { border-left: 1px solid #CCCCCC; border-radius: 4px 0 0 4px; border-right: 1px solid #CCCCCC; margin-left: 9px; }
+.dir-rtl .moodleSkin .mceToolbar .mceGroupStart { border: none; width: 4px; background: transparent;}
+.dir-rtl .moodleSkin .mceToolbar .mceGroupEnd { border-left: 1px solid #CCCCCC; border-radius: 4px 0 0 4px; border-right: 1px solid #CCCCCC; margin-left: 4px; }
 .dir-rtl .moodleSkin .mceToolbar .mceGroupEnd a { border-radius: 4px 0 0 4px; }
 .dir-rtl .moodleSkin .mceToolbar td div .mceGroupEnd { border: none; }
 .dir-rtl .moodleSkin .mceToolbar .mceListBox td { border: none; border-radius: 0; }
 .moodleSkin .mceButtonDisabled .mceButtonLabel {color:#888}
 
 /* Separator */
-.moodleSkin .mceSeparator {display:block; width:0px; height:22px; margin: 0 2px 0 1px; border-left: 1px solid #BBB }
+.moodleSkin .mceSeparator {display:none;}
 
 /* ListBox */
 .moodleSkin .mceListBox {padding: 0 3px}
 .moodleSkin .mceListBox, .moodleSkin .mceListBox a {display:block}
-.moodleSkin .mceListBox .mceText {padding-left:4px; text-align:left; width:70px; background:#eaf2fb; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; height:20px; line-height:20px; overflow:hidden}
-.moodleSkin .mceListBox .mceOpen {width:14px; height:22px; background:url(img/button_bg.png) -66px 0}
+.moodleSkin .mceListBox .mceText {padding-left:4px; text-align:left; width:70px; background:#eaf2fb; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; height:30px; line-height:30px; overflow:hidden}
+.moodleSkin .mceListBox .mceOpen {width:14px; height:30px; background:url(img/button_bg.png) -66px 0}
 .moodleSkin table.mceListBoxEnabled:hover .mceText, .moodleSkin .mceListBoxHover .mceText, .moodleSkin .mceListBoxSelected .mceText {background:#FFF}
 .moodleSkin table.mceListBoxEnabled:hover .mceOpen, .moodleSkin .mceListBoxHover .mceOpen, .moodleSkin .mceListBoxSelected .mceOpen {background-position:-66px -22px}
 .moodleSkin .mceListBoxDisabled .mceText {color:gray}
 /* SplitButton */
 .moodleSkin .mceSplitButton a {display:block; direction:ltr; height: 22px;}
 .moodleSkin .mceSplitButton, .moodleSkin .mceSplitButton span {display:block; height:30px; direction:ltr; }
-.moodleSkin .mceSplitButton { width: 50px; }
-.moodleSkin .mceSplitButton:hover {background-image: radial-gradient(ellipse at center, #ffffff 60%,#dfdfdf 100%); cursor: pointer;}
-.moodleSkin .mceSplitButton:active {background-image: radial-gradient(ellipse at center, #ffffff 40%,#dfdfdf 100%);}
-.moodleSkin .mceSplitButton .mceFirst { padding: 4px 0 0 9px; }
-.moodleSkin .mceSplitButton .mceLast { background: transparent;}
+.moodleSkin .mceSplitButton {width:48px;}
+.moodleSkin .mceSplitButton .mceFirst:hover {background-image:radial-gradient(ellipse at center, #ffffff 60%,#dfdfdf 100%); cursor:pointer;}
+.moodleSkin .mceSplitButton .mceFirst:active {background-image:radial-gradient(ellipse at center, #ffffff 40%,#dfdfdf 100%);}
+.moodleSkin .mceSplitButton .mceFirst {width:30px; padding:4px 0 0 6px;}
+.moodleSkin .mceSplitButton .mceLast {background: transparent; width: 15px;}
 .moodleSkin .mceSplitButton a.mceAction {width:20px}
-.moodleSkin .mceSplitButton a.mceOpen {width:10px; background:url(img/button_bg.png) 44px 0}
+.moodleSkin .mceSplitButton a.mceOpen {width:12px; background:url(img/button_bg.png) -66px 0px; border-left: 1px dashed #DDD; border-radius: 0; height: 30px;}
 .moodleSkin .mceSplitButton span.mceOpen {display:none}
-.moodleSkin table.mceSplitButtonEnabled:hover a.mceOpen, .moodleSkin .mceSplitButtonHover a.mceOpen, .moodleSkin .mceSplitButtonSelected a.mceOpen {background-position:-44px -44px}
+.moodleSkin table.mceSplitButtonEnabled:hover a.mceOpen, .moodleSkin .mceSplitButtonHover a.mceOpen, .moodleSkin .mceSplitButtonSelected a.mceOpen {background-position:-66px -22px;}
 .moodleSkin .mceSplitButtonDisabled .mceAction {opacity:0.3;}
 .moodleSkin .mceSplitButtonActive {background-position:0 -44px}
 
 /* ColorSplitButton */
+.moodleSkin div.mceColorSplitMenu { margin-left: -1px; }
 .moodleSkin div.mceColorSplitMenu table {background:#FFF; border: 1px solid #BBB;}
 .moodleSkin .mceColorSplitMenu td {padding:2px}
 .moodleSkin .mceColorSplitMenu a {display:block; width:18px; height:18px; overflow:hidden; border:1px solid #BBB}
 .moodleSkin .mceColorSplitMenu a.mceMoreColors:hover {border:1px solid #0A246A; background-color:#B6BDD2}
 .moodleSkin a.mceMoreColors:hover {border:1px solid #0A246A}
 /* Font selector preview stuff */
-.moodleSkin .mce_forecolor span.mceAction, .moodleSkin .mce_backcolor span.mceAction {height:21px;overflow:hidden; position: relative; z-index: 1 }
+.moodleSkin .mce_forecolor span.mceAction, .moodleSkin .mce_backcolor span.mceAction {height:21px;overflow:hidden; position: relative; z-index: 1; }
 .moodleSkin .mceColorPreview {width:16px; height:16px; overflow:hidden; margin-left: 2px; position: relative; top: -18px;}
 
 /* Menu */
index 0ea7cbd..72fc96d 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2013110500;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2013110600;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2013110500;        // Requires this Moodle version
 $plugin->component = 'editor_tinymce';  // Full name of the plugin (used for diagnostics)
 $plugin->release   = '3.5.8';           // This is NOT a directory name, see lib.php if you need to know where is the editor code!
index 645073c..fbafa1a 100644 (file)
@@ -33,7 +33,12 @@ defined('MOODLE_INTERNAL') || die();
  * @return texteditor object
  */
 function editors_get_preferred_editor($format = NULL) {
-    global $USER;
+    global $USER, $CFG;
+
+    if (!empty($CFG->adminsetuppending)) {
+        // Must not use other editors before install completed!
+        return get_texteditor('textarea');
+    }
 
     $enabled = editors_get_enabled();
 
index e149e35..c4c08e2 100644 (file)
@@ -137,14 +137,16 @@ class mbz_packer extends file_packer {
     }
 
     /**
-     * Selects appropriate packer for new archive depending on system option.
+     * Selects appropriate packer for new archive depending on system option
+     * and whether required extension is available.
      *
      * @return file_packer Suitable packer
      */
     protected function get_packer_for_archive_operation() {
         global $CFG;
+        require_once($CFG->dirroot . '/lib/filestorage/tgz_packer.php');
 
-        if ($CFG->enabletgzbackups) {
+        if ($CFG->enabletgzbackups && tgz_packer::has_required_extension()) {
             return get_file_packer('application/x-gzip');
         } else {
             return get_file_packer('application/zip');
@@ -156,13 +158,18 @@ class mbz_packer extends file_packer {
      *
      * @param string|stored_file $archivefile full pathname of zip file or stored_file instance
      * @return file_packer Suitable packer
+     * @throws moodle_exception If the file cannot be restored because of missing zlib
      */
     protected function get_packer_for_read_operation($archivefile) {
         global $CFG;
         require_once($CFG->dirroot . '/lib/filestorage/tgz_packer.php');
 
         if (tgz_packer::is_tgz_file($archivefile)) {
-            return get_file_packer('application/x-gzip');
+            if (tgz_packer::has_required_extension()) {
+                return get_file_packer('application/x-gzip');
+            } else {
+                throw new moodle_exception('errortgznozlib', 'backup');
+            }
         } else {
             return get_file_packer('application/zip');
         }
index e0873b2..abd8c1d 100644 (file)
@@ -40,7 +40,8 @@ class core_files_file_storage_testcase extends advanced_testcase {
 
         $this->resetAfterTest(true);
 
-        $this->assertEquals(0, $DB->count_records('files', array()));
+        // Number of files installed in the database on a fresh Moodle site.
+        $installedfiles = $DB->count_records('files', array());
 
         $content = 'abcd';
         $syscontext = context_system::instance();
@@ -68,7 +69,7 @@ class core_files_file_storage_testcase extends advanced_testcase {
         $this->assertFileExists($location);
 
         // Verify the dir placeholder files are created.
-        $this->assertEquals(3, $DB->count_records('files', array()));
+        $this->assertEquals($installedfiles + 3, $DB->count_records('files', array()));
         $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].'/.'))));
         $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].$filerecord['filepath'].'.'))));
 
@@ -83,7 +84,7 @@ class core_files_file_storage_testcase extends advanced_testcase {
         $this->assertSame($file->get_contenthash(), $file2->get_contenthash());
         $this->assertFileExists($location);
 
-        $this->assertEquals(4, $DB->count_records('files', array()));
+        $this->assertEquals($installedfiles + 4, $DB->count_records('files', array()));
 
         // Test that borked content file is recreated.
 
@@ -98,7 +99,7 @@ class core_files_file_storage_testcase extends advanced_testcase {
         $this->assertSame($content, file_get_contents($location));
         $this->assertDebuggingCalled();
 
-        $this->assertEquals(5, $DB->count_records('files', array()));
+        $this->assertEquals($installedfiles + 5, $DB->count_records('files', array()));
     }
 
     /**
@@ -109,8 +110,8 @@ class core_files_file_storage_testcase extends advanced_testcase {
 
         $this->resetAfterTest(true);
 
-        $filecount = $DB->count_records('files', array());
-        $this->assertEquals(0, $filecount);
+        // Number of files installed in the database on a fresh Moodle site.
+        $installedfiles = $DB->count_records('files', array());
 
         $filepath = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
         $syscontext = context_system::instance();
@@ -137,7 +138,7 @@ class core_files_file_storage_testcase extends advanced_testcase {
         $this->assertFileExists($location);
 
         // Verify the dir placeholder files are created.
-        $this->assertEquals(3, $DB->count_records('files', array()));
+        $this->assertEquals($installedfiles + 3, $DB->count_records('files', array()));
         $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].'/.'))));
         $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].$filerecord['filepath'].'.'))));
 
@@ -152,7 +153,7 @@ class core_files_file_storage_testcase extends advanced_testcase {
         $this->assertSame($file->get_contenthash(), $file2->get_contenthash());
         $this->assertFileExists($location);
 
-        $this->assertEquals(4, $DB->count_records('files', array()));
+        $this->assertEquals($installedfiles + 4, $DB->count_records('files', array()));
 
         // Test that borked content file is recreated.
 
@@ -167,7 +168,7 @@ class core_files_file_storage_testcase extends advanced_testcase {
         $this->assertSame(file_get_contents($filepath), file_get_contents($location));
         $this->assertDebuggingCalled();
 
-        $this->assertEquals(5, $DB->count_records('files', array()));
+        $this->assertEquals($installedfiles + 5, $DB->count_records('files', array()));
 
         // Test invalid file creation.
 
index cdb7bef..b232e4f 100644 (file)
@@ -35,6 +35,11 @@ class core_files_mbz_packer_testcase extends advanced_testcase {
 
         // Get backup packer.
         $packer = get_file_packer('application/vnd.moodle.backup');
+        require_once($CFG->dirroot . '/lib/filestorage/tgz_packer.php');
+        if (!tgz_packer::has_required_extension()) {
+            $this->markTestSkipped('zlib not available');
+            return;
+        }
 
         // Set up basic archive contents.
         $files = array('1.txt' => array('frog'));
index d61d264..d495ec8 100644 (file)
@@ -114,6 +114,9 @@ class core_files_tgz_packer_testcase extends advanced_testcase implements file_p
     public function test_to_normal_files() {
         global $CFG;
         $packer = get_file_packer('application/x-gzip');
+        if (self::skip_because_missing_zlib()) {
+            return;
+        }
 
         // Archive files.
         $files = $this->prepare_file_list();
@@ -154,6 +157,9 @@ class core_files_tgz_packer_testcase extends advanced_testcase implements file_p
      */
     public function test_to_stored_files() {
         global $CFG;
+        if (self::skip_because_missing_zlib()) {
+            return;
+        }
         $packer = get_file_packer('application/x-gzip');
 
         // Archive files.
@@ -220,6 +226,9 @@ class core_files_tgz_packer_testcase extends advanced_testcase implements file_p
      */
     public function test_only_specified_files() {
         global $CFG;
+        if (self::skip_because_missing_zlib()) {
+            return;
+        }
         $packer = get_file_packer('application/x-gzip');
 
         // Archive files.
@@ -252,6 +261,9 @@ class core_files_tgz_packer_testcase extends advanced_testcase implements file_p
      */
     public function test_file_progress() {
         global $CFG;
+        if (self::skip_because_missing_zlib()) {
+            return;
+        }
 
         // Set up.
         $filelist = $this->prepare_file_list();
@@ -309,6 +321,9 @@ class core_files_tgz_packer_testcase extends advanced_testcase implements file_p
      */
     public function test_list_files() {
         global $CFG;
+        if (self::skip_because_missing_zlib()) {
+            return;
+        }
 
         // Set up.
         $filelist = $this->prepare_file_list();
@@ -376,6 +391,9 @@ class core_files_tgz_packer_testcase extends advanced_testcase implements file_p
 
     public function test_is_tgz_file() {
         global $CFG;
+        if (self::skip_because_missing_zlib()) {
+            return;
+        }
 
         // Set up.
         $filelist = $this->prepare_file_list();
@@ -428,4 +446,19 @@ class core_files_tgz_packer_testcase extends advanced_testcase implements file_p
     public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) {
         $this->progress[] = array($progress, $max);
     }
+
+    /**
+     * Checks if zlib is available. If not, marks current test as skipped.
+     *
+     * @return bool True if text should be skipped
+     */
+    protected function skip_because_missing_zlib() {
+        global $CFG;
+        require_once($CFG->dirroot . '/lib/filestorage/tgz_packer.php');
+        if (!tgz_packer::has_required_extension()) {
+            $this->markTestSkipped('zlib not available');
+            return true;
+        }
+        return false;
+    }
 }
index bfa459d..c47662a 100644 (file)
@@ -695,6 +695,16 @@ class tgz_packer extends file_packer {
         fclose($fp);
         return ($firstbytes[0] == "\x1f" && $firstbytes[1] == "\x8b");
     }
+
+    /**
+     * The zlib extension is required for this packer to work. This is a single
+     * location for the code to check whether the extension is available.
+     *
+     * @return bool True if the extension is available OK
+     */
+    public static function has_required_extension() {
+        return extension_loaded('zlib');
+    }
 }
 
 
index 79b83eb..2e64523 100644 (file)
@@ -1113,7 +1113,9 @@ class phpFlickr {
         $args['content_type']   = isset($meta['content_type']) ? $meta['content_type'] : 1; // photo by default
         $args['hidden']         = isset($meta['hidden']) ? $meta['hidden'] : 2;             // hide from public searches by default
 
-        $args['async'] = 1;
+        // Do not enable the asynchronous more because then the query does not return a photo ID,
+        // and we need a photo ID to add the photo to a set later on.
+        // $args['async'] = 1;
         $args['api_key'] = $this->api_key;
 
         if (!empty($this->email)) {
@@ -1144,8 +1146,17 @@ class phpFlickr {
         $args['photo'] = $photo; // $this->curl will process it correctly
 
         if ($response = $this->curl->post($this->Upload, $args)) {
+            $xml = simplexml_load_string($response);
+            if ($xml['stat'] == 'fail') {
+                $this->parsed_response = array('stat' => (string) $xml['stat'], 'code' => (int) $xml->err['code'],
+                    'message' => (string) $xml->err['msg']);
+            } elseif ($xml['stat'] == 'ok') {
+                $this->parsed_response = array('stat' => (string) $xml['stat'], 'photoid' => (int) $xml->photoid);
+            }
             return true;
         } else {
+            $this->parsed_response = array('stat' => 'fail', 'code' => $this->curl->get_errno(),
+                'message' => $this->curl->error);
             return false;
         }
     }
index 54bb661..31ef0e1 100644 (file)
@@ -247,15 +247,17 @@ function ldap_find_userdn($ldapconnection, $username, $contexts, $objectclass, $
         }
 
         if ($search_sub) {
-            if (!$ldap_result = @ldap_search($ldapconnection, $context,
-                                           '(&'.$objectclass.'('.$search_attrib.'='.ldap_filter_addslashes($username).'))',
-                                           array($search_attrib))) {
-                break; // Not found in this context.
-            }
+            $ldap_result = @ldap_search($ldapconnection, $context,
+                                        '(&'.$objectclass.'('.$search_attrib.'='.ldap_filter_addslashes($username).'))',
+                                        array($search_attrib));
         } else {
-            $ldap_result = ldap_list($ldapconnection, $context,
-                                     '(&'.$objectclass.'('.$search_attrib.'='.ldap_filter_addslashes($username).'))',
-                                     array($search_attrib));
+            $ldap_result = @ldap_list($ldapconnection, $context,
+                                      '(&'.$objectclass.'('.$search_attrib.'='.ldap_filter_addslashes($username).'))',
+                                      array($search_attrib));
+        }
+
+        if (!$ldap_result) {
+            continue; // Not found in this context.
         }
 
         $entry = ldap_first_entry($ldapconnection, $ldap_result);
index c21b5fe..f112956 100644 (file)
@@ -3567,6 +3567,10 @@ function ismoving($courseid) {
 function fullname($user, $override=false) {
     global $CFG, $SESSION;
 
+    if (!isset($user->firstname) and !isset($user->lastname)) {
+        return '';
+    }
+
     // Get all of the name fields.
     $allnames = get_all_user_name_fields();
     if ($CFG->debugdeveloper) {
@@ -3580,10 +3584,6 @@ function fullname($user, $override=false) {
         }
     }
 
-    if (!isset($user->firstname) and !isset($user->lastname)) {
-        return '';
-    }
-
     if (!$override) {
         if (!empty($CFG->forcefirstname)) {
             $user->firstname = $CFG->forcefirstname;
@@ -3657,20 +3657,37 @@ function fullname($user, $override=false) {
  * A centralised location for the all name fields. Returns an array / sql string snippet.
  *
  * @param bool $returnsql True for an sql select field snippet.
- * @param string $alias table alias to use in front of each field.
+ * @param string $tableprefix table query prefix to use in front of each field.
+ * @param string $prefix prefix added to the name fields e.g. authorfirstname.
+ * @param string $fieldprefix sql field prefix e.g. id AS userid.
  * @return array|string All name fields.
  */
-function get_all_user_name_fields($returnsql = false, $alias = null) {
-    $alternatenames = array('firstnamephonetic',
-                            'lastnamephonetic',
-                            'middlename',
-                            'alternatename',
-                            'firstname',
-                            'lastname');
+function get_all_user_name_fields($returnsql = false, $tableprefix = null, $prefix = null, $fieldprefix = null) {
+    $alternatenames = array('firstnamephonetic' => 'firstnamephonetic',
+                            'lastnamephonetic' => 'lastnamephonetic',
+                            'middlename' => 'middlename',
+                            'alternatename' => 'alternatename',
+                            'firstname' => 'firstname',
+                            'lastname' => 'lastname');
+
+    // Let's add a prefix to the array of user name fields if provided.
+    if ($prefix) {
+        foreach ($alternatenames as $key => $altname) {
+            $alternatenames[$key] = $prefix . $altname;
+        }
+    }
+
+    // Create an sql field snippet if requested.
     if ($returnsql) {
-        if ($alias) {
-            foreach ($alternatenames as $key => $altname) {
-                $alternatenames[$key] = "$alias.$altname";
+        if ($tableprefix) {
+            if ($fieldprefix) {
+                foreach ($alternatenames as $key => $altname) {
+                    $alternatenames[$key] = $tableprefix . '.' . $altname . ' AS ' . $fieldprefix . $altname;
+                }
+            } else {
+                foreach ($alternatenames as $key => $altname) {
+                    $alternatenames[$key] = $tableprefix . '.' . $altname;
+                }
             }
         }
         $alternatenames = implode(',', $alternatenames);
@@ -3678,6 +3695,41 @@ function get_all_user_name_fields($returnsql = false, $alias = null) {
     return $alternatenames;
 }
 
+/**
+ * Reduces lines of duplicated code for getting user name fields.
+ *
+ * @param object $addtoobject Object to add user name fields to.
+ * @param object $secondobject Object that contains user name field information.
+ * @param string $prefix prefix to be added to all fields (including $additionalfields) e.g. authorfirstname.
+ * @param array $additionalfields Additional fields to be matched with data in the second object.
+ * The key can be set to the user table field name.
+ * @return object User name fields.
+ */
+function username_load_fields_from_object($addtoobject, $secondobject, $prefix = null, $additionalfields = null) {
+    $fields = get_all_user_name_fields(false, null, $prefix);
+    if ($additionalfields) {
+        // Additional fields can specify their own 'alias' such as 'id' => 'userid'. This checks to see if
+        // the key is a number and then sets the key to the array value.
+        foreach ($additionalfields as $key => $value) {
+            if (is_numeric($key)) {
+                $additionalfields[$value] = $prefix . $value;
+                unset($additionalfields[$key]);
+            } else {
+                $additionalfields[$key] = $prefix . $value;
+            }
+        }
+        $fields = array_merge($fields, $additionalfields);
+    }
+    foreach ($fields as $key => $field) {
+        // Important that we have all of the user name fields present in the object that we are sending back.
+        $addtoobject->$key = '';
+        if (isset($secondobject->$field)) {
+            $addtoobject->$key = $secondobject->$field;
+        }
+    }
+    return $addtoobject;
+}
+
 /**
  * Returns an array of values in order of occurance in a provided string.
  * The key in the result is the character postion in the string.
index 79244ab..cad40cd 100644 (file)
@@ -3139,12 +3139,22 @@ class navbar extends navigation_node {
      * @return array
      */
     private function get_course_categories() {
+        global $CFG;
+
+        require_once($CFG->dirroot.'/course/lib.php');
         $categories = array();
+        $cap = 'moodle/category:viewhiddencategories';
         foreach ($this->page->categories as $category) {
+            if (!$category->visible && !has_capability($cap, get_category_or_system_context($category->parent))) {
+                continue;
+            }
             $url = new moodle_url('/course/index.php', array('categoryid' => $category->id));
             $name = format_string($category->name, true, array('context' => context_coursecat::instance($category->id)));
-            $categories[] = navigation_node::create($name, $url, self::TYPE_CATEGORY, null, $category->id);
-            $id = $category->parent;
+            $categorynode = navigation_node::create($name, $url, self::TYPE_CATEGORY, null, $category->id);
+            if (!$category->visible) {
+                $categorynode->hidden = true;
+            }
+            $categories[] = $categorynode;
         }
         if (is_enrolled(context_course::instance($this->page->course->id))) {
             $courses = $this->page->navigation->get('mycourses');
index afc9264..423480c 100644 (file)
@@ -617,6 +617,39 @@ abstract class oauth2_client extends curl {
         return null;
     }
 
+    /**
+     * Get access token.
+     *
+     * This is just a getter to read the private property.
+     *
+     * @return string
+     */
+    public function get_accesstoken() {
+        return $this->accesstoken;
+    }
+
+    /**
+     * Get the client ID.
+     *
+     * This is just a getter to read the private property.
+     *
+     * @return string
+     */
+    public function get_clientid() {
+        return $this->clientid;
+    }
+
+    /**
+     * Get the client secret.
+     *
+     * This is just a getter to read the private property.
+     *
+     * @return string
+     */
+    public function get_clientsecret() {
+        return $this->clientsecret;
+    }
+
     /**
      * Should HTTP GET be used instead of POST?
      * Some APIs do not support POST and want oauth to use
index 39a6e05..0b1d2a4 100644 (file)
@@ -1114,11 +1114,13 @@ class core_renderer extends renderer_base {
      * @return string HTML fragment
      */
     protected function render_action_menu_link(action_menu_link $action) {
+        static $actioncount = 0;
+        $actioncount++;
 
         $comparetoalt = '';
         $text = '';
         if (!$action->icon || $action->primary === false) {
-            $text .= html_writer::start_tag('span', array('class'=>'menu-action-text'));
+            $text .= html_writer::start_tag('span', array('class'=>'menu-action-text', 'id' => 'actionmenuaction-'.$actioncount));
             if ($action->text instanceof renderable) {
                 $text .= $this->render($action->text);
             } else {
@@ -1134,12 +1136,9 @@ class core_renderer extends renderer_base {
             if ($action->primary || !$action->actionmenu->will_be_enhanced()) {
                 $action->attributes['title'] = $action->text;
             }
-            if ((string)$icon->attributes['alt'] === $comparetoalt && $action->actionmenu->will_be_enhanced()) {
-                $icon->attributes['alt'] = ' ';
-            }
             if (!$action->primary && $action->actionmenu->will_be_enhanced()) {
                 if ((string)$icon->attributes['alt'] === $comparetoalt) {
-                    $icon->attributes['alt'] = ' ';
+                    $icon->attributes['alt'] = '';
                 }
                 if (isset($icon->attributes['title']) && (string)$icon->attributes['title'] === $comparetoalt) {
                     unset($icon->attributes['title']);
@@ -1157,6 +1156,9 @@ class core_renderer extends renderer_base {
         $attributes = $action->attributes;
         unset($action->attributes['disabled']);
         $attributes['href'] = $action->url;
+        if ($text !== '') {
+            $attributes['aria-labelledby'] = 'actionmenuaction-'.$actioncount;
+        }
 
         return html_writer::tag('a', $icon.$text, $attributes);
     }
@@ -3153,7 +3155,7 @@ EOD;
             // No name for tabtree root.
         } else if ($tabobject->inactive || $tabobject->activated || ($tabobject->selected && !$tabobject->linkedwhenselected)) {
             // Tab name without a link. The <a> tag is used for styling.
-            $str .= html_writer::tag('a', html_writer::span($tabobject->text), array('class' => 'nolink'));
+            $str .= html_writer::tag('a', html_writer::span($tabobject->text), array('class' => 'nolink moodle-has-zindex'));
         } else {
             // Tab name with a link.
             if (!($tabobject->link instanceof moodle_url)) {
index 4c1e2fc..657c72e 100644 (file)
@@ -218,6 +218,8 @@ class flexible_table {
 
     /**
      * Use text sorting functions for this column (required for text columns with Oracle).
+     * Be warned that you cannot use this with column aliases. You can only do this
+     * with real columns. See MDL-40481 for an example.
      * @param string column name
      */
     function text_sorting($column) {
index 5d1a972..e190a59 100644 (file)
@@ -177,7 +177,7 @@ class testing_repository_generator extends component_generator_base {
         $record = $this->prepare_type_record($record);
         foreach ($typeoptions as $option) {
             if (!isset($record[$option])) {
-                throw new coding_exception("$option must be present in testing::create_repository_type() $record");
+                throw new coding_exception("$option must be present in testing::create_repository_type() for $type");
             }
         }
 
index 50c9dce..654e493 100644 (file)
@@ -602,6 +602,40 @@ class behat_general extends behat_base {
         }
     }
 
+    /**
+     * Checks the provided element and selector type are readonly on the current page.
+     *
+     * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be readonly$/
+     * @throws ExpectationException Thrown by behat_base::find
+     * @param string $element Element we look in
+     * @param string $selectortype The type of element where we are looking in.
+     */
+    public function the_element_should_be_readonly($element, $selectortype) {
+        // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement.
+        $node = $this->get_selected_node($selectortype, $element);
+
+        if (!$node->hasAttribute('readonly')) {
+            throw new ExpectationException('The element "' . $element . '" is not readonly', $this->getSession());
+        }
+    }
+
+    /**
+     * Checks the provided element and selector type are not readonly on the current page.
+     *
+     * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not be readonly$/
+     * @throws ExpectationException Thrown by behat_base::find
+     * @param string $element Element we look in
+     * @param string $selectortype The type of element where we are looking in.
+     */
+    public function the_element_should_not_be_readonly($element, $selectortype) {
+        // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement.
+        $node = $this->get_selected_node($selectortype, $element);
+
+        if ($node->hasAttribute('readonly')) {
+            throw new ExpectationException('The element "' . $element . '" is readonly', $this->getSession());
+        }
+    }
+
     /**
      * Checks the provided element and selector type exists in the current page.
      *
@@ -651,4 +685,52 @@ class behat_general extends behat_base {
         $this->getSession()->visit($this->locate_path('/admin/cron.php'));
     }
 
+    /**
+     * Checks that an element and selector type exists in another element and selector type on the current page.
+     *
+     * This step is for advanced users, use it if you don't find anything else suitable for what you need.
+     *
+     * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exist in the "(?P<element2_string>(?:[^"]|\\")*)" "(?P<selector2_string>[^"]*)"$/
+     * @throws ElementNotFoundException Thrown by behat_base::find
+     * @param string $element The locator of the specified selector
+     * @param string $selectortype The selector type
+     * @param string $containerelement The container selector type
+     * @param string $containerselectortype The container locator
+     */
+    public function should_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) {
+        // Get the container node.
+        $containernode = $this->get_selected_node($containerselectortype, $containerelement);
+
+        list($selector, $locator) = $this->transform_selector($selectortype, $element);
+
+        // Specific exception giving info about where can't we find the element.
+        $locatorexceptionmsg = $element . '" in the "' . $containerelement. '" "' . $containerselectortype. '"';
+        $exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $locatorexceptionmsg);
+
+        // Looks for the requested node inside the container node.
+        $this->find($selector, $locator, $exception, $containernode);
+    }
+
+    /**
+     * Checks that an element and selector type does not exist in another element and selector type on the current page.
+     *
+     * This step is for advanced users, use it if you don't find anything else suitable for what you need.
+     *
+     * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exist in the "(?P<element2_string>(?:[^"]|\\")*)" "(?P<selector2_string>[^"]*)"$/
+     * @throws ExpectationException
+     * @param string $element The locator of the specified selector
+     * @param string $selectortype The selector type
+     * @param string $containerelement The container selector type
+     * @param string $containerselectortype The container locator
+     */
+    public function should_not_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) {
+        try {
+            $this->should_exist_in_the($element, $selectortype, $containerelement, $containerselectortype);
+            throw new ExpectationException('The "' . $element . '" "' . $selectortype . '" exists in the "' .
+                $containerelement . '" "' . $containerselectortype . '"', $this->getSession());
+        } catch (ElementNotFoundException $e) {
+            // It passes.
+            return;
+        }
+    }
 }
index de6482e..fe7a58b 100644 (file)
@@ -2406,12 +2406,12 @@ class core_moodlelib_testcase extends advanced_testcase {
         $this->resetAfterTest();
 
         // Additional names in an array.
-        $testarray = array('firstnamephonetic',
-                           'lastnamephonetic',
-                           'middlename',
-                           'alternatename',
-                           'firstname',
-                           'lastname');
+        $testarray = array('firstnamephonetic' => 'firstnamephonetic',
+                'lastnamephonetic' => 'lastnamephonetic',
+                'middlename' => 'middlename',
+                'alternatename' => 'alternatename',
+                'firstname' => 'firstname',
+                'lastname' => 'lastname');
         $this->assertEquals($testarray, get_all_user_name_fields());
 
         // Additional names as a string.
@@ -2421,6 +2421,19 @@ class core_moodlelib_testcase extends advanced_testcase {
         // Additional names as a string with an alias.
         $teststring = 't.firstnamephonetic,t.lastnamephonetic,t.middlename,t.alternatename,t.firstname,t.lastname';
         $this->assertEquals($teststring, get_all_user_name_fields(true, 't'));
+
+        // Additional name fields with a prefix - object.
+        $testarray = array('firstnamephonetic' => 'authorfirstnamephonetic',
+                'lastnamephonetic' => 'authorlastnamephonetic',
+                'middlename' => 'authormiddlename',
+                'alternatename' => 'authoralternatename',
+                'firstname' => 'authorfirstname',
+                'lastname' => 'authorlastname');
+        $this->assertEquals($testarray, get_all_user_name_fields(false, null, 'author'));
+
+        // Additional name fields with an alias and a title - string.
+        $teststring = 'u.firstnamephonetic AS authorfirstnamephonetic,u.lastnamephonetic AS authorlastnamephonetic,u.middlename AS authormiddlename,u.alternatename AS authoralternatename,u.firstname AS authorfirstname,u.lastname AS authorlastname';
+        $this->assertEquals($teststring, get_all_user_name_fields(true, 'u', null, 'author'));
     }
 
     public function test_order_in_string() {
@@ -2599,4 +2612,84 @@ class core_moodlelib_testcase extends advanced_testcase {
         }
     }
 
+    /**
+     * Test function username_load_fields_from_object().
+     */
+    public function test_username_load_fields_from_object() {
+        $this->resetAfterTest();
+
+        // This object represents the information returned from an sql query.
+        $userinfo = new stdClass();
+        $userinfo->userid = 1;
+        $userinfo->username = 'loosebruce';
+        $userinfo->firstname = 'Bruce';
+        $userinfo->lastname = 'Campbell';
+        $userinfo->firstnamephonetic = 'ブルース';
+        $userinfo->lastnamephonetic = 'カンベッル';
+        $userinfo->middlename = '';
+        $userinfo->alternatename = '';
+        $userinfo->email = '';
+        $userinfo->picture = 23;
+        $userinfo->imagealt = 'Michael Jordan draining another basket.';
+        $userinfo->idnumber = 3982;
+
+        // Just user name fields.
+        $user = new stdClass();
+        $user = username_load_fields_from_object($user, $userinfo);
+        $expectedarray = new stdClass();
+        $expectedarray->firstname = 'Bruce';
+        $expectedarray->lastname = 'Campbell';
+        $expectedarray->firstnamephonetic = 'ブルース';
+        $expectedarray->lastnamephonetic = 'カンベッル';
+        $expectedarray->middlename = '';
+        $expectedarray->alternatename = '';
+        $this->assertEquals($user, $expectedarray);
+
+        // User information for showing a picture.
+        $user = new stdClass();
+        $additionalfields = explode(',', user_picture::fields());
+        $user = username_load_fields_from_object($user, $userinfo, null, $additionalfields);
+        $user->id = $userinfo->userid;
+        $expectedarray = new stdClass();
+        $expectedarray->id = 1;
+        $expectedarray->firstname = 'Bruce';
+        $expectedarray->lastname = 'Campbell';
+        $expectedarray->firstnamephonetic = 'ブルース';
+        $expectedarray->lastnamephonetic = 'カンベッル';
+        $expectedarray->middlename = '';
+        $expectedarray->alternatename = '';
+        $expectedarray->email = '';
+        $expectedarray->picture = 23;
+        $expectedarray->imagealt = 'Michael Jordan draining another basket.';
+        $this->assertEquals($user, $expectedarray);
+
+        // Alter the userinfo object to have a prefix.
+        $userinfo->authorfirstname = 'Bruce';
+        $userinfo->authorlastname = 'Campbell';
+        $userinfo->authorfirstnamephonetic = 'ブルース';
+        $userinfo->authorlastnamephonetic = 'カンベッル';
+        $userinfo->authormiddlename = '';
+        $userinfo->authorpicture = 23;
+        $userinfo->authorimagealt = 'Michael Jordan draining another basket.';
+        $userinfo->authoremail = 'test@testing.net';
+
+
+        // Return an object with user picture information.
+        $user = new stdClass();
+        $additionalfields = explode(',', user_picture::fields());
+        $user = username_load_fields_from_object($user, $userinfo, 'author', $additionalfields);
+        $user->id = $userinfo->userid;
+        $expectedarray = new stdClass();
+        $expectedarray->id = 1;
+        $expectedarray->firstname = 'Bruce';
+        $expectedarray->lastname = 'Campbell';
+        $expectedarray->firstnamephonetic = 'ブルース';
+        $expectedarray->lastnamephonetic = 'カンベッル';
+        $expectedarray->middlename = '';
+        $expectedarray->alternatename = '';
+        $expectedarray->email = 'test@testing.net';
+        $expectedarray->picture = 23;
+        $expectedarray->imagealt = 'Michael Jordan draining another basket.';
+        $this->assertEquals($user, $expectedarray);
+    }
 }
index 2db1ecd..7807cae 100644 (file)
@@ -56,6 +56,9 @@ information provided here is intended especially for developers.
   to 6 hours from 24 hours because etags and x-sendfile support should make file serving less expensive.
 * Date format locale charset for windows server will come from calendar type and for gregorian it will use
   lang file.
+* The library to interact with Box.net (class boxclient) is only compatible with their APIv1 which
+  reaches its end of life on the 14th of Dec. You should migrate your scripts to make usage of the
+  new class boxnet_client(). Note that the method names and return values have changed.
 
 DEPRECATIONS:
 Various previously deprecated functions have now been altered to throw DEBUG_DEVELOPER debugging notices
index 4bfabeb..955526e 100644 (file)
@@ -1323,12 +1323,31 @@ function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseidd
 function reset_text_filters_cache($phpunitreset = false) {
     global $CFG, $DB;
 
-    if (!$phpunitreset) {
-        $DB->delete_records('cache_text');
+    if ($phpunitreset) {
+        // HTMLPurifier does not change, DB is already reset to defaults,
+        // nothing to do here.
+        return;
     }
 
+    $DB->delete_records('cache_text');
+
     $purifdir = $CFG->cachedir.'/htmlpurifier';
     remove_dir($purifdir, true);
+
+    // Update $CFG->filterall cache flag.
+    if (empty($CFG->stringfilters)) {
+        set_config('filterall', 0);
+        return;
+    }
+    $installedfilters = core_component::get_plugin_list('filter');
+    $filters = explode(',', $CFG->stringfilters);
+    foreach ($filters as $filter) {
+        if (isset($installedfilters[$filter])) {
+            set_config('filterall', 1);
+            return;
+        }
+    }
+    set_config('filterall', 0);
 }
 
 /**
index 118da7b..91931ac 100644 (file)
Binary files a/lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-debug.js and b/lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-debug.js differ
index 8b8d1eb..11df04a 100644 (file)
Binary files a/lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-min.js and b/lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-min.js differ
index a1173c3..72fa3d4 100644 (file)
Binary files a/lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu.js and b/lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu.js differ
index 2d954ca..2b7d519 100644 (file)
Binary files a/lib/yui/build/moodle-core-dock-loader/moodle-core-dock-loader-debug.js and b/lib/yui/build/moodle-core-dock-loader/moodle-core-dock-loader-debug.js differ
index 31147f7..a46e4f9 100644 (file)
Binary files a/lib/yui/build/moodle-core-dock-loader/moodle-core-dock-loader-min.js and b/lib/yui/build/moodle-core-dock-loader/moodle-core-dock-loader-min.js differ
index e560694..dcd9684 100644 (file)
Binary files a/lib/yui/build/moodle-core-dock-loader/moodle-core-dock-loader.js and b/lib/yui/build/moodle-core-dock-loader/moodle-core-dock-loader.js differ
index ca6f49d..1362dc3 100644 (file)
Binary files a/lib/yui/build/moodle-core-dock/moodle-core-dock-debug.js and b/lib/yui/build/moodle-core-dock/moodle-core-dock-debug.js differ
index ad5c54b..a1ab1de 100644 (file)
Binary files a/lib/yui/build/moodle-core-dock/moodle-core-dock-min.js and b/lib/yui/build/moodle-core-dock/moodle-core-dock-min.js differ
index 3cba81c..f2c325a 100644 (file)
Binary files a/lib/yui/build/moodle-core-dock/moodle-core-dock.js and b/lib/yui/build/moodle-core-dock/moodle-core-dock.js differ
index 6964253..e8d0895 100644 (file)
@@ -332,7 +332,8 @@ YUI.add('moodle-core-dragdrop', function(Y) {
                 headerContent : dialogtitle,
                 bodyContent : droplist,
                 draggable : true,
-                visible : true
+                visible : true,
+                centered : true
             });
 
             // Focus the first drop target.
index 17044d1..08a467e 100644 (file)
@@ -14,7 +14,8 @@ var BODY = Y.one(Y.config.doc.body),
         MENUCONTENT : '.menu[data-rel=menu-content]',
         MENUCONTENTCHILD: 'li a',
         MENUCHILD: '.menu li a',
-        TOGGLE : '.toggle-display'
+        TOGGLE : '.toggle-display',
+        KEEPOPEN: '[data-keepopen="1"]'
     },
     ACTIONMENU,
     ALIGN = {
@@ -157,6 +158,14 @@ ACTIONMENU.prototype = {
 
         // Check tabbing.
         this.events.push(menu.delegate('key', this.checkFocus, 'down:9', SELECTOR.MENUCHILD, this));
+
+        // Close the menu after a button was pushed.
+        this.events.push(menu.delegate('click', function(e) {
+            if (e.currentTarget.test(SELECTOR.KEEPOPEN)) {
+                return;
+            }
+            this.hideMenu();
+        }, SELECTOR.MENUCHILD, this));
     },
 
     /**
@@ -200,7 +209,7 @@ ACTIONMENU.prototype = {
      * @param {EventFacade} e
      */
     hideIfOutside : function(e) {
-        if (!e.target.test(SELECTOR.MENU) && !e.target.ancestor(SELECTOR.MENU)) {
+        if (!e.target.ancestor(SELECTOR.MENUCHILD, true)) {
             this.hideMenu();
         }
     },
index e837107..c70839a 100644 (file)
@@ -138,7 +138,7 @@ BLOCK.prototype = {
 
         movetoimg = Y.Node.create('<img />').setAttrs({
             alt : Y.Escape.html(M.str.block.undockitem),
-            title : Y.Escape.html(M.util.get_string('undockblock', 'block', blocktitle.innerHTML)),
+            title : Y.Escape.html(M.util.get_string('undockblock', 'block', blocktitle.get('innerHTML'))),
             src : M.util.image_url(icon, 'moodle')
         });
         moveto = Y.Node.create('<a class="moveto customcommand requiresjs"></a>').setAttrs({
index a3dbb82..25b04bc 100644 (file)
@@ -33,14 +33,17 @@ M.core.dock.ensureMoveToIconExists = function(blocknode) {
     var commands,
         moveto = Y.Node.create('<input type="image" class="moveto customcommand requiresjs" />'),
         blockaction = blocknode.one('.block_action'),
-        icon = 't/block_to_dock';
+        icon = 't/block_to_dock',
+        titleh2 = blocknode.one('.header .title h2');
 
     // Must set the image src separately of we get an error with XML strict headers
     if (Y.one(document.body).hasClass('dir-rtl')) {
         icon = icon + '_rtl';
     }
     moveto.setAttribute('alt', M.util.get_string('addtodock', 'block'));
-    moveto.setAttribute('title', Y.Escape.html(M.util.get_string('dockblock', 'block', blocknode.one('.header .title h2').getHTML())));
+    if (titleh2) {
+        moveto.setAttribute('title', Y.Escape.html(M.util.get_string('dockblock', 'block', titleh2.getHTML())));
+    }
     moveto.setAttribute('src', M.util.image_url(icon, 'moodle'));
 
     if (blockaction) {
index e13c486..d4c6d4d 100644 (file)
@@ -136,7 +136,7 @@ if (substr($viewing, 0, 7) == MESSAGE_VIEW_COURSE) {
 if (!empty($user1->id) && $user1->id != $USER->id) {
     $PAGE->navigation->extend_for_user($user1);
 }
-if (!empty($user2->id) && $user2->id != $USER->id) {
+if (!empty($user2->id) && $user2realuser && ($user2->id != $USER->id)) {
     $PAGE->navigation->extend_for_user($user2);
 }
 
@@ -161,7 +161,7 @@ if ($unblockcontact and confirm_sesskey()) {
 
 //was a message sent? Do NOT allow someone looking at someone else's messages to send them.
 $messageerror = null;
-if ($currentuser && $user2realuser && has_capability('moodle/site:sendmessage', $systemcontext)) {
+if ($currentuser && !empty($user2) && has_capability('moodle/site:sendmessage', $systemcontext)) {
     // Check that the user is not blocking us!!
     if ($contact = $DB->get_record('message_contacts', array('userid' => $user2->id, 'contactid' => $user1->id))) {
         if ($contact->blocked and !has_capability('moodle/site:readallmessages', $systemcontext)) {
@@ -221,7 +221,7 @@ $countunreadtotal = 0; //count of unread messages from all users
 
 //we're dealing with unread messages early so the contact list will accurately reflect what is read/unread
 $viewingnewmessages = false;
-if ($user2realuser) {
+if (!empty($user2)) {
     //are there any unread messages from $user2
     $countunread = message_count_unread_messages($user1, $user2);
     if ($countunread>0) {
@@ -249,7 +249,7 @@ list($onlinecontacts, $offlinecontacts, $strangers) = message_get_contacts($user
 message_print_contact_selector($countunreadtotal, $viewing, $user1, $user2, $blockedusers, $onlinecontacts, $offlinecontacts, $strangers, $showactionlinks, $page);
 
 echo html_writer::start_tag('div', array('class' => 'messagearea mdl-align'));
-    if ($user2realuser) {
+    if (!empty($user2)) {
 
         echo html_writer::start_tag('div', array('class' => 'mdl-left messagehistory'));
 
@@ -306,7 +306,7 @@ echo html_writer::start_tag('div', array('class' => 'messagearea mdl-align'));
         echo html_writer::end_tag('div');
 
         //send message form
-        if ($currentuser && has_capability('moodle/site:sendmessage', $systemcontext)) {
+        if ($currentuser && has_capability('moodle/site:sendmessage', $systemcontext) && $user2realuser) {
             echo html_writer::start_tag('div', array('class' => 'mdl-align messagesend'));
                 if (!empty($messageerror)) {
                     echo html_writer::tag('span', $messageerror, array('id' => 'messagewarning'));
index bf9f73f..98e5775 100644 (file)
@@ -378,6 +378,17 @@ function message_get_contacts($user1=null, $user2=null) {
     }
     $rs->close();
 
+    // Add noreply user and support user to the list.
+    $supportuser = core_user::get_support_user();
+    $supportuser->messagecount = message_count_unread_messages($USER, $supportuser);
+    if ($supportuser->messagecount > 0) {
+        $strangers[] = $supportuser;
+    }
+    $noreplyuser = core_user::get_noreply_user();
+    $noreplyuser->messagecount = message_count_unread_messages($USER, $noreplyuser);
+    if ($noreplyuser->messagecount > 0) {
+        $strangers[] = $noreplyuser;
+    }
     return array($onlinecontacts, $offlinecontacts, $strangers);
 }
 
@@ -1941,7 +1952,12 @@ function message_print_message_history($user1, $user2 ,$search = '', $messagelim
     echo html_writer::end_tag('td');
 
     echo html_writer::start_tag('td', array('align' => 'center', 'id' => 'user2'));
-    echo $OUTPUT->user_picture($user2, array('size' => 100, 'courseid' => SITEID));
+    // Show user picture with link is real user else without link.
+    if (core_user::is_real_user($user2->id)) {
+        echo $OUTPUT->user_picture($user2, array('size' => 100, 'courseid' => SITEID));
+    } else {
+        echo $OUTPUT->user_picture($user2, array('size' => 100, 'courseid' => SITEID, 'link' => false));
+    }
     echo html_writer::tag('div', fullname($user2), array('class' => 'heading'));
 
     if ($showactionlinks && isset($user2->iscontact) && isset($user2->isblocked)) {
@@ -2156,8 +2172,11 @@ function message_print_contactlist_user($contact, $incontactlist = true, $isbloc
     $strcontact = $strblock = $strhistory = null;
 
     if ($showactionlinks) {
-        $strcontact = message_get_contact_add_remove_link($incontactlist, $isblocked, $contact);
-        $strblock   = message_get_contact_block_link($incontactlist, $isblocked, $contact);
+        // Show block and delete links if user is real user.
+        if (core_user::is_real_user($contact->id)) {
+            $strcontact = message_get_contact_add_remove_link($incontactlist, $isblocked, $contact);
+            $strblock   = message_get_contact_block_link($incontactlist, $isblocked, $contact);
+        }
         $strhistory = message_history_link($USER->id, $contact->id, true, '', '', 'icon');
     }
 
index d7747d1..1eb58aa 100644 (file)
@@ -64,6 +64,10 @@ class pdf extends \FPDI {
     const GSPATH_NOTESTFILE = 'notestfile';
     /** Any other error */
     const GSPATH_ERROR = 'error';
+    /** Min. width an annotation should have */
+    const MIN_ANNOTATION_WIDTH = 5;
+    /** Min. height an annotation should have */
+    const MIN_ANNOTATION_HEIGHT = 5;
 
     /**
      * Combine the given PDF files into a single PDF. Optionally add a coversheet and coversheet fields.
@@ -300,6 +304,15 @@ class pdf extends \FPDI {
                 $ry = abs($sy - $ey) / 2;
                 $sx = min($sx, $ex) + $rx;
                 $sy = min($sy, $ey) + $ry;
+
+                // $rx and $ry should be >= min width and height
+                if ($rx < self::MIN_ANNOTATION_WIDTH) {
+                    $rx = self::MIN_ANNOTATION_WIDTH;
+                }
+                if ($ry < self::MIN_ANNOTATION_HEIGHT) {
+                    $ry = self::MIN_ANNOTATION_HEIGHT;
+                }
+
                 $this->Ellipse($sx, $sy, $rx, $ry);
                 break;
             case 'rectangle':
@@ -307,6 +320,14 @@ class pdf extends \FPDI {
                 $h = abs($sy - $ey);
                 $sx = min($sx, $ex);
                 $sy = min($sy, $ey);
+
+                // Width or height should be >= min width and height
+                if ($w < self::MIN_ANNOTATION_WIDTH) {
+                    $w = self::MIN_ANNOTATION_WIDTH;
+                }
+                if ($h < self::MIN_ANNOTATION_HEIGHT) {
+                    $h = self::MIN_ANNOTATION_HEIGHT;
+                }
                 $this->Rect($sx, $sy, $w, $h);
                 break;
             case 'highlight':
@@ -316,6 +337,12 @@ class pdf extends \FPDI {
                 $sy = min($sy, $ey) + ($h * 0.5);
                 $this->SetAlpha(0.5, 'Normal', 0.5, 'Normal');
                 $this->SetLineWidth(8.0 * $this->scale);
+
+                // width should be >= min width
+                if ($w < self::MIN_ANNOTATION_WIDTH) {
+                    $w = self::MIN_ANNOTATION_WIDTH;
+                }
+
                 $this->Rect($sx, $sy, $w, $h);
                 $this->SetAlpha(1.0, 'Normal', 1.0, 'Normal');
                 break;
@@ -326,7 +353,10 @@ class pdf extends \FPDI {
                     foreach ($points as $point) {
                         $scalepath[] = intval($point) * $this->scale;
                     }
-                    $this->PolyLine($scalepath, 'S');
+
+                    if (!empty($scalepath)) {
+                        $this->PolyLine($scalepath, 'S');
+                    }
                 }
                 break;
             case 'stamp':
@@ -335,6 +365,8 @@ class pdf extends \FPDI {
                 $h = abs($sy - $ey);
                 $sx = min($sx, $ex);
                 $sy = min($sy, $ey);
+
+                // Stamp is always more than 40px, so no need to check width/height.
                 $this->Image($imgfile, $sx, $sy, $w, $h);
                 break;
             default: // Line.
diff --git a/mod/assign/feedback/editpdf/db/install.php b/mod/assign/feedback/editpdf/db/install.php
new file mode 100644 (file)
index 0000000..1214a6a
--- /dev/null
@@ -0,0 +1,53 @@
+<?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/>.
+
+/**
+ * Install code for the feedback_editpdf module.
+ *
+ * @package   assignfeedback_editpdf
+ * @copyright 2013 Jerome Mouneyrac
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * EditPDF install code
+ */
+function xmldb_assignfeedback_editpdf_install() {
+    global $CFG;
+
+    // List of default stamps.
+    $defaultstamps = array('smile.png', 'sad.png', 'tick.png', 'cross.png');
+
+    // Stamp file object.
+    $filerecord = new stdClass;
+    $filerecord->component = 'assignfeedback_editpdf';
+    $filerecord->contextid = context_system::instance()->id;
+    $filerecord->userid    = get_admin()->id;
+    $filerecord->filearea  = 'stamps';
+    $filerecord->filepath  = '/';
+    $filerecord->itemid    = 0;
+
+    $fs = get_file_storage();
+
+    // Load all default stamps.
+    foreach ($defaultstamps as $stamp) {
+        $filerecord->filename = $stamp;
+        $fs->create_file_from_pathname($filerecord,
+            $CFG->dirroot . '/mod/assign/feedback/editpdf/pix/' . $filerecord->filename);
+    }
+}
diff --git a/mod/assign/feedback/editpdf/db/upgrade.php b/mod/assign/feedback/editpdf/db/upgrade.php
new file mode 100644 (file)
index 0000000..46ff47a
--- /dev/null
@@ -0,0 +1,68 @@
+<?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/>.
+
+/**
+ * Upgrade code for the feedback_editpdf module.
+ *
+ * @package   assignfeedback_editpdf
+ * @copyright 2013 Jerome Mouneyrac
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * EditPDF upgrade code
+ * @param int $oldversion
+ * @return bool
+ */
+function xmldb_assignfeedback_editpdf_upgrade($oldversion) {
+    global $CFG;
+
+    if ($oldversion < 2013110800) {
+
+        // Check that no stamps where uploaded.
+        $fs = get_file_storage();
+        $stamps = $fs->get_area_files(context_system::instance()->id, 'assignfeedback_editpdf',
+            'stamps', 0, "filename", false);
+
+        // Add default stamps.
+        if (empty($stamps)) {
+            // List of default stamps.
+            $defaultstamps = array('smile.png', 'sad.png', 'tick.png', 'cross.png');
+
+            // Stamp file object.
+            $filerecord = new stdClass;
+            $filerecord->component = 'assignfeedback_editpdf';
+            $filerecord->contextid = context_system::instance()->id;
+            $filerecord->userid    = get_admin()->id;
+            $filerecord->filearea  = 'stamps';
+            $filerecord->filepath  = '/';
+            $filerecord->itemid    = 0;
+
+            // Add all default stamps.
+            foreach ($defaultstamps as $stamp) {
+                $filerecord->filename = $stamp;
+                $fs->create_file_from_pathname($filerecord,
+                    $CFG->dirroot . '/mod/assign/feedback/editpdf/pix/' . $filerecord->filename);
+            }
+        }
+
+        upgrade_plugin_savepoint(true, 2013110800, 'assignfeedback', 'editpdf');
+    }
+
+    return true;
+}
index a504604..3ad4571 100644 (file)
 defined('MOODLE_INTERNAL') || die();
 
 $string['addtoquicklist'] = 'Add to quicklist';
-$string['annotationcolour'] = 'Annotation color';
+$string['annotationcolour'] = 'Annotation colour';
 $string['black'] = 'Black';
 $string['blue'] = 'Blue';
-$string['cannotopenpdf'] = 'Cannot open the pdf file. The file may be corrupt, or in an unsupported format.';
+$string['cannotopenpdf'] = 'Cannot open the PDF. The file may be corrupt, or in an unsupported format.';
 $string['clear'] = 'Clear';
-$string['colourpicker'] = 'Colour Picker';
-$string['commentcolour'] = 'Comment color';
+$string['colourpicker'] = 'Colour picker';
+$string['commentcolour'] = 'Comment colour';
 $string['comment'] = 'Comments';
 $string['command'] = 'Command:';
 $string['commentcontextmenu'] = 'Comment context menu';
@@ -46,7 +46,7 @@ $string['errorgenerateimage'] = 'Error generating image with ghostscript, debugg
 $string['editpdf'] = 'Annotate PDF';
 $string['editpdf_help'] = 'Annotate students submissions directly in the browser and produce an edited downloadable PDF.';
 $string['enabled'] = 'Annotate PDF';
-$string['enabled_help'] = 'If enabled, the teacher will be able to create annotated pdf files when marking the assignments. This allows the teacher to add comments, drawing and stamps directly on top of the students work. The annotating is done in the browser and no extra software is required.';
+$string['enabled_help'] = 'If enabled, the teacher will be able to create annotated PDF files when marking the assignments. This allows the teacher to add comments, drawing and stamps directly on top of the students work. The annotating is done in the browser and no extra software is required.';
 $string['filter'] = 'Filter comments...';
 $string['generatefeedback'] = 'Generate feedback PDF';
 $string['gotopage'] = 'Go to page';
@@ -54,7 +54,7 @@ $string['green'] = 'Green';
 $string['gspath'] = 'Ghostscript path';
 $string['gspath_help'] = 'On most Linux installs, this can be left as \'/usr/bin/gs\'. On Windows it will be something like \'c:\\gs\\bin\\gswin32c.exe\' (make sure there are no spaces in the path - if necessary copy the files \'gswin32c.exe\' and \'gsdll32.dll\' to a new folder without a space in the path)';
 $string['highlight'] = 'Highlight';
-$string['jsrequired'] = 'Annotating PDF documents requires javascript. Please enable javascript in your browser if you want to use this feature.';
+$string['jsrequired'] = 'JavaScript is required to annotate a PDF. Please enable JavaScript in your browser to use this feature.';
 $string['launcheditor'] = 'Launch PDF editor...';
 $string['line'] = 'Line';
 $string['loadingeditor'] = 'Loading PDF editor';
@@ -73,7 +73,7 @@ $string['result'] = 'Result:';
 $string['searchcomments'] = 'Search comments';
 $string['select'] = 'Select';
 $string['stamppicker'] = 'Stamp picker';
-$string['stampsdesc'] = 'Stamps must be image files. These images can be used with the stamp tool to annotate the PDF.';
+$string['stampsdesc'] = 'Stamps must be image files (recommended size: 40x40). These images can be used with the stamp tool to annotate the PDF.';
 $string['stamps'] = 'Stamps';
 $string['stamp'] = 'Stamp';
 $string['test_doesnotexist'] = 'The ghostscript path points to a non-existent file';
@@ -86,8 +86,7 @@ $string['test_ok'] = 'The ghostscript path appears to be OK - please check you c
 $string['toolbarbutton'] = '{$a->tool} {$a->shortcut}';
 $string['tool'] = 'Tool';
 $string['unsavedchanges'] = 'Unsaved changes';
-$string['viewfeedbackonline'] = 'View annotated pdf...';
+$string['viewfeedbackonline'] = 'View annotated PDF...';
 $string['white'] = 'White';
 $string['yellow'] = 'Yellow';
-$string['zlibenabled'] = 'zlib enabled';
 $string['zlibnotavailable'] = 'Php extension "zlib" is not available. The annotate PDF feature relies on this php extension and will be disabled until zlib is installed and enabled.';
diff --git a/mod/assign/feedback/editpdf/pix/cross.png b/mod/assign/feedback/editpdf/pix/cross.png
new file mode 100644 (file)
index 0000000..daf333d
Binary files /dev/null and b/mod/assign/feedback/editpdf/pix/cross.png differ
diff --git a/mod/assign/feedback/editpdf/pix/sad.png b/mod/assign/feedback/editpdf/pix/sad.png
new file mode 100644 (file)
index 0000000..e87bf42
Binary files /dev/null and b/mod/assign/feedback/editpdf/pix/sad.png differ
diff --git a/mod/assign/feedback/editpdf/pix/smile.png b/mod/assign/feedback/editpdf/pix/smile.png
new file mode 100644 (file)
index 0000000..ec6d945
Binary files /dev/null and b/mod/assign/feedback/editpdf/pix/smile.png differ
diff --git a/mod/assign/feedback/editpdf/pix/tick.png b/mod/assign/feedback/editpdf/pix/tick.png
new file mode 100644 (file)
index 0000000..a74ec2d
Binary files /dev/null and b/mod/assign/feedback/editpdf/pix/tick.png differ
index 10493d0..7831262 100644 (file)
@@ -40,7 +40,7 @@ $settings->add(new admin_setting_configexecutable('assignfeedback_editpdf/gspath
                                                   '/usr/bin/gs'));
 
 $setting = new admin_setting_php_extension_enabled('assignfeedback_editpdf/zlibenabled',
-                                                   get_string('zlibenabled', 'assignfeedback_editpdf'),
+                                                   get_string('zlibenabled', 'admin'),
                                                    get_string('zlibnotavailable', 'assignfeedback_editpdf'),
                                                    'zlib');
 
index 291c8a0..d596434 100644 (file)
@@ -54,7 +54,7 @@
 .assignfeedback_editpdf_widget .pageheader select,
 .assignfeedback_editpdf_widget .pageheader button {
     background: none;
-    padding: 4px 10px;
+    padding: 4px 7px;
     border: 0px;
     border-radius: 0px;
     margin: 0px;
 .assignfeedback_editpdf_dropdown button {
     border: 0px;
     background: none;
-    padding-left: 10px;
-   &nbs