Merge branch 'MDL-49934-master' of git://github.com/jleyva/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 21 Mar 2016 00:39:15 +0000 (08:39 +0800)
committerDavid Monllao <davidm@moodle.com>
Mon, 21 Mar 2016 00:39:15 +0000 (08:39 +0800)
204 files changed:
admin/settings/server.php
admin/tests/behat/behat_admin.php
admin/tool/behat/cli/run.php
admin/tool/behat/cli/util_single_run.php
admin/tool/behat/tests/behat/manipulate_forms.feature
admin/tool/behat/tests/manager_test.php
admin/tool/langimport/tests/behat/manage_langpacks.feature
admin/tool/uploaduser/index.php
auth/ldap/auth.php
auth/ldap/config.html
auth/ldap/lang/en/auth_ldap.php
auth/manual/tests/behat/auth_manual.feature [new file with mode: 0644]
auth/tests/behat/behat_auth.php
backup/util/ui/tests/behat/behat_backup.php
behat.yml.dist
blocks/site_main_menu/block_site_main_menu.php
blocks/site_main_menu/styles.css
blocks/site_main_menu/tests/behat/edit_activities.feature [new file with mode: 0644]
blocks/social_activities/block_social_activities.php
blocks/social_activities/tests/behat/edit_activities.feature [new file with mode: 0644]
blocks/tests/behat/behat_blocks.php
calendar/tests/behat/behat_calendar.php
cohort/classes/output/cohortidnumber.php [new file with mode: 0644]
cohort/classes/output/cohortname.php [new file with mode: 0644]
cohort/index.php
cohort/lib.php
cohort/tests/behat/add_cohort.feature
cohort/tests/behat/behat_cohort.php
completion/tests/behat/behat_completion.php
composer.json
composer.lock
config-dist.php
course/classes/output/course_module_name.php [new file with mode: 0644]
course/lib.php
course/renderer.php
course/rest.php
course/tests/behat/activities_edit_name.feature [new file with mode: 0644]
course/tests/behat/behat_course.php
course/tests/behat/category_resort.feature
course/tests/behat/course_resort.feature
course/tests/courselib_test.php
course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-debug.js
course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-min.js
course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes.js
course/yui/src/toolboxes/js/resource.js
course/yui/src/toolboxes/js/toolbox.js
enrol/tests/behat/behat_enrol.php
grade/edit/tree/index.php
grade/grading/form/guide/tests/behat/behat_gradingform_guide.php
grade/grading/form/rubric/tests/behat/behat_gradingform_rubric.php
grade/grading/tests/behat/behat_grading.php
grade/report/grader/tests/behat/ajax_grader.feature
grade/report/grader/tests/behat/behat_gradereport_grader.php
grade/tests/behat/behat_grade.php
grade/tests/behat/grade_aggregation.feature
group/tests/behat/behat_groups.php
install/lang/ca/moodle.php
lang/en/auth.php
lang/en/cache.php
lang/en/cohort.php
lang/en/moodle.php
lib/accesslib.php
lib/behat/behat_base.php
lib/behat/classes/behat_config_manager.php
lib/behat/classes/behat_context_helper.php
lib/behat/classes/behat_selectors.php
lib/behat/form_field/behat_form_checkbox.php
lib/behat/form_field/behat_form_radio.php
lib/behat/form_field/behat_form_select.php
lib/classes/dml/recordset_walk.php
lib/db/caches.php
lib/ddl/database_manager.php
lib/ddl/sql_generator.php
lib/deprecatedlib.php
lib/dml/moodle_database.php
lib/dml/mssql_native_moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/oci_native_moodle_database.php
lib/dml/pdo_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/dml/sqlite3_pdo_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/dml/tests/recordset_walk_test.php
lib/editor/atto/plugins/accessibilitychecker/tests/behat/accessibilitychecker.feature
lib/editor/atto/plugins/image/tests/behat/image.feature
lib/form/tests/behat/modgrade_validation.feature
lib/grade/grade_category.php
lib/grade/grade_item.php
lib/gradelib.php
lib/moodlelib.php
lib/tests/accesslib_test.php
lib/tests/behat/behat_deprecated.php
lib/tests/behat/behat_forms.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_hooks.php
lib/tests/behat/behat_navigation.php
lib/tests/behat/behat_permissions.php
lib/tests/behat/behat_transformations.php
lib/tests/moodlelib_test.php
lib/upgrade.txt
lib/upgradelib.php
lib/xhprof/readme_moodle.txt
lib/xhprof/xhprof_html/callgraph.php
lib/xhprof/xhprof_html/index.php
lib/xhprof/xhprof_html/typeahead.php
message/tests/behat/behat_message.php
message/tests/behat/delete_messages.feature
mod/assign/feedback/editpdf/tests/behat/annotate_pdf.feature
mod/assign/feedback/editpdf/tests/behat/behat_assignfeedback_editpdf.php
mod/assign/tests/behat/comment_inline.feature
mod/choice/tests/behat/behat_mod_choice.php
mod/data/backup/moodle2/backup_data_stepslib.php
mod/data/backup/moodle2/restore_data_stepslib.php
mod/data/classes/external.php
mod/data/db/install.xml
mod/data/db/upgrade.php
mod/data/tests/behat/behat_mod_data.php
mod/data/tests/behat/required_entries.feature
mod/data/tests/externallib_test.php
mod/data/version.php
mod/feedback/tests/behat/behat_mod_feedback.php
mod/feedback/tests/behat/question_types.feature
mod/folder/renderer.php
mod/forum/classes/search/post.php
mod/forum/lib.php
mod/forum/tests/behat/behat_mod_forum.php
mod/forum/upgrade.txt
mod/glossary/classes/search/entry.php
mod/glossary/tests/behat/behat_mod_glossary.php
mod/lti/lib.php
mod/lti/locallib.php
mod/lti/tests/externallib_test.php
mod/lti/tests/lib_test.php
mod/page/classes/search/activity.php
mod/quiz/accessmanager.php
mod/quiz/accessmanager_form.php
mod/quiz/classes/external.php
mod/quiz/db/services.php
mod/quiz/locallib.php
mod/quiz/startattempt.php
mod/quiz/tests/behat/behat_mod_quiz.php
mod/quiz/tests/behat/settings_form_fields_disableif.feature
mod/quiz/tests/external_test.php
mod/quiz/version.php
mod/scorm/classes/event/cmielement_submitted.php [new file with mode: 0644]
mod/scorm/classes/event/scoreraw_submitted.php [new file with mode: 0644]
mod/scorm/classes/event/status_submitted.php [new file with mode: 0644]
mod/scorm/lang/en/scorm.php
mod/scorm/locallib.php
mod/scorm/tests/events_test.php
mod/scorm/version.php
mod/wiki/classes/external.php
mod/wiki/db/services.php
mod/wiki/locallib.php
mod/wiki/tests/externallib_test.php
mod/wiki/upgrade.txt [new file with mode: 0644]
mod/wiki/version.php
mod/workshop/allocation/manual/tests/behat/behat_workshopallocation_manual.php
mod/workshop/tests/behat/behat_mod_workshop.php
question/behaviour/adaptive/tests/walkthrough_test.php
question/behaviour/adaptivenopenalty/tests/walkthrough_test.php
question/behaviour/interactive/renderer.php
question/behaviour/interactive/tests/walkthrough_test.php
question/behaviour/interactivecountback/tests/walkthrough_test.php
question/behaviour/rendererbase.php
question/behaviour/upgrade.txt
question/engine/tests/helpers.php
question/tests/behat/behat_question.php
question/tests/behat/behat_question_base.php
question/type/calculated/tests/walkthrough_test.php
question/type/calculatedmulti/tests/walkthrough_test.php
question/type/calculatedsimple/tests/walkthrough_test.php
question/type/ddimageortext/tests/behat/preview.feature
question/type/ddimageortext/tests/walkthrough_test.php
question/type/ddmarker/tests/behat/behat_qtype_ddmarker.php
question/type/ddmarker/tests/behat/preview.feature
question/type/ddmarker/tests/walkthrough_test.php
question/type/ddwtos/tests/behat/preview.feature
question/type/ddwtos/tests/walkthrough_test.php
question/type/gapselect/tests/walkthrough_test.php
question/type/match/tests/walkthrough_test.php
question/type/multianswer/tests/walkthrough_test.php
question/type/numerical/tests/walkthrough_test.php
question/type/randomsamatch/tests/walkthrough_test.php
repository/boxnet/cli/migrationv1.php [deleted file]
repository/boxnet/lang/en/repository_boxnet.php
repository/boxnet/lib.php
repository/boxnet/locallib.php
repository/boxnet/migrationv1.php [deleted file]
repository/filesystem/lang/en/repository_filesystem.php
repository/filesystem/lib.php
repository/tests/behat/behat_filepicker.php
repository/upload/tests/behat/behat_repository_upload.php
repository/wikimedia/wikimedia.php
search/classes/area/base_activity.php
search/classes/document.php
search/classes/engine.php
search/classes/manager.php
search/engine/solr/classes/engine.php
search/engine/solr/tests/engine_test.php
search/tests/fixtures/mock_search_area.php
theme/upgrade.txt
version.php

index cc43e70..573ed4b 100644 (file)
@@ -18,11 +18,10 @@ $ADMIN->add('server', $temp);
 
 // "supportcontact" settingpage
 $temp = new admin_settingpage('supportcontact', new lang_string('supportcontact','admin'));
-if (isloggedin()) {
-    global $USER;
-    $primaryadminemail = $USER->email;
-    $primaryadminname  = fullname($USER, true);
-
+$primaryadmin = get_admin();
+if ($primaryadmin) {
+    $primaryadminemail = $primaryadmin->email;
+    $primaryadminname  = fullname($primaryadmin, true);
 } else {
     // no defaults during installation - admin user must be created first
     $primaryadminemail = NULL;
index 8c56f7a..8f69e31 100644 (file)
@@ -28,8 +28,7 @@
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 require_once(__DIR__ . '/../../../lib/behat/behat_field_manager.php');
 
-use Behat\Behat\Context\Step\Given as Given,
-    Behat\Gherkin\Node\TableNode as TableNode,
+use Behat\Gherkin\Node\TableNode as TableNode,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 
 /**
index 6e19659..98fc305 100644 (file)
@@ -120,10 +120,14 @@ $tags = '';
 
 if ($options['profile']) {
     $profile = $options['profile'];
-    if (!isset($CFG->behat_config[$profile])) {
-        echo "Invalid profile passed: " . $profile;
+
+    // If profile passed is not set, then exit.
+    if (!isset($CFG->behat_config[$profile]) && !isset($CFG->behat_profiles[$profile]) &&
+        !(isset($options['replace']) && (strpos($options['profile'], $options['replace']) >= 0 ))) {
+        echo "Invalid profile passed: " . $profile . PHP_EOL;
         exit(1);
     }
+
     $extraopts[] = '--profile="' . $profile . '"';
     // By default, profile tags will be used.
     if (!empty($CFG->behat_config[$profile]['filters']['tags'])) {
@@ -226,22 +230,27 @@ $exitcodes = print_combined_run_output($processes, $stoponfail);
 $time = round(microtime(true) - $time, 1);
 echo "Finished in " . gmdate("G\h i\m s\s", $time) . PHP_EOL . PHP_EOL;
 
+ksort($exitcodes);
 
 // Print exit info from each run.
-$status = false;
+// Status bits contains pass/fail status of parallel runs.
+$status = 0;
+$processcounter = 0;
 foreach ($exitcodes as $exitcode) {
-    $status = (bool)$status || (bool)$exitcode;
+    if ($exitcode) {
+        $status |= (1 << $processcounter);
+    }
+    $processcounter++;
 }
 
 // Run finished. Show exit code and output from individual process.
 $verbose = empty($options['verbose']) ? false : true;
-$verbose = $verbose || $status;
+$verbose = $verbose || !empty($status);
 
 // Show exit code from each process, if any process failed.
 if ($verbose) {
     // Echo exit codes.
     echo "Exit codes for each behat run: " . PHP_EOL;
-    ksort($exitcodes);
     foreach ($exitcodes as $run => $exitcode) {
         echo $run . ": " . $exitcode . PHP_EOL;
     }
@@ -263,7 +272,7 @@ print_each_process_info($processes, $verbose);
 // Remove site symlink if necessary.
 behat_config_manager::drop_parallel_site_links();
 
-exit((int) $status);
+exit($status);
 
 /**
  * Signal handler for terminal exit.
index d523569..1991707 100644 (file)
@@ -173,6 +173,11 @@ if ($options['install']) {
 
     // This is only displayed once for parallel install.
     if (empty($options['run'])) {
+        // Notify user that 2.5 profile has been converted to 3.5.
+        if (behat_config_manager::$autoprofileconversion) {
+            mtrace("2.5 behat profile detected, automatically converted to current 3.x format");
+        }
+
         $runtestscommand = behat_command::get_behat_command(true, !empty($options['run']));
 
         $runtestscommand .= ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
index 20c3c29..126a800 100644 (file)
@@ -12,7 +12,6 @@ Feature: Forms manipulation
     When I set the field "First name" to "Field value"
     And I set the field "Select a country" to "Japan"
     And I set the field "Unmask" to "1"
-    And I expand all fieldsets
     Then the field "First name" matches value "Field value"
     And the "Select a country" select box should contain "Japan"
     And the field "Unmask" matches value "1"
index 1dc44ef..0f24f38 100644 (file)
@@ -142,11 +142,10 @@ class tool_behat_manager_testcase extends advanced_testcase {
         // YAML decides when is is necessary to wrap strings between single quotes, so not controlled
         // values like paths should not be asserted including the key name as they would depend on the
         // directories values.
-        $this->assertContains($CFG->dirroot . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . 'features', $contents);
+        $this->assertContains($CFG->dirroot, $contents);
 
         // Not quoted strings.
         $this->assertContains('micarro: /me/lo/robaron', $contents);
-        $this->assertContains('class: behat_init_context', $contents);
 
         // YAML uses single quotes to wrap URL strings.
         $this->assertContains("base_url: '" . $CFG->behat_wwwroot . "'", $contents);
index 4d518de..6d6f0f0 100644 (file)
@@ -13,10 +13,10 @@ Feature: Manage language packs
   Scenario: Install language pack
     Given I log in as "admin"
     And I navigate to "Language packs" node in "Site administration > Language"
-    When I set the field "Available language packs" to "English - Pirate (en_ar)"
+    When I set the field "Available language packs" to "en_ar"
     And I press "Install selected language pack(s)"
     Then I should see "Language pack 'en_ar' was successfully installed"
-    And the "Installed language packs" select box should contain "English - Pirate (en_ar)"
+    And the "Installed language packs" select box should contain "en_ar"
     And I navigate to "Live logs" node in "Site administration > Reports"
     And I should see "The language pack 'en_ar' was installed."
     And I log out
@@ -35,14 +35,14 @@ Feature: Manage language packs
   Scenario: Try to uninstall language pack
     Given I log in as "admin"
     And I navigate to "Language packs" node in "Site administration > Language"
-    And I set the field "Available language packs" to "English - Pirate (en_ar)"
+    And I set the field "Available language packs" to "en_ar"
     And I press "Install selected language pack(s)"
-    When I set the field "Installed language packs" to "English - Pirate (en_ar)"
+    When I set the field "Installed language packs" to "en_ar"
     And I press "Uninstall selected language pack(s)"
     And I press "Continue"
     Then I should see "Language pack 'en_ar' was uninstalled"
-    And the "Installed language packs" select box should not contain "English - Pirate (en_ar)"
-    And the "Available language packs" select box should contain "English - Pirate (en_ar)"
+    And the "Installed language packs" select box should not contain "en_ar"
+    And the "Available language packs" select box should contain "en_ar"
     And I navigate to "Live logs" node in "Site administration > Reports"
     And I should see "The language pack 'en_ar' was removed."
     And I should see "Language pack uninstalled"
@@ -51,7 +51,7 @@ Feature: Manage language packs
   Scenario: Try to uninstall English language pack
     Given I log in as "admin"
     And I navigate to "Language packs" node in "Site administration > Language"
-    When I set the field "Installed language packs" to "English (en)"
+    When I set the field "Installed language packs" to "en"
     And I press "Uninstall selected language pack(s)"
     Then I should see "The English language pack cannot be uninstalled."
     And I navigate to "Live logs" node in "Site administration > Reports"
index 2b3f43e..b053388 100644 (file)
@@ -568,8 +568,18 @@ if ($formdata = $mform2->is_cancelled()) {
                     }
                     if ($existinguser->$column !== $user->$column) {
                         if ($column === 'email') {
-                            if ($DB->record_exists('user', array('email'=>$user->email))) {
-                                if ($noemailduplicates) {
+                            $select = $DB->sql_like('email', ':email', false, true, false, '|');
+                            $params = array('email' => $DB->sql_like_escape($user->email, '|'));
+                            if ($DB->record_exists_select('user', $select , $params)) {
+
+                                $changeincase = core_text::strtolower($existinguser->$column) === core_text::strtolower(
+                                                $user->$column);
+
+                                if ($changeincase) {
+                                    // If only case is different then switch to lower case and carry on.
+                                    $user->$column = core_text::strtolower($user->$column);
+                                    continue;
+                                } else if ($noemailduplicates) {
                                     $upt->track('email', $stremailduplicate, 'error');
                                     $upt->track('status', $strusernotupdated, 'error');
                                     $userserrors++;
@@ -1182,7 +1192,10 @@ while ($linenum <= $previewrows and $fields = $cir->next()) {
         if (!validate_email($rowcols['email'])) {
             $rowcols['status'][] = get_string('invalidemail');
         }
-        if ($DB->record_exists('user', array('email'=>$rowcols['email']))) {
+
+        $select = $DB->sql_like('email', ':email', false, true, false, '|');
+        $params = array('email' => $DB->sql_like_escape($rowcols['email'], '|'));
+        if ($DB->record_exists_select('user', $select , $params)) {
             $rowcols['status'][] = $stremailduplicate;
         }
     }
index 7098223..30536e6 100644 (file)
@@ -849,6 +849,9 @@ class auth_plugin_ldap extends auth_plugin_base {
                     }
                 }
             }
+            if ($this->config->suspended_attribute && $this->config->sync_suspended) {
+                $updatekeys[] = 'suspended';
+            }
             unset($all_keys); unset($key);
 
         } else {
@@ -931,6 +934,10 @@ class auth_plugin_ldap extends auth_plugin_base {
                 // get_userinfo_asobj() might have replaced $user->username with the value
                 // from the LDAP server (which can be mixed-case). Make sure it's lowercase
                 $user->username = trim(core_text::strtolower($user->username));
+                // It isn't possible to just rely on the configured suspension attribute since
+                // things like active directory use bit masks, other things using LDAP might
+                // do different stuff as well.
+                $user->suspended = $this->is_user_suspended($user);
                 if (empty($user->lang)) {
                     $user->lang = $CFG->lang;
                 }
@@ -1005,6 +1012,7 @@ class auth_plugin_ldap extends auth_plugin_base {
             if (!empty($updatekeys)) {
                 $newuser = new stdClass();
                 $newuser->id = $userid;
+                $newuser->suspended = $this->is_user_suspended((object) $newinfo);
 
                 foreach ($updatekeys as $key) {
                     if (isset($newinfo[$key])) {
@@ -1504,6 +1512,7 @@ class auth_plugin_ldap extends auth_plugin_base {
             }
         }
         $moodleattributes['username'] = core_text::strtolower(trim($this->config->user_attribute));
+        $moodleattributes['suspended'] = core_text::strtolower(trim($this->config->suspended_attribute));
         return $moodleattributes;
     }
 
@@ -1848,6 +1857,12 @@ class auth_plugin_ldap extends auth_plugin_base {
         if (!isset($config->user_attribute)) {
              $config->user_attribute = '';
         }
+        if (!isset($config->suspended_attribute)) {
+            $config->suspended_attribute = '';
+        }
+        if (!isset($config->sync_suspended)) {
+            $config->sync_suspended = false;
+        }
         if (!isset($config->search_sub)) {
              $config->search_sub = '';
         }
@@ -1944,6 +1959,8 @@ class auth_plugin_ldap extends auth_plugin_base {
         set_config('contexts', $config->contexts, $this->pluginconfig);
         set_config('user_type', core_text::strtolower(trim($config->user_type)), $this->pluginconfig);
         set_config('user_attribute', core_text::strtolower(trim($config->user_attribute)), $this->pluginconfig);
+        set_config('suspended_attribute', core_text::strtolower(trim($config->suspended_attribute)), $this->pluginconfig);
+        set_config('sync_suspended', $config->sync_suspended, $this->pluginconfig);
         set_config('search_sub', $config->search_sub, $this->pluginconfig);
         set_config('opt_deref', $config->opt_deref, $this->pluginconfig);
         set_config('preventpassindb', $config->preventpassindb, $this->pluginconfig);
@@ -2272,4 +2289,24 @@ class auth_plugin_ldap extends auth_plugin_base {
         return false;
     }
 
+    /**
+     * Check if a user is suspended. This function is intended to be used after calling
+     * get_userinfo_asobj. This is needed because LDAP doesn't have a notion of disabled
+     * users, however things like MS Active Directory support it and expose information
+     * through a field.
+     *
+     * @param object $user the user object returned by get_userinfo_asobj
+     * @return boolean
+     */
+    protected function is_user_suspended($user) {
+        if (!$this->config->suspended_attribute || !isset($user->suspended)) {
+            return false;
+        }
+        if ($this->config->suspended_attribute == 'useraccountcontrol' && $this->config->user_type == 'ad') {
+            return (bool)($user->suspended & AUTH_AD_ACCOUNTDISABLE);
+        }
+
+        return (bool)$user->suspended;
+    }
+
 } // End of the class
index 7c05937..d66cda2 100644 (file)
@@ -22,6 +22,12 @@ if (!isset($config->user_type)) {
 if (!isset($config->user_attribute)) {
     $config->user_attribute = '';
 }
+if (!isset($config->suspended_attribute)) {
+    $config->suspended_attribute = '';
+}
+if (!isset($config->sync_suspended)) {
+    $config->sync_suspended = '';
+}
 if (!isset($config->search_sub)) {
     $config->search_sub = '';
 }
@@ -305,6 +311,18 @@ if (!ldap_paged_results_supported($config->ldap_version)) {
         <?php print_string('auth_ldap_user_attribute', 'auth_ldap') ?>
     </td>
 </tr>
+<tr valign="top" class="required">
+    <td align="right">
+        <label for="suspended_attribute"><?php print_string('auth_ldap_suspended_attribute_key', 'auth_ldap') ?></label>
+    </td>
+    <td>
+        <input name="suspended_attribute" id="suspended_attribute" type="text" size="30" value="<?php echo $config->suspended_attribute?>" />
+        <?php if (isset($err['suspended_attribute'])) { echo $OUTPUT->error_text($err['suspended_attribute']); } ?>
+    </td>
+    <td>
+        <?php print_string('auth_ldap_suspended_attribute', 'auth_ldap') ?>
+    </td>
+</tr>
 <tr valign="top" class="required">
     <td align="right">
         <label for="memberattribute"><?php print_string('auth_ldap_memberattribute_key', 'auth_ldap') ?></label>
@@ -534,6 +552,17 @@ if (!ldap_paged_results_supported($config->ldap_version)) {
         <?php print_string('auth_remove_user', 'auth') ?>
     </td>
 </tr>
+<tr valign="top">
+    <td align="right">
+        <label for="menusync_suspended"><?php print_string('auth_sync_suspended_key', 'auth') ?></label>
+    </td>
+    <td>
+        <?php echo html_writer::select($yesno, 'sync_suspended', $config->sync_suspended, false); ?>
+    </td>
+    <td>
+        <?php print_string('auth_sync_suspended', 'auth'); ?>
+    </td>
+</tr>
 <tr>
     <td colspan="2">
         <h4><?php print_string('auth_ntlmsso', 'auth_ldap') ?></h4>
index 6812abe..349dd13 100644 (file)
@@ -87,6 +87,8 @@ $string['auth_ldap_unsupportedusertype'] = 'auth: ldap user_create() does not su
 $string['auth_ldap_update_userinfo'] = 'Update user information (firstname, lastname, address..) from LDAP to Moodle.  Specify "Data mapping" settings as you need.';
 $string['auth_ldap_user_attribute'] = 'Optional: Overrides the attribute used to name/search users. Usually \'cn\'.';
 $string['auth_ldap_user_attribute_key'] = 'User attribute';
+$string['auth_ldap_suspended_attribute'] = 'Optional: When provided this attribute will be used to enable/suspend the locally created user account.';
+$string['auth_ldap_suspended_attribute_key'] = 'Suspended attribute';
 $string['auth_ldap_user_exists'] = 'LDAP username already exists.';
 $string['auth_ldap_user_settings'] = 'User lookup settings';
 $string['auth_ldap_user_type'] = 'Select how users are stored in LDAP. This setting also specifies how login expiration, grace logins and user creation will work.';
diff --git a/auth/manual/tests/behat/auth_manual.feature b/auth/manual/tests/behat/auth_manual.feature
new file mode 100644 (file)
index 0000000..e7970dd
--- /dev/null
@@ -0,0 +1,28 @@
+@auth @auth_manual
+Feature: Test manual authentication works.
+  In order to check manual authentication
+  As a teacher
+  I need to go on login page and enter username and password.
+
+  Background:
+    Given the following "users" exist:
+      | username |
+      | teacher1 |
+
+  @javascript
+  Scenario: Check login works with javascript.
+    Given I am on homepage
+    And I expand navigation bar
+    And I click on "Log in" "link" in the ".logininfo" "css_element"
+    When I set the field "Username" to "teacher1"
+    And I set the field "Password" to "teacher1"
+    When I press "Log in"
+    Then I should see "You are logged in as"
+
+  Scenario: Check login works without javascript.
+    Given I am on homepage
+    And I click on "Log in" "link" in the ".logininfo" "css_element"
+    When I set the field "Username" to "teacher1"
+    And I set the field "Password" to "teacher1"
+    When I press "Log in"
+    Then I should see "You are logged in as"
index 959c2fb..53349b4 100644 (file)
@@ -28,8 +28,8 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given;
-use Behat\Behat\Context\Step\When as When;
+use Moodle\BehatExtension\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\When as When;
 
 /**
  * Log in log out steps definitions.
@@ -47,34 +47,16 @@ class behat_auth extends behat_base {
      * @Given /^I log in as "(?P<username_string>(?:[^"]|\\")*)"$/
      */
     public function i_log_in_as($username) {
+        // Visit login page.
+        $this->getSession()->visit($this->locate_path('login/index.php'));
 
-        // Running this step using the API rather than a chained step because
-        // we need to see if the 'Log in' link is available or we need to click
-        // the dropdown to expand the navigation bar before.
-        $this->getSession()->visit($this->locate_path('/'));
+        // Enter username and password.
+        $behatforms = behat_context_helper::get('behat_forms');
+        $behatforms->i_set_the_field_to('Username', $this->escape($username));
+        $behatforms->i_set_the_field_to('Password', $this->escape($username));
 
-        // Generic steps (we will prefix them later expanding the navigation dropdown if necessary).
-        $steps = array(
-            new Given('I click on "' . get_string('login') . '" "link" in the ".logininfo" "css_element"'),
-            new Given('I set the field "' . get_string('username') . '" to "' . $this->escape($username) . '"'),
-            new Given('I set the field "' . get_string('password') . '" to "'. $this->escape($username) . '"'),
-            new Given('I press "' . get_string('login') . '"')
-        );
-
-        // If Javascript is disabled we have enough with these steps.
-        if (!$this->running_javascript()) {
-            return $steps;
-        }
-
-        // Wait for the homepage to be ready.
-        $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
-
-        // If it is needed, it expands the navigation bar with the 'Log in' link.
-        if ($clicknavbar = $this->get_expand_navbar_step()) {
-            array_unshift($steps, $clicknavbar);
-        }
-
-        return $steps;
+        // Press log in button.
+        $behatforms->press_button(get_string('login'));
     }
 
     /**
@@ -101,35 +83,4 @@ class behat_auth extends behat_base {
 
         return $steps;
     }
-
-    /**
-     * Returns a step to open the navigation bar if it is needed.
-     *
-     * The top log in and log out links are hidden when middle or small
-     * size windows (or devices) are used. This step returns a step definition
-     * clicking to expand the navbar if it is hidden.
-     *
-     * @return Given|bool A step definition or false if there is no need to show the navbar.
-     */
-    protected function get_expand_navbar_step() {
-
-        // Checking if we need to click the navbar button to show the navigation menu, it
-        // is hidden by default when using clean theme and a medium or small screen size.
-
-        // The DOM and the JS should be all ready and loaded. Running without spinning
-        // as this is a widely used step and we can not spend time here trying to see
-        // a DOM node that is not always there (at the moment clean is not even the
-        // default theme...).
-        $navbuttonjs = "return (
-            Y.one('.btn-navbar') &&
-            Y.one('.btn-navbar').getComputedStyle('display') !== 'none'
-        )";
-
-        // Adding an extra click we need to show the 'Log in' link.
-        if (!$this->getSession()->getDriver()->evaluateScript($navbuttonjs)) {
-            return false;
-        }
-
-        return new Given('I click on ".btn-navbar" "css_element"');
-    }
 }
index bd0f777..410cdef 100644 (file)
@@ -219,7 +219,6 @@ class behat_backup extends behat_base {
             "/descendant::div[@class='restore-course-search']" .
             "/descendant::tr[contains(., $existingcourse)]" .
             "/descendant::input[@type='radio']");
-        $radionode->check();
         $radionode->click();
 
         // Pressing the continue button of the restore into an existing course section.
@@ -250,7 +249,6 @@ class behat_backup extends behat_base {
         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
             "/descendant::div[@class='restore-course-search']" .
             "/descendant::input[@type='radio']");
-        $radionode->check();
         $radionode->click();
 
         // Pressing the continue button of the restore into an existing course section.
@@ -280,7 +278,6 @@ class behat_backup extends behat_base {
         // Merge without deleting radio option.
         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
             "/descendant::input[@type='radio'][@name='target'][@value='1']");
-        $radionode->check();
         $radionode->click();
 
         // Pressing the continue button of the restore merging section.
@@ -310,7 +307,6 @@ class behat_backup extends behat_base {
         // Delete contents radio option.
         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
             "/descendant::input[@type='radio'][@name='target'][@value='0']");
-        $radionode->check();
         $radionode->click();
 
         // Pressing the continue button of the restore merging section.
@@ -419,8 +415,6 @@ class behat_backup extends behat_base {
             return;
         }
 
-        $pageoptions = clone $options;
-
         $rows = $options->getRows();
         $newrows = array();
         foreach ($rows as $k => $data) {
@@ -433,7 +427,8 @@ class behat_backup extends behat_base {
                 $newrows[] = $data;
             }
         }
-        $pageoptions->setRows($newrows);
+        $pageoptions = new TableNode($newrows);
+
         return $pageoptions;
     }
 
index 60cf807..8c7e910 100644 (file)
@@ -1,14 +1,13 @@
 default:
-  paths:
-    features: lib/behat/features
-    bootstrap: lib/behat/features/bootstrap
-  context:
-    class: behat_init_context
+  suites:
+    default:
+      paths: {  }
+      contexts: {  }
   extensions:
-    Behat\MinkExtension\Extension:
+    Behat\MinkExtension:
       base_url: 'http://localhost:8000'
       goutte: null
       selenium2: null
-    Moodle\BehatExtension\Extension:
-      features: {  }
+    Moodle\BehatExtension:
+      moodledirroot: /Should/Change/To/Moodle/www/dir
       steps_definitions: {  }
index cd26cbc..866047d 100644 (file)
@@ -51,6 +51,7 @@ class block_site_main_menu extends block_list {
         require_once($CFG->dirroot.'/course/lib.php');
         $context = context_course::instance($course->id);
         $isediting = $this->page->user_is_editing() && has_capability('moodle/course:manageactivities', $context);
+        $courserenderer = $this->page->get_renderer('core', 'course');
 
 /// extra fast view mode
         if (!$isediting) {
@@ -69,32 +70,18 @@ class block_site_main_menu extends block_list {
                     }
 
                     if (!empty($cm->url)) {
-                        $attrs = array();
-                        $attrs['title'] = $cm->modfullname;
-                        $attrs['class'] = $cm->extraclasses . ' activity-action';
-                        if ($cm->onclick) {
-                            // Get on-click attribute value if specified and decode the onclick - it
-                            // has already been encoded for display.
-                            $attrs['onclick'] = htmlspecialchars_decode($cm->onclick);
-                        }
-                        if (!$cm->visible) {
-                            $attrs['class'] .= ' dimmed';
-                        }
-                        $icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />';
-                        $content = html_writer::link($cm->url, $icon . $cm->get_formatted_name(), $attrs);
+                        $content = html_writer::div($courserenderer->course_section_cm_name($cm), 'activity');
                     } else {
                         $content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
                     }
 
-                    $this->content->items[] = $indent.html_writer::div($content, 'main-menu-content');
+                    $this->content->items[] = $indent . $content;
                 }
             }
             return $this->content;
         }
 
         // Slow & hacky editing mode.
-        /** @var core_course_renderer $courserenderer */
-        $courserenderer = $this->page->get_renderer('core', 'course');
         $ismoving = ismoving($course->id);
         course_create_sections_if_missing($course, 0);
         $modinfo = get_fast_modinfo($course);
@@ -104,11 +91,9 @@ class block_site_main_menu extends block_list {
             $strmovehere = get_string('movehere');
             $strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
             $strcancel= get_string('cancel');
-            $stractivityclipboard = $USER->activitycopyname;
         } else {
             $strmove = get_string('move');
         }
-        $editbuttons = '';
 
         if ($ismoving) {
             $this->content->icons[] = '<img src="'.$OUTPUT->pix_url('t/move') . '" class="iconsmall" alt="" />';
@@ -116,7 +101,6 @@ class block_site_main_menu extends block_list {
         }
 
         if (!empty($modinfo->sections[0])) {
-            $options = array('overflowdiv'=>true);
             foreach ($modinfo->sections[0] as $modnumber) {
                 $mod = $modinfo->cms[$modnumber];
                 if (!$mod->uservisible) {
@@ -153,27 +137,12 @@ class block_site_main_menu extends block_list {
                     } else {
                         $indent = '';
                     }
-                    $url = $mod->url;
-                    if (!$url) {
+                    if (!$mod->url) {
                         $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
                     } else {
-                        //Accessibility: incidental image - should be empty Alt text
-                        $attrs = array();
-                        $attrs['title'] = $mod->modfullname;
-                        $attrs['class'] = $mod->extraclasses . ' activity-action';
-                        if ($mod->onclick) {
-                            // Get on-click attribute value if specified and decode the onclick - it
-                            // has already been encoded for display.
-                            $attrs['onclick'] = htmlspecialchars_decode($mod->onclick);
-                        }
-                        if (!$mod->visible) {
-                            $attrs['class'] .= ' dimmed';
-                        }
-
-                        $icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />';
-                        $content = html_writer::link($url, $icon . $mod->get_formatted_name(), $attrs);
+                        $content = html_writer::div($courserenderer->course_section_cm_name($mod), ' activity');
                     }
-                    $this->content->items[] = $indent.html_writer::div($content . $editbuttons, 'main-menu-content');
+                    $this->content->items[] = $indent. $content . $editbuttons;
                 }
             }
         }
index 0e15ab7..f437bae 100644 (file)
@@ -6,5 +6,4 @@
 .block_site_main_menu .footer { margin-top: 1em; }
 .block_site_main_menu .section_add_menus noscript div { display: inline;}
 .block_site_main_menu .mod-indent,
-.block_site_main_menu .main-menu-content { display: table-cell; }
-.block_site_main_menu .main-menu-content > .activity-action { display: block; }
+.block_site_main_menu .activity { display: table-cell; }
diff --git a/blocks/site_main_menu/tests/behat/edit_activities.feature b/blocks/site_main_menu/tests/behat/edit_activities.feature
new file mode 100644 (file)
index 0000000..b94a0ad
--- /dev/null
@@ -0,0 +1,21 @@
+@block @block_main_menu
+Feature: Edit activities in main menu block
+  In order to use main menu block
+  As an admin
+  I need to add and edit activities there
+
+  @javascript
+  Scenario: Edit name of acitivity in-place in site main menu block
+    Given I log in as "admin"
+    And I am on site homepage
+    And I navigate to "Turn editing on" node in "Front page settings"
+    When I add a "Forum" to section "0" and I fill the form with:
+      | Forum name | My forum name |
+    And I click on "Edit title" "link" in the "//.[contains(@class,'block_site_main_menu')]//li[contains(.,'My forum name')]" "xpath_element"
+    And I set the field "New name for activity My forum name" to "New forum name"
+    And I press key "13" in the field "New name for activity My forum name"
+    Then I should not see "My forum name"
+    And I should see "New forum name"
+    And I follow "New forum name"
+    And I should not see "My forum name"
+    And I should see "New forum name"
index 6c6971b..2553413 100644 (file)
@@ -48,6 +48,7 @@ class block_social_activities extends block_list {
         }
 
         $course = $this->page->course;
+        $courserenderer = $this->page->get_renderer('core', 'course');
 
         require_once($CFG->dirroot.'/course/lib.php');
 
@@ -58,25 +59,18 @@ class block_social_activities extends block_list {
 /// extra fast view mode
         if (!$isediting) {
             if (!empty($modinfo->sections[0])) {
-                $options = array('overflowdiv'=>true);
                 foreach($modinfo->sections[0] as $cmid) {
                     $cm = $modinfo->cms[$cmid];
                     if (!$cm->uservisible) {
                         continue;
                     }
 
-                    $content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
-                    $instancename = $cm->get_formatted_name();
-
-                    if (!($url = $cm->url)) {
+                    if (!$cm->url) {
+                        $content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
                         $this->content->items[] = $content;
                         $this->content->icons[] = '';
                     } else {
-                        $linkcss = $cm->visible ? '' : ' class="dimmed" ';
-                        //Accessibility: incidental image - should be empty Alt text
-                        $icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />&nbsp;';
-                        $this->content->items[] = '<a title="'.$cm->modplural.'" '.$linkcss.' '.$cm->extra.
-                                ' href="' . $url . '">' . $icon . $instancename . '</a>';
+                        $this->content->items[] = html_writer::div($courserenderer->course_section_cm_name($cm), 'activity');
                     }
                 }
             }
@@ -85,21 +79,16 @@ class block_social_activities extends block_list {
 
 
         // Slow & hacky editing mode.
-        /** @var core_course_renderer $courserenderer */
-        $courserenderer = $this->page->get_renderer('core', 'course');
         $ismoving = ismoving($course->id);
-        $modinfo = get_fast_modinfo($course);
         $section = $modinfo->get_section_info(0);
 
         if ($ismoving) {
             $strmovehere = get_string('movehere');
             $strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
             $strcancel= get_string('cancel');
-            $stractivityclipboard = $USER->activitycopyname;
         } else {
             $strmove = get_string('move');
         }
-        $editbuttons = '';
 
         if ($ismoving) {
             $this->content->icons[] = '&nbsp;<img align="bottom" src="'.$OUTPUT->pix_url('t/move') . '" class="iconsmall" alt="" />';
@@ -107,7 +96,6 @@ class block_social_activities extends block_list {
         }
 
         if (!empty($modinfo->sections[0])) {
-            $options = array('overflowdiv'=>true);
             foreach ($modinfo->sections[0] as $modnumber) {
                 $mod = $modinfo->cms[$modnumber];
                 if (!$mod->uservisible) {
@@ -139,19 +127,13 @@ class block_social_activities extends block_list {
                             '<img style="height:16px; width:80px; border:0px" src="'.$OUTPUT->pix_url('movehere') . '" alt="'.$strmovehere.'" /></a>';
                         $this->content->icons[] = '';
                     }
-                    $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
-                    $instancename = $mod->get_formatted_name();
-
-                    $linkcss = $mod->visible ? '' : ' class="dimmed" ';
-
-                    if (!($url = $mod->url)) {
+                    if (!$mod->url) {
+                        $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
                         $this->content->items[] = $content . $editbuttons;
                         $this->content->icons[] = '';
                     } else {
-                        //Accessibility: incidental image - should be empty Alt text
-                        $icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />&nbsp;';
-                        $this->content->items[] = '<a title="' . $mod->modfullname . '" ' . $linkcss . ' ' . $mod->extra .
-                            ' href="' . $url . '">' . $icon . $instancename . '</a>' . $editbuttons;
+                        $this->content->items[] = html_writer::div($courserenderer->course_section_cm_name($mod), 'activity') .
+                            $editbuttons;
                     }
                 }
             }
diff --git a/blocks/social_activities/tests/behat/edit_activities.feature b/blocks/social_activities/tests/behat/edit_activities.feature
new file mode 100644 (file)
index 0000000..1db7a7f
--- /dev/null
@@ -0,0 +1,31 @@
+@block @block_social_activities
+Feature: Edit activities in social activities block
+  In order to use social activities block
+  As a teacher
+  I need to add and edit activities there
+
+  @javascript
+  Scenario: Edit name of acitivity in-place in social activities block
+    Given the following "courses" exist:
+      | fullname | shortname | format |
+      | Course 1 | C1        | social |
+    And the following "users" exist:
+      | username | firstname | lastname |
+      | user1 | User | One |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | user1 | C1 | editingteacher |
+    Given I log in as "user1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I set the field "Add an activity to section 'section 0'" to "Forum"
+    And I set the field "Forum name" to "My forum name"
+    And I press "Save and return to course"
+    And I click on "Edit title" "link" in the "//.[contains(@class,'block_social_activities')]//li[contains(.,'My forum name')]" "xpath_element"
+    And I set the field "New name for activity My forum name" to "New forum name"
+    And I press key "13" in the field "New name for activity My forum name"
+    Then I should not see "My forum name" in the "Social activities" "block"
+    And I should see "New forum name"
+    And I follow "New forum name"
+    And I should not see "My forum name"
+    And I should see "New forum name"
index a10250d..2d8143c 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
 
 /**
  * Blocks management steps definitions.
index 70e57b2..9dc24a2 100644 (file)
@@ -26,7 +26,7 @@
 // NOTE: no MOODLE_INTERNAL used, this file may be required by behat before including /config.php.
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
 use Behat\Gherkin\Node\TableNode as TableNode;
 
 /**
diff --git a/cohort/classes/output/cohortidnumber.php b/cohort/classes/output/cohortidnumber.php
new file mode 100644 (file)
index 0000000..5628917
--- /dev/null
@@ -0,0 +1,70 @@
+<?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/>.
+
+/**
+ * Contains class core_cohort\output\cohortidnumber
+ *
+ * @package   core_cohort
+ * @copyright 2016 Marina Glancy
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_cohort\output;
+
+use lang_string;
+
+/**
+ * Class to prepare a cohort idnumber for display.
+ *
+ * @package   core_cohort
+ * @copyright 2016 Marina Glancy
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cohortidnumber extends \core\output\inplace_editable {
+    /**
+     * Constructor.
+     *
+     * @param stdClass $cohort
+     */
+    public function __construct($cohort) {
+        $cohortcontext = \context::instance_by_id($cohort->contextid);
+        $editable = has_capability('moodle/cohort:manage', $cohortcontext);
+        $displayvalue = s($cohort->idnumber); // All idnumbers are plain text.
+        parent::__construct('core_cohort', 'cohortidnumber', $cohort->id, $editable,
+            $displayvalue,
+            $cohort->idnumber,
+            new lang_string('editcohortidnumber', 'cohort'),
+            new lang_string('newidnumberfor', 'cohort', $displayvalue));
+    }
+
+    /**
+     * Updates cohort name and returns instance of this object
+     *
+     * @param int $cohortid
+     * @param string $newvalue
+     * @return static
+     */
+    public static function update($cohortid, $newvalue) {
+        global $DB;
+        $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
+        $cohortcontext = \context::instance_by_id($cohort->contextid);
+        require_capability('moodle/cohort:manage', $cohortcontext);
+        $record = (object)array('id' => $cohort->id, 'idnumber' => $newvalue, 'contextid' => $cohort->contextid);
+        cohort_update_cohort($record);
+        $cohort->idnumber = $newvalue;
+        return new static($cohort);
+    }
+}
diff --git a/cohort/classes/output/cohortname.php b/cohort/classes/output/cohortname.php
new file mode 100644 (file)
index 0000000..7c74809
--- /dev/null
@@ -0,0 +1,73 @@
+<?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/>.
+
+/**
+ * Contains class core_cohort\output\cohortname
+ *
+ * @package   core_cohort
+ * @copyright 2016 Marina Glancy
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_cohort\output;
+
+use lang_string;
+
+/**
+ * Class to prepare a cohort name for display.
+ *
+ * @package   core_cohort
+ * @copyright 2016 Marina Glancy
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cohortname extends \core\output\inplace_editable {
+    /**
+     * Constructor.
+     *
+     * @param stdClass $cohort
+     */
+    public function __construct($cohort) {
+        $cohortcontext = \context::instance_by_id($cohort->contextid);
+        $editable = has_capability('moodle/cohort:manage', $cohortcontext);
+        $displayvalue = format_string($cohort->name, true, array('context' => $cohortcontext));
+        parent::__construct('core_cohort', 'cohortname', $cohort->id, $editable,
+            $displayvalue,
+            $cohort->name,
+            new lang_string('editcohortname', 'cohort'),
+            new lang_string('newnamefor', 'cohort', $displayvalue));
+    }
+
+    /**
+     * Updates cohort name and returns instance of this object
+     *
+     * @param int $cohortid
+     * @param string $newvalue
+     * @return static
+     */
+    public static function update($cohortid, $newvalue) {
+        global $DB;
+        $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
+        $cohortcontext = \context::instance_by_id($cohort->contextid);
+        require_capability('moodle/cohort:manage', $cohortcontext);
+        $newvalue = clean_param($newvalue, PARAM_TEXT);
+        if (strval($newvalue) !== '') {
+            $record = (object)array('id' => $cohort->id, 'name' => $newvalue, 'contextid' => $cohort->contextid);
+            cohort_update_cohort($record);
+            $cohort->name = $newvalue;
+        }
+        return new static($cohort);
+    }
+}
index 923e464..7352bb8 100644 (file)
@@ -132,8 +132,10 @@ foreach($cohorts['cohorts'] as $cohort) {
             $line[] = $cohortcontext->get_context_name(false);
         }
     }
-    $line[] = format_string($cohort->name);
-    $line[] = s($cohort->idnumber); // All idnumbers are plain text.
+    $tmpl = new \core_cohort\output\cohortname($cohort);
+    $line[] = $OUTPUT->render_from_template('core/inplace_editable', $tmpl->export_for_template($OUTPUT));
+    $tmpl = new \core_cohort\output\cohortidnumber($cohort);
+    $line[] = $OUTPUT->render_from_template('core/inplace_editable', $tmpl->export_for_template($OUTPUT));
     $line[] = format_text($cohort->description, $cohort->descriptionformat);
 
     $line[] = $DB->count_records('cohort_members', array('cohortid'=>$cohort->id));
index 5c51578..de015a4 100644 (file)
@@ -516,3 +516,19 @@ function cohort_edit_controls(context $context, moodle_url $currenturl) {
     }
     return null;
 }
+
+/**
+ * Implements callback inplace_editable() allowing to edit values in-place
+ *
+ * @param string $itemtype
+ * @param int $itemid
+ * @param mixed $newvalue
+ * @return \core\output\inplace_editable
+ */
+function core_cohort_inplace_editable($itemtype, $itemid, $newvalue) {
+    if ($itemtype === 'cohortname') {
+        return \core_cohort\output\cohortname::update($itemid, $newvalue);
+    } else if ($itemtype === 'cohortidnumber') {
+        return \core_cohort\output\cohortidnumber::update($itemid, $newvalue);
+    }
+}
index f6b5512..e6983b2 100644 (file)
@@ -56,3 +56,14 @@ Feature: Add cohorts of users
     And the "Current users" select box should contain "Third User (third@example.com)"
     And the "Current users" select box should contain "Forth User (forth@example.com)"
     And the "Current users" select box should not contain "First User (first@example.com)"
+
+  @javascript
+  Scenario: Edit cohort name in-place
+    When I follow "Cohorts"
+    And I click on "Edit cohort name" "link" in the "Test cohort name" "table_row"
+    And I set the field "New name for cohort Test cohort name" to "Students cohort"
+    And I press key "13" in the field "New name for cohort Test cohort name"
+    Then I should not see "Test cohort name"
+    And I should see "Students cohort"
+    And I follow "Cohorts"
+    And I should see "Students cohort"
index 8c860b5..e1fd621 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
 
 /**
  * Steps definitions for cohort actions.
index f1b4215..0bd4e80 100644 (file)
@@ -27,8 +27,8 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given,
-    Behat\Behat\Context\Step\Then,
+use Moodle\BehatExtension\Context\Step\Given,
+    Moodle\BehatExtension\Context\Step\Then,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 
 /**
index fdff4bd..f578d7a 100644 (file)
@@ -2,6 +2,6 @@
     "require-dev": {
         "phpunit/phpunit": "4.8.*",
         "phpunit/dbUnit": "1.4.*",
-        "moodlehq/behat-extension": "1.31.0"
+        "moodlehq/behat-extension": "3.31.0"
     }
 }
index c97fa52..7ce9df0 100644 (file)
@@ -4,37 +4,41 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "a957f7332dd1d221be96fb356cb9f03d",
-    "content-hash": "463a75022982e6a64bd9fc0513d9b44c",
+    "hash": "769fa23c4b31f60c9fb82d5b23171e0f",
+    "content-hash": "5fca4c69d043cb1f985fc08cd82a64f8",
     "packages": [],
     "packages-dev": [
         {
             "name": "behat/behat",
-            "version": "v2.5.5",
+            "version": "v3.0.15",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/Behat.git",
-                "reference": "c1e48826b84669c97a1efa78459aedfdcdcf2120"
+                "reference": "b35ae3d45332d80c532af69cc36f780a9397a996"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Behat/zipball/c1e48826b84669c97a1efa78459aedfdcdcf2120",
-                "reference": "c1e48826b84669c97a1efa78459aedfdcdcf2120",
+                "url": "https://api.github.com/repos/Behat/Behat/zipball/b35ae3d45332d80c532af69cc36f780a9397a996",
+                "reference": "b35ae3d45332d80c532af69cc36f780a9397a996",
                 "shasum": ""
             },
             "require": {
-                "behat/gherkin": "~2.3.0",
-                "php": ">=5.3.1",
+                "behat/gherkin": "~4.3",
+                "behat/transliterator": "~1.0",
+                "ext-mbstring": "*",
+                "php": ">=5.3.3",
+                "symfony/class-loader": "~2.1",
                 "symfony/config": "~2.3",
-                "symfony/console": "~2.0",
-                "symfony/dependency-injection": "~2.0",
-                "symfony/event-dispatcher": "~2.0",
-                "symfony/finder": "~2.0",
+                "symfony/console": "~2.1",
+                "symfony/dependency-injection": "~2.1",
+                "symfony/event-dispatcher": "~2.1",
                 "symfony/translation": "~2.3",
-                "symfony/yaml": "~2.0"
+                "symfony/yaml": "~2.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "~3.7.19"
+                "phpspec/prophecy-phpunit": "~1.0",
+                "phpunit/phpunit": "~4.0",
+                "symfony/process": "~2.1"
             },
             "suggest": {
                 "behat/mink-extension": "for integration with Mink testing framework",
                 "bin/behat"
             ],
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0.x-dev"
+                }
+            },
             "autoload": {
                 "psr-0": {
-                    "Behat\\Behat": "src/"
+                    "Behat\\Behat": "src/",
+                    "Behat\\Testwork": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             "description": "Scenario-oriented BDD framework for PHP 5.3",
             "homepage": "http://behat.org/",
             "keywords": [
+                "Agile",
                 "BDD",
-                "Behat",
-                "Symfony2"
+                "ScenarioBDD",
+                "Scrum",
+                "StoryBDD",
+                "User story",
+                "business",
+                "development",
+                "documentation",
+                "examples",
+                "symfony",
+                "testing"
             ],
-            "time": "2015-06-01 09:37:55"
+            "time": "2015-02-22 14:10:33"
         },
         {
             "name": "behat/gherkin",
-            "version": "v2.3.5",
+            "version": "v4.4.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/Gherkin.git",
-                "reference": "2b33963da5525400573560c173ab5c9c057e1852"
+                "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Gherkin/zipball/2b33963da5525400573560c173ab5c9c057e1852",
-                "reference": "2b33963da5525400573560c173ab5c9c057e1852",
+                "url": "https://api.github.com/repos/Behat/Gherkin/zipball/1576b485c0f92ef6d27da9c4bbfc57ee30cf6911",
+                "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.1",
-                "symfony/finder": "~2.0"
+                "php": ">=5.3.1"
             },
             "require-dev": {
-                "symfony/config": "~2.0",
-                "symfony/translation": "~2.0",
-                "symfony/yaml": "~2.0"
+                "phpunit/phpunit": "~4.0",
+                "symfony/yaml": "~2.1"
             },
             "suggest": {
-                "symfony/config": "If you want to use Config component to manage resources",
-                "symfony/translation": "If you want to use Symfony2 translations adapter",
                 "symfony/yaml": "If you want to parse features, represented in YAML files"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-develop": "2.2-dev"
+                    "dev-master": "4.4-dev"
                 }
             },
             "autoload": {
             "keywords": [
                 "BDD",
                 "Behat",
+                "Cucumber",
                 "DSL",
-                "Symfony2",
+                "gherkin",
                 "parser"
             ],
-            "time": "2013-10-15 11:22:17"
+            "time": "2015-12-30 14:47:00"
         },
         {
             "name": "behat/mink",
-            "version": "v1.5.0",
+            "version": "v1.7.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/minkphp/Mink.git",
-                "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe"
+                "reference": "6c129030ec2cc029905cf969a56ca8f087b2dfdf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/minkphp/Mink/zipball/0769e6d9726c140a54dbf827a438c0f9912749fe",
-                "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe",
+                "url": "https://api.github.com/repos/minkphp/Mink/zipball/6c129030ec2cc029905cf969a56ca8f087b2dfdf",
+                "reference": "6c129030ec2cc029905cf969a56ca8f087b2dfdf",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.1",
-                "symfony/css-selector": "~2.0"
+                "symfony/css-selector": "~2.1"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "~2.7"
             },
             "suggest": {
                 "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)",
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-develop": "1.5.x-dev"
+                    "dev-master": "1.7.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Behat\\Mink": "src/"
+                "psr-4": {
+                    "Behat\\Mink\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                     "homepage": "http://everzet.com"
                 }
             ],
-            "description": "Web acceptance testing framework for PHP 5.3",
+            "description": "Browser controller/emulator abstraction for PHP",
             "homepage": "http://mink.behat.org/",
             "keywords": [
                 "browser",
                 "testing",
                 "web"
             ],
-            "time": "2013-04-13 23:39:27"
+            "time": "2015-09-20 20:24:03"
         },
         {
             "name": "behat/mink-browserkit-driver",
-            "version": "v1.1.0",
+            "version": "v1.3.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/minkphp/MinkBrowserKitDriver.git",
-                "reference": "63960c8fcad4529faad1ff33e950217980baa64c"
+                "reference": "2650f5420e713e3807c7f09a07370a4f48367bf9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/63960c8fcad4529faad1ff33e950217980baa64c",
-                "reference": "63960c8fcad4529faad1ff33e950217980baa64c",
+                "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/2650f5420e713e3807c7f09a07370a4f48367bf9",
+                "reference": "2650f5420e713e3807c7f09a07370a4f48367bf9",
                 "shasum": ""
             },
             "require": {
-                "behat/mink": "~1.5.0",
-                "php": ">=5.3.1",
-                "symfony/browser-kit": "~2.0",
-                "symfony/dom-crawler": "~2.0"
+                "behat/mink": "~1.7@dev",
+                "php": ">=5.3.6",
+                "symfony/browser-kit": "~2.3|~3.0",
+                "symfony/dom-crawler": "~2.3|~3.0"
             },
             "require-dev": {
-                "silex/silex": "@dev"
+                "silex/silex": "~1.2",
+                "symfony/phpunit-bridge": "~2.7|~3.0"
             },
             "type": "mink-driver",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1.x-dev"
+                    "dev-master": "1.3.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Behat\\Mink\\Driver": "src/"
+                "psr-4": {
+                    "Behat\\Mink\\Driver\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 "browser",
                 "testing"
             ],
-            "time": "2013-04-13 23:46:30"
+            "time": "2016-01-19 16:59:07"
         },
         {
             "name": "behat/mink-extension",
-            "version": "v1.3.3",
+            "version": "v2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/MinkExtension.git",
-                "reference": "b885b9407cba50a954f72c69ed1b2f8d3bc694f8"
+                "reference": "5b4bda64ff456104564317e212c823e45cad9d59"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/b885b9407cba50a954f72c69ed1b2f8d3bc694f8",
-                "reference": "b885b9407cba50a954f72c69ed1b2f8d3bc694f8",
+                "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/5b4bda64ff456104564317e212c823e45cad9d59",
+                "reference": "5b4bda64ff456104564317e212c823e45cad9d59",
                 "shasum": ""
             },
             "require": {
-                "behat/behat": "~2.5.0",
+                "behat/behat": "~3.0,>=3.0.5",
                 "behat/mink": "~1.5",
                 "php": ">=5.3.2",
-                "symfony/config": "~2.2"
+                "symfony/config": "~2.2|~3.0"
             },
             "require-dev": {
-                "behat/mink-goutte-driver": "~1.0",
-                "fabpot/goutte": "~1.0"
+                "behat/mink-goutte-driver": "~1.1",
+                "phpspec/phpspec": "~2.0"
             },
             "type": "behat-extension",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.1.x-dev"
+                }
+            },
             "autoload": {
                 "psr-0": {
                     "Behat\\MinkExtension": "src/"
                 "MIT"
             ],
             "authors": [
+                {
+                    "name": "Christophe Coevoet",
+                    "email": "stof@notk.org"
+                },
                 {
                     "name": "Konstantin Kudryashov",
-                    "email": "ever.zet@gmail.com",
-                    "homepage": "http://everzet.com"
+                    "email": "ever.zet@gmail.com"
                 }
             ],
             "description": "Mink extension for Behat",
-            "homepage": "http://mink.behat.org",
+            "homepage": "http://extensions.behat.org/mink",
             "keywords": [
                 "browser",
                 "gui",
                 "test",
                 "web"
             ],
-            "time": "2014-05-15 19:27:39"
+            "time": "2016-02-15 07:55:18"
         },
         {
             "name": "behat/mink-goutte-driver",
-            "version": "v1.0.9",
+            "version": "v1.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/minkphp/MinkGoutteDriver.git",
-                "reference": "fa1b073b48761464feb0b05e6825da44b20118d8"
+                "reference": "c8e254f127d6f2242b994afd4339fb62d471df3f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/fa1b073b48761464feb0b05e6825da44b20118d8",
-                "reference": "fa1b073b48761464feb0b05e6825da44b20118d8",
+                "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/c8e254f127d6f2242b994afd4339fb62d471df3f",
+                "reference": "c8e254f127d6f2242b994afd4339fb62d471df3f",
                 "shasum": ""
             },
             "require": {
-                "behat/mink-browserkit-driver": ">=1.0.5,<1.2.0",
-                "fabpot/goutte": "~1.0.1",
+                "behat/mink": "~1.6@dev",
+                "behat/mink-browserkit-driver": "~1.2@dev",
+                "fabpot/goutte": "~1.0.4|~2.0|~3.1",
                 "php": ">=5.3.1"
             },
+            "require-dev": {
+                "symfony/phpunit-bridge": "~2.7"
+            },
             "type": "mink-driver",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "1.2.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Behat\\Mink\\Driver": "src/"
+                "psr-4": {
+                    "Behat\\Mink\\Driver\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 "headless",
                 "testing"
             ],
-            "time": "2013-07-03 18:43:54"
+            "time": "2015-09-21 21:31:11"
         },
         {
             "name": "behat/mink-selenium2-driver",
-            "version": "v1.1.1",
+            "version": "v1.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/minkphp/MinkSelenium2Driver.git",
-                "reference": "bcf1b537de37db6db0822d9e7bd97e600fd7a476"
+                "reference": "bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/bcf1b537de37db6db0822d9e7bd97e600fd7a476",
-                "reference": "bcf1b537de37db6db0822d9e7bd97e600fd7a476",
+                "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9",
+                "reference": "bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9",
                 "shasum": ""
             },
             "require": {
-                "behat/mink": "~1.5.0",
-                "instaclick/php-webdriver": "~1.0.12",
+                "behat/mink": "~1.7@dev",
+                "instaclick/php-webdriver": "~1.1",
                 "php": ">=5.3.1"
             },
+            "require-dev": {
+                "symfony/phpunit-bridge": "~2.7"
+            },
             "type": "mink-driver",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1.x-dev"
+                    "dev-master": "1.3.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Behat\\Mink\\Driver": "src/"
+                "psr-4": {
+                    "Behat\\Mink\\Driver\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 "testing",
                 "webdriver"
             ],
-            "time": "2013-06-02 19:09:45"
+            "time": "2015-09-21 21:02:54"
+        },
+        {
+            "name": "behat/transliterator",
+            "version": "v1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Behat/Transliterator.git",
+                "reference": "868e05be3a9f25ba6424c2dd4849567f50715003"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Behat/Transliterator/zipball/868e05be3a9f25ba6424c2dd4849567f50715003",
+                "reference": "868e05be3a9f25ba6424c2dd4849567f50715003",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Behat\\Transliterator": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Artistic-1.0"
+            ],
+            "description": "String transliterator",
+            "keywords": [
+                "i18n",
+                "slug",
+                "transliterator"
+            ],
+            "time": "2015-09-28 16:26:35"
         },
         {
             "name": "doctrine/instantiator",
         },
         {
             "name": "fabpot/goutte",
-            "version": "v1.0.7",
+            "version": "v2.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/FriendsOfPHP/Goutte.git",
-                "reference": "794b196e76bdd37b5155cdecbad311f0a3b07625"
+                "reference": "0ad3ee6dc2d0aaa832a80041a1e09bf394e99802"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/794b196e76bdd37b5155cdecbad311f0a3b07625",
-                "reference": "794b196e76bdd37b5155cdecbad311f0a3b07625",
+                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/0ad3ee6dc2d0aaa832a80041a1e09bf394e99802",
+                "reference": "0ad3ee6dc2d0aaa832a80041a1e09bf394e99802",
                 "shasum": ""
             },
             "require": {
-                "ext-curl": "*",
-                "guzzle/http": "~3.1",
-                "php": ">=5.3.0",
+                "guzzlehttp/guzzle": ">=4,<6",
+                "php": ">=5.4.0",
                 "symfony/browser-kit": "~2.1",
                 "symfony/css-selector": "~2.1",
-                "symfony/dom-crawler": "~2.1",
-                "symfony/finder": "~2.1",
-                "symfony/process": "~2.1"
-            },
-            "require-dev": {
-                "guzzle/plugin-history": "~3.1",
-                "guzzle/plugin-mock": "~3.1"
+                "symfony/dom-crawler": "~2.1"
             },
             "type": "application",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0-dev"
+                    "dev-master": "2.0-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Goutte": "."
+                "psr-4": {
+                    "Goutte\\": "Goutte"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 }
             ],
             "description": "A simple PHP Web Scraper",
-            "homepage": "https://github.com/fabpot/Goutte",
+            "homepage": "https://github.com/FriendsOfPHP/Goutte",
             "keywords": [
                 "scraper"
             ],
-            "time": "2014-10-09 15:52:51"
+            "time": "2015-05-05 21:14:57"
         },
         {
             "name": "guzzlehttp/guzzle",
-            "version": "v3.8.1",
+            "version": "5.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba"
+                "reference": "f3c8c22471cb55475105c14769644a49c3262b93"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba",
-                "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f3c8c22471cb55475105c14769644a49c3262b93",
+                "reference": "f3c8c22471cb55475105c14769644a49c3262b93",
                 "shasum": ""
             },
             "require": {
-                "ext-curl": "*",
-                "php": ">=5.3.3",
-                "symfony/event-dispatcher": ">=2.1"
-            },
-            "replace": {
-                "guzzle/batch": "self.version",
-                "guzzle/cache": "self.version",
-                "guzzle/common": "self.version",
-                "guzzle/http": "self.version",
-                "guzzle/inflection": "self.version",
-                "guzzle/iterator": "self.version",
-                "guzzle/log": "self.version",
-                "guzzle/parser": "self.version",
-                "guzzle/plugin": "self.version",
-                "guzzle/plugin-async": "self.version",
-                "guzzle/plugin-backoff": "self.version",
-                "guzzle/plugin-cache": "self.version",
-                "guzzle/plugin-cookie": "self.version",
-                "guzzle/plugin-curlauth": "self.version",
-                "guzzle/plugin-error-response": "self.version",
-                "guzzle/plugin-history": "self.version",
-                "guzzle/plugin-log": "self.version",
-                "guzzle/plugin-md5": "self.version",
-                "guzzle/plugin-mock": "self.version",
-                "guzzle/plugin-oauth": "self.version",
-                "guzzle/service": "self.version",
-                "guzzle/stream": "self.version"
+                "guzzlehttp/ringphp": "^1.1",
+                "php": ">=5.4.0"
             },
             "require-dev": {
-                "doctrine/cache": "*",
-                "monolog/monolog": "1.*",
-                "phpunit/phpunit": "3.7.*",
-                "psr/log": "1.0.*",
-                "symfony/class-loader": "*",
-                "zendframework/zend-cache": "<2.3",
-                "zendframework/zend-log": "<2.3"
+                "ext-curl": "*",
+                "phpunit/phpunit": "^4.0",
+                "psr/log": "^1.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.8-dev"
+                    "dev-master": "5.0-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Guzzle": "src/",
-                    "Guzzle\\Tests": "tests/"
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                     "name": "Michael Dowling",
                     "email": "mtdowling@gmail.com",
                     "homepage": "https://github.com/mtdowling"
-                },
-                {
-                    "name": "Guzzle Community",
-                    "homepage": "https://github.com/guzzle/guzzle/contributors"
                 }
             ],
             "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
                 "rest",
                 "web service"
             ],
-            "time": "2014-01-28 22:29:15"
+            "time": "2015-05-20 03:47:55"
+        },
+        {
+            "name": "guzzlehttp/ringphp",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/RingPHP.git",
+                "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/dbbb91d7f6c191e5e405e900e3102ac7f261bc0b",
+                "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/streams": "~3.0",
+                "php": ">=5.4.0",
+                "react/promise": "~2.0"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "phpunit/phpunit": "~4.0"
+            },
+            "suggest": {
+                "ext-curl": "Guzzle will use specific adapters if cURL is present"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Ring\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.",
+            "time": "2015-05-20 03:37:09"
+        },
+        {
+            "name": "guzzlehttp/streams",
+            "version": "3.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/streams.git",
+                "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
+                "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Stream\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Provides a simple abstraction over streams of data",
+            "homepage": "http://guzzlephp.org/",
+            "keywords": [
+                "Guzzle",
+                "stream"
+            ],
+            "time": "2014-10-12 19:18:40"
         },
         {
             "name": "instaclick/php-webdriver",
-            "version": "1.0.17",
+            "version": "1.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/instaclick/php-webdriver.git",
-                "reference": "47a6019553a7a5b42d35493276ffc2c9252c53d5"
+                "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/47a6019553a7a5b42d35493276ffc2c9252c53d5",
-                "reference": "47a6019553a7a5b42d35493276ffc2c9252c53d5",
+                "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/0c20707dcf30a32728fd6bdeeab996c887fdb2fb",
+                "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb",
                 "shasum": ""
             },
             "require": {
                 "ext-curl": "*",
                 "php": ">=5.3.2"
             },
-            "bin": [
-                "bin/webunit"
-            ],
+            "require-dev": {
+                "satooshi/php-coveralls": "dev-master"
+            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "1.4.x-dev"
                 }
             },
             "autoload": {
                 {
                     "name": "Anthon Pang",
                     "email": "apang@softwaredevelopment.ca",
-                    "role": "developer"
+                    "role": "Fork Maintainer"
                 }
             ],
             "description": "PHP WebDriver for Selenium 2",
                 "webdriver",
                 "webtest"
             ],
-            "time": "2013-10-04 15:03:51"
+            "time": "2015-06-15 20:19:33"
         },
         {
             "name": "moodlehq/behat-extension",
-            "version": "v1.31.0",
+            "version": "v3.31.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/moodlehq/moodle-behat-extension.git",
-                "reference": "b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b"
+                "reference": "d985e9da29914b0da90d61c47aadc455586eeee5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b",
-                "reference": "b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b",
+                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/d985e9da29914b0da90d61c47aadc455586eeee5",
+                "reference": "d985e9da29914b0da90d61c47aadc455586eeee5",
                 "shasum": ""
             },
             "require": {
-                "behat/behat": "2.5.5",
-                "behat/mink": "1.5.0",
-                "behat/mink-extension": "1.3.3",
-                "behat/mink-goutte-driver": "1.0.9",
-                "behat/mink-selenium2-driver": "1.1.1",
-                "guzzlehttp/guzzle": "~3.1",
+                "behat/behat": "3.0.*",
+                "behat/mink": "~1.7",
+                "behat/mink-extension": "~2.1",
+                "behat/mink-goutte-driver": "~1.2",
+                "behat/mink-selenium2-driver": "~1.3",
                 "php": ">=5.4.4",
-                "symfony/browser-kit": "2.7.5",
-                "symfony/css-selector": "2.7.5",
-                "symfony/dom-crawler": "2.7.5",
-                "symfony/filesystem": "2.7.5",
-                "symfony/finder": "2.7.5"
+                "symfony/process": "2.8.*"
             },
             "type": "library",
             "autoload": {
                 "Behat",
                 "moodle"
             ],
-            "time": "2016-01-05 02:55:24"
+            "time": "2016-03-04 07:15:40"
         },
         {
             "name": "phpdocumentor/reflection-docblock",
         },
         {
             "name": "phpspec/prophecy",
-            "version": "v1.5.0",
+            "version": "v1.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7"
+                "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7",
-                "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
+                "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
                 "shasum": ""
             },
             "require": {
                 "doctrine/instantiator": "^1.0.2",
+                "php": "^5.3|^7.0",
                 "phpdocumentor/reflection-docblock": "~2.0",
-                "sebastian/comparator": "~1.1"
+                "sebastian/comparator": "~1.1",
+                "sebastian/recursion-context": "~1.0"
             },
             "require-dev": {
                 "phpspec/phpspec": "~2.0"
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.4.x-dev"
+                    "dev-master": "1.5.x-dev"
                 }
             },
             "autoload": {
                 "spy",
                 "stub"
             ],
-            "time": "2015-08-13 10:07:40"
+            "time": "2016-02-15 07:46:21"
         },
         {
             "name": "phpunit/dbunit",
         },
         {
             "name": "phpunit/phpunit",
-            "version": "4.8.21",
+            "version": "4.8.23",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "ea76b17bced0500a28098626b84eda12dbcf119c"
+                "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c",
-                "reference": "ea76b17bced0500a28098626b84eda12dbcf119c",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483",
+                "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483",
                 "shasum": ""
             },
             "require": {
                 "testing",
                 "xunit"
             ],
-            "time": "2015-12-12 07:45:58"
+            "time": "2016-02-11 14:56:33"
         },
         {
             "name": "phpunit/phpunit-mock-objects",
             ],
             "time": "2015-10-02 06:51:40"
         },
+        {
+            "name": "react/promise",
+            "version": "v2.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/reactphp/promise.git",
+                "reference": "3aacad8bf10c7d83e6fa2089d413529888c2bedf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/reactphp/promise/zipball/3aacad8bf10c7d83e6fa2089d413529888c2bedf",
+                "reference": "3aacad8bf10c7d83e6fa2089d413529888c2bedf",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "React\\Promise\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jan Sorgalla",
+                    "email": "jsorgalla@gmail.com"
+                }
+            ],
+            "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+            "time": "2016-02-26 19:09:02"
+        },
         {
             "name": "sebastian/comparator",
             "version": "1.2.0",
         },
         {
             "name": "sebastian/environment",
-            "version": "1.3.3",
+            "version": "1.3.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/environment.git",
-                "reference": "6e7133793a8e5a5714a551a8324337374be209df"
+                "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df",
-                "reference": "6e7133793a8e5a5714a551a8324337374be209df",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
+                "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
                 "shasum": ""
             },
             "require": {
                 "environment",
                 "hhvm"
             ],
-            "time": "2015-12-02 08:37:27"
+            "time": "2016-02-26 18:40:46"
         },
         {
             "name": "sebastian/exporter",
         },
         {
             "name": "symfony/browser-kit",
-            "version": "v2.7.5",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/browser-kit.git",
-                "reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4"
+                "reference": "6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
-                "reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
+                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2",
+                "reference": "6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.9",
-                "symfony/dom-crawler": "~2.0,>=2.0.5"
+                "symfony/dom-crawler": "~2.0,>=2.0.5|~3.0.0"
             },
             "require-dev": {
-                "symfony/css-selector": "~2.0,>=2.0.5",
-                "symfony/phpunit-bridge": "~2.7",
-                "symfony/process": "~2.0,>=2.0.5"
+                "symfony/css-selector": "~2.0,>=2.0.5|~3.0.0",
+                "symfony/process": "~2.3.34|~2.7,>=2.7.6|~3.0.0"
             },
             "suggest": {
                 "symfony/process": ""
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.7-dev"
+                    "dev-master": "2.8-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
                     "Symfony\\Component\\BrowserKit\\": ""
-                }
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
             ],
             "description": "Symfony BrowserKit Component",
             "homepage": "https://symfony.com",
-            "time": "2015-09-06 08:36:38"
+            "time": "2016-01-27 11:34:40"
+        },
+        {
+            "name": "symfony/class-loader",
+            "version": "v2.8.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/class-loader.git",
+                "reference": "517ab0554b6a5744d04480cb06873ffbd9442d73"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/class-loader/zipball/517ab0554b6a5744d04480cb06873ffbd9442d73",
+                "reference": "517ab0554b6a5744d04480cb06873ffbd9442d73",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.9",
+                "symfony/polyfill-apcu": "~1.1"
+            },
+            "require-dev": {
+                "symfony/finder": "~2.0,>=2.0.5|~3.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.8-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\ClassLoader\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony ClassLoader Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-01-30 15:58:35"
         },
         {
             "name": "symfony/config",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/config.git",
-                "reference": "17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2"
+                "reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2",
-                "reference": "17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2",
+                "url": "https://api.github.com/repos/symfony/config/zipball/0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
+                "reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.9",
                 "symfony/filesystem": "~2.3|~3.0.0"
             },
+            "suggest": {
+                "symfony/yaml": "To use the yaml reference dumper"
+            },
             "type": "library",
             "extra": {
                 "branch-alias": {
             ],
             "description": "Symfony Config Component",
             "homepage": "https://symfony.com",
-            "time": "2015-12-26 13:37:56"
+            "time": "2016-02-22 16:12:45"
         },
         {
             "name": "symfony/console",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a"
+                "reference": "56cc5caf051189720b8de974e4746090aaa10d44"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/2e06a5ccb19dcf9b89f1c6a677a39a8df773635a",
-                "reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a",
+                "url": "https://api.github.com/repos/symfony/console/zipball/56cc5caf051189720b8de974e4746090aaa10d44",
+                "reference": "56cc5caf051189720b8de974e4746090aaa10d44",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2015-12-22 10:25:57"
+            "time": "2016-02-28 16:20:50"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v2.7.5",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "abe19cc0429a06be0c133056d1f9859854860970"
+                "reference": "8d83ff9777cdbd83e7f90d9c48f4729823791a5e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/abe19cc0429a06be0c133056d1f9859854860970",
-                "reference": "abe19cc0429a06be0c133056d1f9859854860970",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/8d83ff9777cdbd83e7f90d9c48f4729823791a5e",
+                "reference": "8d83ff9777cdbd83e7f90d9c48f4729823791a5e",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.9"
             },
-            "require-dev": {
-                "symfony/phpunit-bridge": "~2.7"
-            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.7-dev"
+                    "dev-master": "2.8-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
                     "Symfony\\Component\\CssSelector\\": ""
-                }
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
             ],
             "description": "Symfony CssSelector Component",
             "homepage": "https://symfony.com",
-            "time": "2015-09-22 13:49:29"
+            "time": "2016-01-27 05:14:19"
         },
         {
             "name": "symfony/dependency-injection",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dependency-injection.git",
-                "reference": "c5086d186f538c2711b9af6f727be7b0446979cd"
+                "reference": "62251761a7615435b22ccf562384c588b431be44"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/c5086d186f538c2711b9af6f727be7b0446979cd",
-                "reference": "c5086d186f538c2711b9af6f727be7b0446979cd",
+                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/62251761a7615435b22ccf562384c588b431be44",
+                "reference": "62251761a7615435b22ccf562384c588b431be44",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony DependencyInjection Component",
             "homepage": "https://symfony.com",
-            "time": "2015-12-26 13:37:56"
+            "time": "2016-02-28 16:34:46"
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v2.7.5",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "2e185ca136399f902b948694987e62c80099c052"
+                "reference": "e1a4b4c83f5ee6f5902f1d53035e3718909a0c11"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2e185ca136399f902b948694987e62c80099c052",
-                "reference": "2e185ca136399f902b948694987e62c80099c052",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/e1a4b4c83f5ee6f5902f1d53035e3718909a0c11",
+                "reference": "e1a4b4c83f5ee6f5902f1d53035e3718909a0c11",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.9"
+                "php": ">=5.3.9",
+                "symfony/polyfill-mbstring": "~1.0"
             },
             "require-dev": {
-                "symfony/css-selector": "~2.3",
-                "symfony/phpunit-bridge": "~2.7"
+                "symfony/css-selector": "~2.8|~3.0.0"
             },
             "suggest": {
                 "symfony/css-selector": ""
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.7-dev"
+                    "dev-master": "2.8-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
                     "Symfony\\Component\\DomCrawler\\": ""
-                }
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
             ],
             "description": "Symfony DomCrawler Component",
             "homepage": "https://symfony.com",
-            "time": "2015-09-20 21:13:58"
+            "time": "2016-02-28 16:20:50"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc"
+                "reference": "78c468665c9568c3faaa9c416a7134308f2d85c3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc",
-                "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/78c468665c9568c3faaa9c416a7134308f2d85c3",
+                "reference": "78c468665c9568c3faaa9c416a7134308f2d85c3",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2015-10-30 20:15:42"
+            "time": "2016-01-27 05:14:19"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v2.7.5",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab"
+                "reference": "65cb36b6539b1d446527d60457248f30d045464d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
-                "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/65cb36b6539b1d446527d60457248f30d045464d",
+                "reference": "65cb36b6539b1d446527d60457248f30d045464d",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.9"
             },
-            "require-dev": {
-                "symfony/phpunit-bridge": "~2.7"
-            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.7-dev"
+                    "dev-master": "2.8-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
                     "Symfony\\Component\\Filesystem\\": ""
-                }
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2015-09-09 17:42:36"
+            "time": "2016-02-22 15:02:30"
         },
         {
-            "name": "symfony/finder",
-            "version": "v2.7.5",
+            "name": "symfony/polyfill-apcu",
+            "version": "v1.1.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/finder.git",
-                "reference": "8262ab605973afbb3ef74b945daabf086f58366f"
+                "url": "https://github.com/symfony/polyfill-apcu.git",
+                "reference": "d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/finder/zipball/8262ab605973afbb3ef74b945daabf086f58366f",
-                "reference": "8262ab605973afbb3ef74b945daabf086f58366f",
+                "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c",
+                "reference": "d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.9"
-            },
-            "require-dev": {
-                "symfony/phpunit-bridge": "~2.7"
+                "php": ">=5.3.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.7-dev"
+                    "dev-master": "1.1-dev"
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Finder\\": ""
-                }
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
             ],
             "authors": [
                 {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
                 },
                 {
                     "name": "Symfony Community",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony Finder Component",
+            "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions",
             "homepage": "https://symfony.com",
-            "time": "2015-09-19 19:59:23"
+            "keywords": [
+                "apcu",
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2016-01-20 09:13:37"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.0.1",
+            "version": "v1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25"
+                "reference": "1289d16209491b584839022f29257ad859b8532d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25",
-                "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d",
+                "reference": "1289d16209491b584839022f29257ad859b8532d",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0-dev"
+                    "dev-master": "1.1-dev"
                 }
             },
             "autoload": {
                 "portable",
                 "shim"
             ],
-            "time": "2015-11-20 09:19:13"
+            "time": "2016-01-20 09:13:37"
         },
         {
             "name": "symfony/process",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "62c254438b5040bc2217156e1570cf2206e8540c"
+                "reference": "7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/62c254438b5040bc2217156e1570cf2206e8540c",
-                "reference": "62c254438b5040bc2217156e1570cf2206e8540c",
+                "url": "https://api.github.com/repos/symfony/process/zipball/7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe",
+                "reference": "7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Process Component",
             "homepage": "https://symfony.com",
-            "time": "2015-12-23 11:03:46"
+            "time": "2016-02-02 13:33:15"
         },
         {
             "name": "symfony/translation",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "c1db87c51251167dd91198b9d1edf897773adb4f"
+                "reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/c1db87c51251167dd91198b9d1edf897773adb4f",
-                "reference": "c1db87c51251167dd91198b9d1edf897773adb4f",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
+                "reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Translation Component",
             "homepage": "https://symfony.com",
-            "time": "2015-12-05 17:37:59"
+            "time": "2016-02-02 09:49:18"
         },
         {
             "name": "symfony/yaml",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
-                "reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966"
+                "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966",
-                "reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/2a4ee40acb880c56f29fb1b8886e7ffe94f3b995",
+                "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Yaml Component",
             "homepage": "https://symfony.com",
-            "time": "2015-12-26 13:37:56"
+            "time": "2016-02-23 07:41:20"
         }
     ],
     "aliases": [],
index 8d8d481..cdf3de9 100644 (file)
@@ -673,18 +673,16 @@ $CFG->admin = 'admin';
 // params hierarchy. More info: http://docs.behat.org/guides/7.config.html
 // Example:
 //   $CFG->behat_config = array(
-//       'default' => array(
-//           'formatter' => array(
-//               'name' => 'pretty',
-//               'parameters' => array(
-//                   'decorated' => true,
-//                   'verbose' => false
-//               )
-//           )
-//       ),
 //       'Mac-Firefox' => array(
+//           'suites' => array (
+//               'default' => array(
+//                   'filters' => array(
+//                      'tags' => '~@_file_upload'
+//                   ),
+//               ),
+//           ),
 //           'extensions' => array(
-//               'Behat\MinkExtension\Extension' => array(
+//               'Behat\MinkExtension' => array(
 //                   'selenium2' => array(
 //                       'browser' => 'firefox',
 //                       'capabilities' => array(
@@ -697,7 +695,7 @@ $CFG->admin = 'admin';
 //       ),
 //       'Mac-Safari' => array(
 //           'extensions' => array(
-//               'Behat\MinkExtension\Extension' => array(
+//               'Behat\MinkExtension' => array(
 //                   'selenium2' => array(
 //                       'browser' => 'safari',
 //                       'capabilities' => array(
@@ -709,6 +707,20 @@ $CFG->admin = 'admin';
 //           )
 //       )
 //   );
+// You can also use the following config to override default Moodle configuration for Behat.
+// This config is limited to default suite and will be supported in later versions.
+// It will have precedence over $CFG->behat_config.
+// $CFG->behat_profiles = array(
+//     'phantomjs' => array(
+//         'browser' => 'phantomjs',
+//         'tags' => '~@_file_upload&&~@_alert&&~@_bug_phantomjs',
+//         'wd_host' => 'http://127.0.0.1:4443/wd/hub',
+//         'capabilities' => array(
+//             'platform' => 'Linux',
+//             'version' => 2.1
+//         )
+//     ),
+// );
 //
 // You can force the browser session (not user's sessions) to restart after N seconds. This could
 // be useful if you are using a cloud-based service with time restrictions in the browser side.
diff --git a/course/classes/output/course_module_name.php b/course/classes/output/course_module_name.php
new file mode 100644 (file)
index 0000000..e286e55
--- /dev/null
@@ -0,0 +1,100 @@
+<?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/>.
+
+/**
+ * Contains class core_tag\output\course_module_name
+ *
+ * @package   core_course
+ * @copyright 2016 Marina Glancy
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_course\output;
+
+use context_module;
+use lang_string;
+use cm_info;
+
+/**
+ * Class to prepare a course module name for display and in-place editing
+ *
+ * @package   core_course
+ * @copyright 2016 Marina Glancy
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_module_name extends \core\output\inplace_editable {
+
+    /** @var cm_info */
+    protected $cm;
+
+    /** @var array */
+    protected $displayoptions;
+
+    /**
+     * Constructor.
+     *
+     * @param cm_info $cm
+     * @param bool $editable
+     * @param array $displayoptions
+     */
+    public function __construct(cm_info $cm, $editable, $displayoptions = array()) {
+        $this->cm = $cm;
+        $this->displayoptions = $displayoptions;
+        $value = $cm->name;
+        $edithint = new lang_string('edittitle');
+        $editlabel = new lang_string('newactivityname', '', $cm->get_formatted_name());
+        $editable = $editable && has_capability('moodle/course:manageactivities',
+                    context_module::instance($cm->id));
+        parent::__construct(
+            'core_course', 'activityname', $cm->id, $editable, $value, $value, $edithint, $editlabel);
+    }
+
+    /**
+     * Export this data so it can be used as the context for a mustache template (core/inplace_editable).
+     *
+     * @param renderer_base $output typically, the renderer that's calling this function
+     * @return array data context for a mustache template
+     */
+    public function export_for_template(\renderer_base $output) {
+        global $PAGE;
+        $courserenderer = $PAGE->get_renderer('core', 'course');
+        $this->displayvalue = $courserenderer->course_section_cm_name_title($this->cm, $this->displayoptions);
+        if (strval($this->displayvalue) === '') {
+            $this->editable = false;
+        }
+        return parent::export_for_template($output);
+    }
+
+    /**
+     * Updates course module name
+     *
+     * @param int $itemid course module id
+     * @param string $newvalue new name
+     * @return static
+     */
+    public static function update($itemid, $newvalue) {
+        list($course, $cm) = get_course_and_cm_from_cmid($itemid);
+        $context = context_module::instance($cm->id);
+        // Check access.
+        require_login($course, false, $cm, true, true);
+        require_capability('moodle/course:manageactivities', $context);
+        // Update value.
+        set_coursemodule_name($cm->id, $newvalue);
+        // Return instance.
+        $cm = get_fast_modinfo($course)->get_cm($cm->id);
+        return new static($cm, true);
+    }
+}
index 6afaae0..59b69e7 100644 (file)
@@ -1605,6 +1605,50 @@ function set_coursemodule_visible($id, $visible) {
     return true;
 }
 
+/**
+ * Changes the course module name
+ *
+ * @param int $id course module id
+ * @param string $name new value for a name
+ * @return bool whether a change was made
+ */
+function set_coursemodule_name($id, $name) {
+    global $CFG, $DB;
+    require_once($CFG->libdir . '/gradelib.php');
+
+    $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
+
+    $module = new \stdClass();
+    $module->id = $cm->instance;
+
+    // Escape strings as they would be by mform.
+    if (!empty($CFG->formatstringstriptags)) {
+        $module->name = clean_param($name, PARAM_TEXT);
+    } else {
+        $module->name = clean_param($name, PARAM_CLEANHTML);
+    }
+    if ($module->name === $cm->name || strval($module->name) === '') {
+        return false;
+    }
+    if (\core_text::strlen($module->name) > 255) {
+        throw new \moodle_exception('maximumchars', 'moodle', '', 255);
+    }
+
+    $module->timemodified = time();
+    $DB->update_record($cm->modname, $module);
+    $cm->name = $module->name;
+    \core\event\course_module_updated::create_from_cm($cm)->trigger();
+    rebuild_course_cache($cm->course, true);
+
+    // Attempt to update the grade item if relevant.
+    $grademodule = $DB->get_record($cm->modname, array('id' => $cm->instance));
+    $grademodule->cmidnumber = $cm->idnumber;
+    $grademodule->modname = $cm->modname;
+    grade_update_mod_grades($grademodule);
+
+    return true;
+}
+
 /**
  * This function will handle the whole deletion process of a module. This includes calling
  * the modules delete_instance function, deleting files, events, grades, conditional data,
@@ -2241,53 +2285,6 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
     return $actions;
 }
 
-/**
- * Returns the rename action.
- *
- * @param cm_info $mod The module to produce editing buttons for
- * @param int $sr The section to link back to (used for creating the links)
- * @return The markup for the rename action, or an empty string if not available.
- */
-function course_get_cm_rename_action(cm_info $mod, $sr = null) {
-    global $COURSE, $OUTPUT;
-
-    static $str;
-    static $baseurl;
-
-    $modcontext = context_module::instance($mod->id);
-    $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
-
-    if (!isset($str)) {
-        $str = get_strings(array('edittitle'));
-    }
-
-    if (!isset($baseurl)) {
-        $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
-    }
-
-    if ($sr !== null) {
-        $baseurl->param('sr', $sr);
-    }
-
-    // AJAX edit title.
-    if ($mod->has_view() && $hasmanageactivities && course_ajax_enabled($COURSE) &&
-                (($mod->course == $COURSE->id) || ($mod->course == SITEID))) {
-        // we will not display link if we are on some other-course page (where we should not see this module anyway)
-        return html_writer::span(
-            html_writer::link(
-                new moodle_url($baseurl, array('update' => $mod->id)),
-                $OUTPUT->pix_icon('t/editstring', '', 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
-                array(
-                    'class' => 'editing_title',
-                    'data-action' => 'edittitle',
-                    'title' => $str->edittitle,
-                )
-            )
-        );
-    }
-    return '';
-}
-
 /**
  * Returns the move action.
  *
@@ -3897,3 +3894,17 @@ function course_get_tagged_courses($tag, $exclusivemode = false, $fromctx = 0, $
     return new core_tag\output\tagindex($tag, 'core', 'course', $content,
             $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
 }
+
+/**
+ * Implements callback inplace_editable() allowing to edit values in-place
+ *
+ * @param string $itemtype
+ * @param int $itemid
+ * @param mixed $newvalue
+ * @return \core\output\inplace_editable
+ */
+function core_course_inplace_editable($itemtype, $itemid, $newvalue) {
+    if ($itemtype === 'activityname') {
+        return \core_course\output\course_module_name::update($itemid, $newvalue);
+    }
+}
index b71d488..617f4e8 100644 (file)
@@ -733,10 +733,34 @@ class core_course_renderer extends plugin_renderer_base {
      * @return string
      */
     public function course_section_cm_name(cm_info $mod, $displayoptions = array()) {
-        global $CFG;
+        if ((!$mod->uservisible && empty($mod->availableinfo)) || !$mod->url) {
+            // Nothing to be displayed to the user.
+            return '';
+        }
+
+        // Render element that allows to edit activity name inline. It calls {@link course_section_cm_name_title()}
+        // to get the display title of the activity.
+        $tmpl = new \core_course\output\course_module_name($mod, $this->page->user_is_editing(), $displayoptions);
+        return $this->output->render_from_template('core/inplace_editable', $tmpl->export_for_template($this->output));
+    }
+
+    /**
+     * Renders html to display a name with the link to the course module on a course page
+     *
+     * If module is unavailable for user but still needs to be displayed
+     * in the list, just the name is returned without a link
+     *
+     * Note, that for course modules that never have separate pages (i.e. labels)
+     * this function return an empty string
+     *
+     * @param cm_info $mod
+     * @param array $displayoptions
+     * @return string
+     */
+    public function course_section_cm_name_title(cm_info $mod, $displayoptions = array()) {
         $output = '';
         if (!$mod->uservisible && empty($mod->availableinfo)) {
-            // nothing to be displayed to the user
+            // Nothing to be displayed to the user.
             return $output;
         }
         $url = $mod->url;
@@ -985,10 +1009,6 @@ class core_course_renderer extends plugin_renderer_base {
             $output .= $cmname;
 
 
-            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->afterlink;
 
index 675f367..42b45ce 100644 (file)
@@ -150,49 +150,6 @@ switch($requestmethod) {
                         $isvisible = moveto_module($cm, $section, $beforemod);
                         echo json_encode(array('visible' => (bool) $isvisible));
                         break;
-                    case 'gettitle':
-                        require_capability('moodle/course:manageactivities', $modcontext);
-                        $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
-                        $module = new stdClass();
-                        $module->id = $cm->instance;
-
-                        // Don't pass edit strings through multilang filters - we need the entire string
-                        echo json_encode(array('instancename' => $cm->name));
-                        break;
-                    case 'updatetitle':
-                        require_capability('moodle/course:manageactivities', $modcontext);
-                        require_once($CFG->libdir . '/gradelib.php');
-                        $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
-                        $module = new stdClass();
-                        $module->id = $cm->instance;
-
-                        // Escape strings as they would be by mform
-                        if (!empty($CFG->formatstringstriptags)) {
-                            $module->name = clean_param($title, PARAM_TEXT);
-                        } else {
-                            $module->name = clean_param($title, PARAM_CLEANHTML);
-                        }
-
-                        if (strval($module->name) !== '') {
-                            $DB->update_record($cm->modname, $module);
-                            $cm->name = $module->name;
-                            \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
-                            rebuild_course_cache($cm->course);
-                        } else {
-                            $module->name = $cm->name;
-                        }
-
-                        // Attempt to update the grade item if relevant
-                        $grademodule = $DB->get_record($cm->modname, array('id' => $cm->instance));
-                        $grademodule->cmidnumber = $cm->idnumber;
-                        $grademodule->modname = $cm->modname;
-                        grade_update_mod_grades($grademodule);
-
-                        // We need to return strings after they've been through filters for multilang
-                        $stringoptions = new stdClass;
-                        $stringoptions->context = $coursecontext;
-                        echo json_encode(array('instancename' => html_entity_decode(format_string($module->name, true,  $stringoptions))));
-                        break;
                 }
                 break;
 
diff --git a/course/tests/behat/activities_edit_name.feature b/course/tests/behat/activities_edit_name.feature
new file mode 100644 (file)
index 0000000..2d53b7c
--- /dev/null
@@ -0,0 +1,44 @@
+@core @core_course
+Feature: Edit activity name in-place
+  In order to quickly edit activity name
+  As a teacher
+  I need to use inplace editing
+
+  @javascript
+  Scenario: Edit activity name in-place
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | format |
+      | Course 1 | C1 | topics |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Forum" to section "1" and I fill the form with:
+      | Forum name | Test forum name |
+      | Description | Test forum description |
+    # Rename activity
+    And I click on "Edit title" "link" in the "//div[contains(@class,'activityinstance') and contains(.,'Test forum name')]" "xpath_element"
+    And I set the field "New name for activity Test forum name" to "Good news"
+    And I press key "13" in the field "New name for activity Test forum name"
+    Then I should not see "Test forum name" in the ".course-content" "css_element"
+    And "New name for activity Test forum name" "field" should not exist
+    And I should see "Good news"
+    And I follow "Course 1"
+    And I should see "Good news"
+    And I should not see "Test forum name"
+    # Cancel renaming
+    And I click on "Edit title" "link" in the "//div[contains(@class,'activityinstance') and contains(.,'Good news')]" "xpath_element"
+    And I set the field "New name for activity Good news" to "Terrible news"
+    And I press key "27" in the field "New name for activity Good news"
+    And "New name for activity Good news" "field" should not exist
+    And I should see "Good news"
+    And I should not see "Terrible news"
+    And I follow "Course 1"
+    And I should see "Good news"
+    And I should not see "Terrible news"
+    And I log out
index 344a208..d16aa1a 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Gherkin\Node\TableNode as TableNode,
     Behat\Mink\Exception\ExpectationException as ExpectationException,
     Behat\Mink\Exception\DriverException as DriverException,
@@ -97,7 +97,7 @@ class behat_course extends behat_base {
                     unset($rows[$key]);
                 }
             }
-            $table->setRows($rows);
+            $table = new TableNode($rows);
 
             // Adding a forced wait until editors are loaded as otherwise selenium sometimes tries clicks on the
             // format field when the editor is being rendered and the click misses the field coordinates.
@@ -467,7 +467,7 @@ class behat_course extends behat_base {
                 foreach ($activities as $activity) {
                     // Dimmed.
                     $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' activityinstance ')]" .
-                        "/a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')]", $dimmedexception, $activity);
+                        "//a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')]", $dimmedexception, $activity);
                 }
             }
         } else {
@@ -585,7 +585,7 @@ class behat_course extends behat_base {
 
             // The 'Hide' button should be available.
             $nohideexception = new ExpectationException('"' . $activityname . '" don\'t have a "' . get_string('hide') . '" icon', $this->getSession());
-            $this->find('named', array('link', get_string('hide')), $nohideexception, $activitynode);
+            $this->find('named_partial', array('link', get_string('hide')), $nohideexception, $activitynode);
         }
     }
 
@@ -611,7 +611,7 @@ class behat_course extends behat_base {
 
             // Also 'Show' icon.
             $noshowexception = new ExpectationException('"' . $activityname . '" don\'t have a "' . get_string('show') . '" icon', $this->getSession());
-            $this->find('named', array('link', get_string('show')), $noshowexception, $activitynode);
+            $this->find('named_partial', array('link', get_string('show')), $noshowexception, $activitynode);
 
         } else {
 
index dc35898..42dacac 100644 (file)
@@ -105,7 +105,6 @@ Feature: Test we can resort categories in the management interface.
     And I should see the "Course categories and courses" management page
     And I click on <sortby> action for "Master cat" in management category listing
     And a new page should have loaded since I started watching
-    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
index cee3ccd..f117146 100644 (file)
@@ -86,7 +86,6 @@ Feature: Test we can resort course in the management interface.
     And I should see "Sort by Course time created descending" in the ".course-listing-actions" "css_element"
     And I click on <sortby> "link" in the ".course-listing-actions" "css_element"
     And a new page should have loaded since I started watching
-    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see course listing <course1> before <course2>
     And I should see course listing <course2> before <course3>
index 0e4e9de..aa50329 100644 (file)
@@ -2779,4 +2779,34 @@ class core_course_courselib_testcase extends advanced_testcase {
         $this->assertNull($DB->get_field('course_modules', 'availability',
                 array('id' => $label->cmid)));
     }
+
+    /**
+     * Test update_inplace_editable()
+     */
+    public function test_update_module_name_inplace() {
+        global $CFG, $DB, $PAGE;
+        require_once($CFG->dirroot . '/lib/external/externallib.php');
+
+        $this->setUser($this->getDataGenerator()->create_user());
+
+        $this->resetAfterTest(true);
+        $course = $this->getDataGenerator()->create_course();
+        $forum = self::getDataGenerator()->create_module('forum', array('course' => $course->id, 'name' => 'forum name'));
+
+        // Call service for core_course component without necessary permissions.
+        try {
+            core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name');
+            $this->fail('Exception expected');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('Course or activity not accessible. (Not enrolled)',
+                $e->getMessage());
+        }
+
+        // Change to admin user and make sure that cm name can be updated using web service update_inplace_editable().
+        $this->setAdminUser();
+        $res = core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name');
+        $res = external_api::clean_returnvalue(core_external::update_inplace_editable_returns(), $res);
+        $this->assertEquals('New forum name', $res['value']);
+        $this->assertEquals('New forum name', $DB->get_field('forum', 'name', array('id' => $forum->id)));
+    }
 }
index 03d72a6..75ef3b8 100644 (file)
Binary files a/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-debug.js and b/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-debug.js differ
index ec2b8fa..290b316 100644 (file)
Binary files a/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-min.js and b/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-min.js differ
index 03d72a6..75ef3b8 100644 (file)
Binary files a/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes.js and b/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes.js differ
index 578630e..3bb4e60 100644 (file)
@@ -52,17 +52,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
      */
     GROUPS_VISIBLE: 2,
 
-    /**
-     * An Array of events added when editing a title.
-     * These should all be detached when editing is complete.
-     *
-     * @property edittitleevents
-     * @protected
-     * @type Array
-     * @protected
-     */
-    edittitleevents: [],
-
     /**
      * Initialize the resource toolbox
      *
@@ -113,10 +102,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
 
         // Switch based upon the action and do the desired thing.
         switch (action) {
-            case 'edittitle':
-                // The user wishes to edit the title of the event.
-                this.edit_title(ev, node, activity, action);
-                break;
             case 'moveleft':
             case 'moveright':
                 // The user changing the indent of the activity.
@@ -429,11 +414,13 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
                 dimarea.addClass(toggleclass);
                 // We need to toggle dimming on the description too.
                 activity.all(SELECTOR.CONTENTAFTERLINK).addClass(CSS.DIMMEDTEXT);
+                activity.all(SELECTOR.GROUPINGLABEL).addClass(CSS.DIMMEDTEXT);
             } else {
                 // Change the UI.
                 dimarea.removeClass(toggleclass);
                 // We need to toggle dimming on the description too.
                 activity.all(SELECTOR.CONTENTAFTERLINK).removeClass(CSS.DIMMEDTEXT);
+                activity.all(SELECTOR.GROUPINGLABEL).removeClass(CSS.DIMMEDTEXT);
             }
         }
         // Toggle availablity info for conditional activities.
@@ -510,172 +497,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
         return this;
     },
 
-    /**
-     * Edit the title for the resource
-     *
-     * @method edit_title
-     * @protected
-     * @param {EventFacade} ev The event that was fired.
-     * @param {Node} button The button that triggered this action.
-     * @param {Node} activity The activity node that this action will be performed on.
-     * @param {String} action The action that has been requested.
-     * @chainable
-     */
-    edit_title: function(ev, button, activity) {
-        // 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,
-            thisevent,
-            anchor = instancename.ancestor('a'),// Grab the anchor so that we can swap it with the edit form.
-            data = {
-                'class': 'resource',
-                'field': 'gettitle',
-                'id': activityid
-            };
-
-        // Prevent the default actions.
-        ev.preventDefault();
-
-        this.send_request(data, null, function(response) {
-            if (M.core.actionmenu && M.core.actionmenu.instance) {
-                M.core.actionmenu.instance.hideMenu(ev);
-            }
-
-            // Try to retrieve the existing string from the server
-            if (response.instancename) {
-                titletext = response.instancename;
-            }
-
-            // Create the editor and submit button
-            var editform = Y.Node.create('<form action="#" />');
-            var editinstructions = Y.Node.create('<span class="'+CSS.EDITINSTRUCTIONS+'" id="id_editinstructions" />')
-                .set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
-            var editor = Y.Node.create('<input name="title" type="text" class="'+CSS.TITLEEDITOR+'" />').setAttrs({
-                'value': titletext,
-                'autocomplete': 'off',
-                'aria-describedby': 'id_editinstructions',
-                'maxLength': '255'
-            });
-
-            // Clear the existing content and put the editor in
-            editform.appendChild(activity.one(SELECTOR.ACTIVITYICON).cloneNode());
-            editform.appendChild(editor);
-            editform.setData('anchor', anchor);
-            instance.insert(editinstructions, 'before');
-            anchor.replace(editform);
-
-            // Force the editing instruction to match the mod-indent position.
-            var padside = 'left';
-            if (window.right_to_left()) {
-                padside = 'right';
-            }
-
-            // We hide various components whilst editing:
-            activity.addClass(CSS.EDITINGTITLE);
-
-            // Focus and select the editor text
-            editor.focus().select();
-
-            // Cancel the edit if we lose focus or the escape key is pressed.
-            thisevent = editor.on('blur', this.edit_title_cancel, this, activity, false);
-            this.edittitleevents.push(thisevent);
-            thisevent = editor.on('key', this.edit_title_cancel, 'esc', this, activity, true);
-            this.edittitleevents.push(thisevent);
-
-            // Handle form submission.
-            thisevent = editform.on('submit', this.edit_title_submit, this, activity, oldtitle);
-            this.edittitleevents.push(thisevent);
-        });
-        return this;
-    },
-
-    /**
-     * Handles the submit event when editing the activity or resources title.
-     *
-     * @method edit_title_submit
-     * @protected
-     * @param {EventFacade} ev The event that triggered this.
-     * @param {Node} activity The activity whose title we are altering.
-     * @param {String} originaltitle The original title the activity or resource had.
-     */
-    edit_title_submit: function(ev, activity, originaltitle) {
-        // We don't actually want to submit anything
-        ev.preventDefault();
-
-        var newtitle = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYTITLE).get('value'));
-        this.edit_title_clear(activity);
-        var spinner = this.add_spinner(activity);
-        if (newtitle !== null && newtitle !== "" && newtitle !== originaltitle) {
-            var data = {
-                'class': 'resource',
-                'field': 'updatetitle',
-                'title': newtitle,
-                'id': Y.Moodle.core_course.util.cm.getId(activity)
-            };
-            this.send_request(data, spinner, function(response) {
-                if (response.instancename) {
-                    activity.one(SELECTOR.INSTANCENAME).setContent(response.instancename);
-                }
-            });
-        }
-    },
-
-    /**
-     * Handles the cancel event when editing the activity or resources title.
-     *
-     * @method edit_title_cancel
-     * @protected
-     * @param {EventFacade} ev The event that triggered this.
-     * @param {Node} activity The activity whose title we are altering.
-     * @param {Boolean} preventdefault If true we should prevent the default action from occuring.
-     */
-    edit_title_cancel: function(ev, activity, preventdefault) {
-        if (preventdefault) {
-            ev.preventDefault();
-        }
-        this.edit_title_clear(activity);
-    },
-
-    /**
-     * Handles clearing the editing UI and returning things to the original state they were in.
-     *
-     * @method edit_title_clear
-     * @protected
-     * @param {Node} activity  The activity whose title we were altering.
-     */
-    edit_title_clear: function(activity) {
-        // Detach all listen events to prevent duplicate triggers
-        new Y.EventHandle(this.edittitleevents).detach();
-
-        var editform = activity.one(SELECTOR.ACTIVITYFORM),
-            instructions = activity.one('#id_editinstructions');
-        if (editform) {
-            editform.replace(editform.getData('anchor'));
-        }
-        if (instructions) {
-            instructions.remove();
-        }
-
-        // Remove the editing class again to revert the display.
-        activity.removeClass(CSS.EDITINGTITLE);
-
-        // Refocus the link which was clicked originally so the user can continue using keyboard nav.
-        Y.later(100, this, function() {
-            activity.one(SELECTOR.EDITTITLE).focus();
-        });
-
-        // TODO MDL-50768 This hack is to keep Behat happy until they release a version of
-        // MinkSelenium2Driver that fixes
-        // https://github.com/Behat/MinkSelenium2Driver/issues/80.
-        if (!Y.one('input[name=title]')) {
-            Y.one('body').append('<input type="text" name="title" style="display: none">');
-        }
-    },
-
     /**
      * Set the visibility of the specified resource to match the visible parameter.
      *
index 7fd7b3b..e3f6c8a 100644 (file)
@@ -17,31 +17,28 @@ var CSS = {
         DIMCLASS : 'dimmed',
         DIMMEDTEXT : 'dimmed_text',
         EDITINSTRUCTIONS : 'editinstructions',
-        EDITINGTITLE: 'editor_displayed',
         HIDE : 'hide',
         MODINDENTCOUNT : 'mod-indent-',
         MODINDENTHUGE : 'mod-indent-huge',
         MODULEIDPREFIX : 'module-',
         SECTIONHIDDENCLASS : 'hidden',
         SECTIONIDPREFIX : 'section-',
-        SHOW : 'editing_show',
-        TITLEEDITOR : 'titleeditor'
+        SHOW : 'editing_show'
     },
     // The CSS selectors we use.
     SELECTOR = {
         ACTIONAREA: '.actions',
         ACTIONLINKTEXT : '.actionlinktext',
-        ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_title',
-        ACTIVITYFORM : '.' + CSS.ACTIVITYINSTANCE + ' form',
+        ACTIVITYACTION : 'a.cm-edit-action[data-action]',
         ACTIVITYICON : 'img.activityicon',
         ACTIVITYINSTANCE : '.' + CSS.ACTIVITYINSTANCE,
-        ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a',
+        ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a, .'+ CSS.ACTIVITYINSTANCE +
+            ' > span[data-inplaceeditable] > a:not([data-inplaceeditablelink])',
         ACTIVITYLI : 'li.activity',
-        ACTIVITYTITLE : 'input[name=title]',
         COMMANDSPAN : '.commands',
         CONTENTAFTERLINK : 'div.contentafterlink',
         CONTENTWITHOUTLINK : 'div.contentwithoutlink',
-        EDITTITLE: 'a.editing_title',
+        GROUPINGLABEL: '.' + CSS.ACTIVITYINSTANCE + ' .groupinglabel',
         HIDE : 'a.editing_hide',
         HIGHLIGHT : 'a.editing_highlight',
         INSTANCENAME : 'span.instancename',
index 116e0a2..034898c 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Gherkin\Node\TableNode as TableNode;
 
 /**
index 49ab967..8384259 100644 (file)
@@ -263,7 +263,7 @@ if (!$moving) {
 if (!$moving && count($grade_edit_tree->categories) > 1) {
     echo '<br /><br />';
     echo '<input type="hidden" name="bulkmove" value="0" id="bulkmoveinput" />';
-    $attributes = array('id'=>'menumoveafter', 'class' => 'ignoredirty');
+    $attributes = array('id'=>'menumoveafter', 'class' => 'ignoredirty singleselect');
     echo html_writer::label(get_string('moveselectedto', 'grades'), 'menumoveafter');
     echo html_writer::select($grade_edit_tree->categories, 'moveafter', '', array(''=>'choosedots'), $attributes);
     $OUTPUT->add_action_handler(new component_action('change', 'submit_bulk_move'), 'menumoveafter');
index 7ce443c..e63fc5d 100644 (file)
@@ -26,9 +26,9 @@
 require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
 
 use Behat\Gherkin\Node\TableNode as TableNode,
-    Behat\Behat\Context\Step\Given as Given,
-    Behat\Behat\Context\Step\When as When,
-    Behat\Behat\Context\Step\Then as Then,
+    Moodle\BehatExtension\Context\Step\Given as Given,
+    Moodle\BehatExtension\Context\Step\When as When,
+    Moodle\BehatExtension\Context\Step\Then as Then,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
     Behat\Mink\Exception\ExpectationException as ExpectationException;
 
index e01387b..778c7ff 100644 (file)
@@ -28,9 +28,7 @@
 require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
 
 use Behat\Gherkin\Node\TableNode as TableNode,
-    Behat\Behat\Context\Step\Given as Given,
-    Behat\Behat\Context\Step\When as When,
-    Behat\Behat\Context\Step\Then as Then,
+    Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
     Behat\Mink\Exception\ExpectationException as ExpectationException;
 
@@ -107,6 +105,15 @@ class behat_gradingform_rubric extends behat_base {
                     }
                 }
 
+                // Remove empty criterion, as TableNode might contain them to make table rows equal size.
+                $newcriterion = array();
+                foreach ($criterion as $k => $c) {
+                    if (!empty($c)) {
+                        $newcriterion[$k] = $c;
+                    }
+                }
+                $criterion = $newcriterion;
+
                 // Checking the number of cells.
                 if (count($criterion) % 2 === 0) {
                     throw new ExpectationException(
index 0a0bf07..f76117c 100644 (file)
@@ -28,8 +28,8 @@
 require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
 
 use Behat\Gherkin\Node\TableNode as TableNode,
-    Behat\Behat\Context\Step\Given as Given,
-    Behat\Behat\Context\Step\When as When;
+    Moodle\BehatExtension\Context\Step\Given as Given,
+    Moodle\BehatExtension\Context\Step\When as When;
 
 /**
  * Generic grading methods step definitions.
@@ -93,7 +93,7 @@ class behat_grading extends behat_base {
 
         // Shortcut in case we already are in the grading page.
         $usergradetextliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($usergradetext);
-        if ($this->getSession()->getPage()->find('named', array('link', $usergradetextliteral))) {
+        if ($this->getSession()->getPage()->find('named_partial', array('link', $usergradetextliteral))) {
             return $gradeuserstep;
         }
 
index 880a341..d341ffd 100644 (file)
@@ -25,22 +25,25 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
       | name       | scale                                  |
       | Test Scale | Disappointing,Good,Very good,Excellent |
     And the following "grade categories" exist:
-      | fullname | course |
-      | Grade Cat | C1 |
+      | fullname  | course |
+      | Grade Cat | C1     |
+    And the following "grade categories" exist:
+      | fullname  | course | gradecategory |
+      | Grade Sub Cat  | C1 | Grade Cat |
     And the following "grade items" exist:
       | itemname | course | locked | gradetype | gradecategory |
-      | Item 1 | C1 | 0 | value | Grade Cat |
+      | Item 1  | C1 | 0 | value | Grade Cat |
       | Item VU | C1 | 0 | value | Grade Cat |
       | Item VL | C1 | 1 | value | Grade Cat |
-      | Item TU | C1 | 0 | text | Grade Cat |
-      | Item TL | C1 | 1 | text | Grade Cat |
-    And the following "grade items" exist:
-      | itemname | course | locked | gradetype | scale | gradecategory |
-      | Item SU | C1 | 0 | scale | Test Scale | Grade Cat |
-      | Item SL | C1 | 1 | scale | Test Scale | Grade Cat |
+      | Item TU | C1 | 0 | text  | Grade Cat |
+      | Item TL | C1 | 1 | text  | Grade Cat |
+      | Item 3  | C1 | 0 | value | Grade Cat |
+      | Calc Item  | C1 | 0 | value | Grade Cat     |
+      | Item VUSub | C1 | 0 | value | Grade Sub Cat |
     And the following "grade items" exist:
-      | itemname | course | locked | gradetype | gradecategory |
-      | Item 3 | C1 | 0 | value | Grade Cat |
+      | itemname   | course | locked | gradetype | scale | gradecategory |
+      | Item SU    | C1 | 0 | scale | Test Scale | Grade Cat |
+      | Item SL    | C1 | 1 | scale | Test Scale | Grade Cat |
     And the following config values are set as admin:
       | grade_report_showaverages | 0 |
       | grade_report_enableajax | 1 |
@@ -71,7 +74,7 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
     And I set the field "ajaxgrade" to "Very good"
     And I press key "13" in the field "ajaxgrade"
     And the following should exist in the "user-grades" table:
-      | -1-                | -4-      | -5-      | -9-       | -13-         |
+      | -1-                | -6-      | -7-      | -13-      | -16-         |
       | Student 2          | -        | 33.00    | -         | 33.00        |
       | Student 3          | 80.00    | 50.00    | Very good | 133.00       |
     And I click on student "Student 3" for grade item "Item VL"
@@ -89,14 +92,14 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
     And I set the field "ajaxgrade" to "90"
     And I press key "13" in the field "ajaxgrade"
     And the following should exist in the "user-grades" table:
-      | -1-                | -13-      |
+      | -1-                | -16-      |
       | Student 1          | 90.00     |
     And I navigate to "Grader report" node in "Grade administration"
     And the following should exist in the "user-grades" table:
-      | -1-                | -4-      | -5-      | -9-       | -13-         |
-      | Student 1          | -        | -        | -         | 90.00        |
-      | Student 2          | -        | 33.00    | -         | 33.00        |
-      | Student 3          | 80.00    | 50.00    | Very good | 133.00       |
+      | -1-                | -6-   | -7-   | -13-      | -16-      |
+      | Student 1          | -     | -     | -         | 90.00     |
+      | Student 2          | -     | 33.00 | -         | 33.00     |
+      | Student 3          | 80.00 | 50.00 | Very good | 133.00    |
 
   @javascript
   Scenario: Use the grader report without editing, with AJAX and quick feedback on
@@ -126,8 +129,8 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
     And I press key "13" in the field "ajaxfeedback"
     And I navigate to "Grader report" node in "Grade administration"
     And the following should exist in the "user-grades" table:
-      | -1-                | -5-      | -9-       | -13-         |
-      | Student 2          | 33.00    | Very good | 36.00        |
+      | -1-       | -7-   | -13-      | -16-  |
+      | Student 2 | 33.00 | Very good | 36.00 |
     And I click on student "Student 3" for grade item "Item TU"
     And the field "ajaxfeedback" matches value "Student 3 TU feedback"
     And I click on student "Student 2" for grade item "Item SU"
@@ -150,8 +153,8 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
     And I should not see a grade field for "Student 3" and grade item "Course total"
     And I should not see a feedback field for "Student 3" and grade item "Course total"
     And the following should exist in the "user-grades" table:
-      | -1-                | -5-      | -13-    |
-      | Student 2          | 33.00    | 33.00   |
+      | -1-         | -7-      | -16-    |
+      | Student 2   | 33.00    | 33.00   |
 
   @javascript
   Scenario: Use the grader report with editing, with AJAX and quick feedback on, with category override
@@ -179,8 +182,8 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
     And the grade for "Student 2" in grade item "Course total" should match "53.00"
     And I turn editing mode off
     And the following should exist in the "user-grades" table:
-      | -1-                | -4-      | -5-     | -9-       | -12-     | -13-    |
-      | Student 2          | 30.00    | 20.00   | Very good | 53.00    | 53.00   |
+      | -1-        | -6-      | -7-     | -13-      | -15-     | -16-    |
+      | Student 2  | 30.00    | 20.00   | Very good | 53.00    | 53.00   |
     And I click on student "Student 2" for grade item "Item 1"
     And the field "ajaxfeedback" matches value "Some feedback"
 
@@ -193,20 +196,41 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I turn editing mode on
+    And I change window size to "large"
+    And I set "=[[i1]] + [[i3]] + [[gsc]]" calculation for grade item "Calc Item" with idnumbers:
+      | Item 1        | i1  |
+      | Item 3        | i3  |
+      | Grade Sub Cat | gsc |
     Then I should not see a grade field for "Student 2" and grade item "Course total"
     And I should not see a feedback field for "Student 2" and grade item "Course total"
     And I give the grade "20.00" to the user "Student 2" for the grade item "Item VU"
     And I click away from student "Student 2" and grade item "Item VU" value
+    And the following should exist in the "user-grades" table:
+      | -1-        | -15-   | -16-  |
+      | Student 2  | 20.00  | 20.00 |
     And I give the grade "30.00" to the user "Student 2" for the grade item "Item 1"
     And I click away from student "Student 2" and grade item "Item 1" value
+    And the following should exist in the "user-grades" table:
+      | -1-        | -15-  | -16-  |
+      | Student 2  | 80.00 | 80.00 |
+    And the field "Student 2 Calc Item grade" matches value "30.00"
+    And I give the grade "5.00" to the user "Student 2" for the grade item "Item 3"
+    And I click away from student "Student 2" and grade item "Item 3" value
+    And the following should exist in the "user-grades" table:
+      | -1-        | -15-  | -16- |
+      | Student 2  | 90.00 | 90.00 |
+    And the field "Student 2 Calc Item grade" matches value "35.00"
+    And I give the grade "10.00" to the user "Student 2" for the grade item "Item VUSub"
+    And I click away from student "Student 2" and grade item "Item VUSub" value
+    And the following should exist in the "user-grades" table:
+      | -1-        | -5-   | -15-   | -16-   |
+      | Student 2  | 10.00 | 110.00 | 110.00 |
+    And the field "Student 2 Calc Item grade" matches value "45.00"
     And I give the feedback "Some feedback" to the user "Student 2" for the grade item "Item 1"
     And I click away from student "Student 2" and grade item "Item 1" feedback
-    And the following should exist in the "user-grades" table:
-      | -1-                | -13-     |
-      | Student 2          | 50.00    |
     And I turn editing mode off
     And the following should exist in the "user-grades" table:
-      | -1-                | -4-      | -5-      | -13-         |
-      | Student 2          | 30.00    | 20.00    | 50.00        |
+      | -1-        | -4-   | -6-   | -7-   | -11- | -12-  | -15-   | -16-   |
+      | Student 2  | 10.00 | 30.00 | 20.00 | 5.00 | 45.00 | 110.00 | 110.00 |
     And I click on student "Student 2" for grade item "Item 1"
     And the field "ajaxfeedback" matches value "Some feedback"
index ee12feb..3e2bcd1 100644 (file)
@@ -27,8 +27,8 @@
 
 require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given,
-    Behat\Behat\Context\Step\Then,
+use Moodle\BehatExtension\Context\Step\Given,
+    Moodle\BehatExtension\Context\Step\Then,
     Behat\Mink\Exception\ExpectationException as ExpectationException,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 
index 6ee26bf..76ef7f6 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Gherkin\Node\TableNode as TableNode;
 
 class behat_grade extends behat_base {
index c51e281..3e2ab94 100644 (file)
@@ -44,6 +44,7 @@ Feature: We can use calculated grade totals
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I turn editing mode on
+    And I change window size to "large"
     And I give the grade "60.00" to the user "Student 1" for the grade item "Test assignment one"
     And I give the grade "20.00" to the user "Student 1" for the grade item "Test assignment two"
     And I give the grade "40.00" to the user "Student 1" for the grade item "Test assignment three"
@@ -58,6 +59,7 @@ Feature: We can use calculated grade totals
       | Hidden | 1 |
     And I set the following settings for grade item "Test assignment eight":
       | Hidden | 1 |
+    And I change window size to "medium"
     And I navigate to "Course grade settings" node in "Grade administration > Setup"
     And I set the field "Grade display type" to "Real (percentage)"
     And I press "Save changes"
index 54aea96..daede1d 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Then;
+use Moodle\BehatExtension\Context\Step\Then;
 use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 
 /**
@@ -62,6 +62,11 @@ class behat_groups extends behat_base {
         $fulloption = $groupoption->getText();
         $select->selectOption($fulloption);
 
+        // This is needed by some drivers to ensure relevant event is triggred and button is enabled.
+        $script = "Syn.trigger('change', {}, {{ELEMENT}})";
+        $this->getSession()->getDriver()->triggerSynScript($select->getXpath(), $script);
+        $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
+
         // Here we don't need to wait for the AJAX response.
         $this->find_button(get_string('adduserstogroup', 'group'))->click();
 
index 6725972..866ee05 100644 (file)
@@ -31,6 +31,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['language'] = 'Idioma';
+$string['moodlelogo'] = 'Logotip de Moodle';
 $string['next'] = 'Seg├╝ent';
 $string['previous'] = 'Anterior';
 $string['reload'] = 'Torna a carregar';
index c7944d1..19eea47 100644 (file)
@@ -49,6 +49,8 @@ $string['auth_remove_keep'] = 'Keep internal';
 $string['auth_remove_suspend'] = 'Suspend internal';
 $string['auth_remove_user'] = 'Specify what to do with internal user account during mass synchronization when user was removed from external source. Only suspended users are automatically revived if they reappear in ext source.';
 $string['auth_remove_user_key'] = 'Removed ext user';
+$string['auth_sync_suspended']  = 'When enabled, the suspended attribute will be used to update the local user account\'s suspension status.';
+$string['auth_sync_suspended_key'] = 'Synchronize local user suspension status';
 $string['auth_sync_script'] = 'User account syncronisation';
 $string['auth_updatelocal'] = 'Update local';
 $string['auth_updatelocal_expl'] = '<p><b>Update local:</b> If enabled, the field will be updated (from external auth) every time the user logs in or there is a user synchronization. Fields set to update locally should be locked.</p>';
index 6c34d47..327da65 100644 (file)
@@ -60,8 +60,10 @@ $string['cachedef_plugin_manager'] = 'Plugin info manager';
 $string['cachedef_questiondata'] = 'Question definitions';
 $string['cachedef_repositories'] = 'Repositories instances data';
 $string['cachedef_search_results'] = 'Search results user data';
+$string['cachedef_grade_categories'] = 'Grade category queries';
 $string['cachedef_string'] = 'Language string cache';
 $string['cachedef_tags'] = 'Tags collections and areas';
+$string['cachedef_temp_tables'] = 'Temporary tables cache';
 $string['cachedef_userselections'] = 'Data used to persist user selections throughout Moodle';
 $string['cachedef_yuimodules'] = 'YUI Module definitions';
 $string['cachelock_file_default'] = 'Default file locking';
index 7b1dc38..3aa114e 100644 (file)
@@ -50,6 +50,8 @@ $string['description'] = 'Description';
 $string['displayedrows'] = '{$a->displayed} rows displayed out of {$a->total}.';
 $string['duplicateidnumber'] = 'Cohort with the same ID number already exists';
 $string['editcohort'] = 'Edit cohort';
+$string['editcohortidnumber'] = 'Edit cohort ID';
+$string['editcohortname'] = 'Edit cohort name';
 $string['eventcohortcreated'] = 'Cohort created';
 $string['eventcohortdeleted'] = 'Cohort deleted';
 $string['eventcohortmemberadded'] = 'User added to a cohort';
@@ -61,6 +63,8 @@ $string['memberscount'] = 'Cohort size';
 $string['name'] = 'Name';
 $string['namecolumnmissing'] = 'There is something wrong with the format of the CSV file. Please check that it includes column names.';
 $string['namefieldempty'] = 'Field name can not be empty';
+$string['newnamefor'] = 'New name for cohort {$a}';
+$string['newidnumberfor'] = 'New idnumber for cohort {$a}';
 $string['nocomponent'] = 'Created manually';
 $string['potusers'] = 'Potential users';
 $string['potusersmatching'] = 'Potential matching users';
index 13641d5..53c98f4 100644 (file)
@@ -1228,6 +1228,7 @@ $string['never'] = 'Never';
 $string['neverdeletelogs'] = 'Never delete logs';
 $string['new'] = 'New';
 $string['newaccount'] = 'New account';
+$string['newactivityname'] = 'New name for activity {$a}';
 $string['newcourse'] = 'New course';
 $string['newpassword'] = 'New password';
 $string['newpassword_help'] = 'Enter a new password or leave blank to keep current password.';
index f50c23f..32362d1 100644 (file)
@@ -4110,8 +4110,7 @@ function sort_by_roleassignment_authority($users, context $context, $roles = arr
  * system is more flexible. If you really need, you can to use this
  * function but consider has_capability() as a possible substitute.
  *
- * The caller function is responsible for including all the
- * $sort fields in $fields param.
+ * All $sort fields are added into $fields if not present there yet.
  *
  * If $roleid is an array or is empty (all roles) you need to set $fields
  * (and $sort by extension) params according to it, as the first field
@@ -4209,6 +4208,24 @@ function get_role_users($roleid, context $context, $parent = false, $fields = ''
         $params = array_merge($params, $sortparams);
     }
 
+    // Adding the fields from $sort that are not present in $fields.
+    $sortarray = preg_split('/,\s*/', $sort);
+    $fieldsarray = preg_split('/,\s*/', $fields);
+    $addedfields = array();
+    foreach ($sortarray as $sortfield) {
+        // Throw away any additional arguments to the sort (e.g. ASC/DESC).
+        list ($sortfield) = explode(' ', $sortfield);
+        if (!in_array($sortfield, $fieldsarray)) {
+            $fieldsarray[] = $sortfield;
+            $addedfields[] = $sortfield;
+        }
+    }
+    $fields = implode(', ', $fieldsarray);
+    if (!empty($addedfields)) {
+        $addedfields = implode(', ', $addedfields);
+        debugging('get_role_users() adding '.$addedfields.' to the query result because they were required by $sort but missing in $fields');
+    }
+
     if ($all === null) {
         // Previously null was used to indicate that parameter was not used.
         $all = true;
index efe954b..819dba9 100644 (file)
@@ -102,6 +102,13 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
      */
     protected function find($selector, $locator, $exception = false, $node = false, $timeout = false) {
 
+        // Throw exception, so dev knows it is not supported.
+        if ($selector === 'named') {
+            $exception = 'Using the "named" selector is deprecated as of 3.1. '
+                .' Use the "named_partial" or use the "named_exact" selector instead.';
+            throw new ExpectationException($exception, $this->getSession());
+        }
+
         // Returns the first match.
         $items = $this->find_all($selector, $locator, $exception, $node, $timeout);
         return count($items) ? reset($items) : null;
@@ -122,11 +129,18 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
      */
     protected function find_all($selector, $locator, $exception = false, $node = false, $timeout = false) {
 
+        // Throw exception, so dev knows it is not supported.
+        if ($selector === 'named') {
+            $exception = 'Using the "named" selector is deprecated as of 3.1. '
+                .' Use the "named_partial" or use the "named_exact" selector instead.';
+            throw new ExpectationException($exception, $this->getSession());
+        }
+
         // Generic info.
         if (!$exception) {
 
             // With named selectors we can be more specific.
-            if ($selector == 'named') {
+            if (($selector == 'named_exact') || ($selector == 'named_partial')) {
                 $exceptiontype = $locator[0];
                 $exceptionlocator = $locator[1];
 
@@ -236,7 +250,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
 
         // Redirecting execution to the find method with the specified selector.
         // It will detect if it's pointing to an unexisting named selector.
-        return $this->find('named',
+        return $this->find('named_partial',
             array(
                 $cleanname,
                 $this->getSession()->getSelectorsHandler()->xpathLiteral($arguments[0])
@@ -637,4 +651,66 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
         }
         $this->getSession()->getDriver()->resizeWindow($width, $height);
     }
+
+    /**
+     * Waits for all the JS to be loaded.
+     *
+     * @throws \Exception
+     * @throws NoSuchWindow
+     * @throws UnknownError
+     * @return bool True or false depending whether all the JS is loaded or not.
+     */
+    public function wait_for_pending_js() {
+        // Waiting for JS is only valid for JS scenarios.
+        if (!$this->running_javascript()) {
+            return;
+        }
+
+        // We don't use behat_base::spin() here as we don't want to end up with an exception
+        // if the page & JSs don't finish loading properly.
+        for ($i = 0; $i < self::EXTENDED_TIMEOUT * 10; $i++) {
+            $pending = '';
+            try {
+                $jscode = '
+                    return function() {
+                        if (typeof M === "undefined") {
+                            if (document.readyState === "complete") {
+                                return "";
+                            } else {
+                                return "incomplete";
+                            }
+                        } else if (' . self::PAGE_READY_JS . ') {
+                            return "";
+                        } else {
+                            return M.util.pending_js.join(":");
+                        }
+                    }();';
+                $pending = $this->getSession()->evaluateScript($jscode);
+            } catch (NoSuchWindow $nsw) {
+                // We catch an exception here, in case we just closed the window we were interacting with.
+                // No javascript is running if there is no window right?
+                $pending = '';
+            } catch (UnknownError $e) {
+                // M is not defined when the window or the frame don't exist anymore.
+                if (strstr($e->getMessage(), 'M is not defined') != false) {
+                    $pending = '';
+                }
+            }
+
+            // If there are no pending JS we stop waiting.
+            if ($pending === '') {
+                return true;
+            }
+
+            // 0.1 seconds.
+            usleep(100000);
+        }
+
+        // Timeout waiting for JS to complete. It will be catched and forwarded to behat_hooks::i_look_for_exceptions().
+        // It is unlikely that Javascript code of a page or an AJAX request needs more than self::EXTENDED_TIMEOUT seconds
+        // to be loaded, although when pages contains Javascript errors M.util.js_complete() can not be executed, so the
+        // number of JS pending code and JS completed code will not match and we will reach this point.
+        throw new \Exception('Javascript code and/or AJAX requests are not ready after ' . self::EXTENDED_TIMEOUT .
+            ' seconds. There is a Javascript error or the code is extremely slow.');
+    }
 }
index 0108064..54049f5 100644 (file)
@@ -42,6 +42,11 @@ require_once(__DIR__ . '/../../testing/classes/tests_finder.php');
  */
 class behat_config_manager {
 
+    /**
+     * @var bool Keep track of the automatic profile conversion. So we can notify user.
+     */
+    public static $autoprofileconversion = false;
+
     /**
      * Updates a config file
      *
@@ -397,47 +402,160 @@ class behat_config_manager {
             $CFG->behat_wwwroot = 'http://itwillnotbeused.com';
         }
 
-        $basedir = $CFG->dirroot . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'behat';
-
+        // Comments use black color, so failure path is not visible. Using color other then black/white is safer.
+        // https://github.com/Behat/Behat/pull/628.
         $config = array(
             'default' => array(
-                'paths' => array(
-                    'features' => $basedir . DIRECTORY_SEPARATOR . 'features',
-                    'bootstrap' => $basedir . DIRECTORY_SEPARATOR . 'features' . DIRECTORY_SEPARATOR . 'bootstrap',
+                'formatters' => array(
+                    'moodle_progress' => array(
+                        'output_styles' => array(
+                            'comment' => array('magenta'))
+                        )
                 ),
-                'context' => array(
-                    'class' => 'behat_init_context'
+                'suites' => array(
+                    'default' => array(
+                        'paths' => $features,
+                        'contexts' => array_keys($stepsdefinitions)
+                    )
                 ),
                 'extensions' => array(
-                    'Behat\MinkExtension\Extension' => array(
+                    'Behat\MinkExtension' => array(
                         'base_url' => $CFG->behat_wwwroot,
                         'goutte' => null,
                         'selenium2' => $selenium2wdhost
                     ),
-                    'Moodle\BehatExtension\Extension' => array(
-                        'formatters' => array(
-                            'moodle_progress' => 'Moodle\BehatExtension\Formatter\MoodleProgressFormatter',
-                            'moodle_list' => 'Moodle\BehatExtension\Formatter\MoodleListFormatter',
-                            'moodle_step_count' => 'Moodle\BehatExtension\Formatter\MoodleStepCountFormatter'
-                        ),
-                        'features' => $features,
+                    'Moodle\BehatExtension' => array(
+                        'moodledirroot' => $CFG->dirroot,
                         'steps_definitions' => $stepsdefinitions
                     )
-                ),
-                'formatter' => array(
-                    'name' => 'moodle_progress'
                 )
             )
         );
 
         // In case user defined overrides respect them over our default ones.
         if (!empty($CFG->behat_config)) {
-            $config = self::merge_config($config, $CFG->behat_config);
+            foreach ($CFG->behat_config as $profile => $values) {
+                $config = self::merge_config($config, self::merge_behat_config($profile, $values));
+            }
+        }
+        // Check for Moodle custom ones.
+        if (!empty($CFG->behat_profiles) && is_array($CFG->behat_profiles)) {
+            foreach ($CFG->behat_profiles as $profile => $values) {
+                $config = self::merge_config($config, self::get_behat_profile($profile, $values));
+            }
         }
 
         return Symfony\Component\Yaml\Yaml::dump($config, 10, 2);
     }
 
+    /**
+     * Parse $CFG->behat_config and return the array with required config structure for behat.yml
+     *
+     * @param string $profile profile name
+     * @param array $values values for profile
+     * @return array
+     */
+    protected static function merge_behat_config($profile, $values) {
+        // Only add profile which are compatible with Behat 3.x
+        // Just check if any of Bheat 2.5 config is set. Not checking for 3.x as it might have some other configs
+        // Like : rerun_cache etc.
+        if (!isset($values['filters']['tags']) && !isset($values['extensions']['Behat\MinkExtension\Extension'])) {
+            return array($profile => $values);
+        }
+
+        // Parse 2.5 format and get related values.
+        $oldconfigvalues = array();
+        if (isset($values['extensions']['Behat\MinkExtension\Extension'])) {
+            $extensionvalues = $values['extensions']['Behat\MinkExtension\Extension'];
+            if (isset($extensionvalues['selenium2']['browser'])) {
+                $oldconfigvalues['browser'] = $extensionvalues['selenium2']['browser'];
+            }
+            if (isset($extensionvalues['selenium2']['wd_host'])) {
+                $oldconfigvalues['wd_host'] = $extensionvalues['selenium2']['wd_host'];
+            }
+            if (isset($extensionvalues['capabilities'])) {
+                $oldconfigvalues['capabilities'] = $extensionvalues['capabilities'];
+            }
+        }
+
+        if (isset($values['filters']['tags'])) {
+            $oldconfigvalues['tags'] = $values['filters']['tags'];
+        }
+
+        if (!empty($oldconfigvalues)) {
+            self::$autoprofileconversion = true;
+            return self::get_behat_profile($profile, $oldconfigvalues);
+        }
+
+        // If nothing set above then return empty array.
+        return array();
+    }
+
+    /**
+     * Parse $CFG->behat_profile and return the array with required config structure for behat.yml.
+     *
+     * $CFG->behat_profiles = array(
+     *     'profile' = array(
+     *         'browser' => 'firefox',
+     *         'tags' => '@javascript',
+     *         'wd_host' => 'http://127.0.0.1:4444/wd/hub',
+     *         'capabilities' => array(
+     *             'platform' => 'Linux',
+     *             'version' => 44
+     *         )
+     *     )
+     * );
+     *
+     * @param string $profile profile name
+     * @param array $values values for profile.
+     * @return array
+     */
+    protected static function get_behat_profile($profile, $values) {
+        // Values should be an array.
+        if (!is_array($values)) {
+            return array();
+        }
+
+        // Check suite values.
+        $behatprofilesuites = array();
+        // Fill tags information.
+        if (isset($values['tags'])) {
+            $behatprofilesuites = array(
+                'suites' => array(
+                    'default' => array(
+                        'filters' => array(
+                            'tags' => $values['tags'],
+                        )
+                    )
+                )
+            );
+        }
+
+        // Selenium2 config values.
+        $behatprofileextension = array();
+        $seleniumconfig = array();
+        if (isset($values['browser'])) {
+            $seleniumconfig['browser'] = $values['browser'];
+        }
+        if (isset($values['wd_host'])) {
+            $seleniumconfig['wd_host'] = $values['wd_host'];
+        }
+        if (isset($values['capabilities'])) {
+            $seleniumconfig['capabilities'] = $values['capabilities'];
+        }
+        if (!empty($seleniumconfig)) {
+            $behatprofileextension = array(
+                'extensions' => array(
+                    'Behat\MinkExtension' => array(
+                        'selenium2' => $seleniumconfig,
+                    )
+                )
+            );
+        }
+
+        return array($profile => array_merge($behatprofilesuites, $behatprofileextension));
+    }
+
     /**
      * Attempt to split feature list into fairish buckets using timing information, if available.
      * Simply add each one to lightest buckets until all files allocated.
index 0223495..2d68166 100644 (file)
@@ -25,7 +25,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-use \Behat\Behat\Context\BehatContext;
+use Behat\Testwork\Environment\Environment;
 
 /**
  * Helper to get behat contexts.
@@ -38,18 +38,20 @@ use \Behat\Behat\Context\BehatContext;
 class behat_context_helper {
 
     /**
-     * @var BehatContext main behat context.
+     * Behat environment.
+     *
+     * @var Environment
      */
-    protected static $maincontext = false;
+    protected static $environment = null;
 
     /**
-     * Save main behat context reference to be used for finding sub-contexts.
+     * Sets the browser session.
      *
-     * @param BehatContext $maincontext
+     * @param Environment $environment
      * @return void
      */
-    public static function set_main_context(BehatContext $maincontext) {
-        self::$maincontext = $maincontext;
+    public static function set_session(Environment $environment) {
+        self::$environment = $environment;
     }
 
     /**
@@ -65,7 +67,7 @@ class behat_context_helper {
      */
     public static function get($classname) {
 
-        if (!$subcontext = self::$maincontext->getSubcontextByClassName($classname)) {
+        if (!$subcontext = self::$environment->getContext($classname)) {
             throw coding_exception('The required "' . $classname . '" class does not exist');
         }
 
index 76921aa..c328f5d 100644 (file)
@@ -158,7 +158,7 @@ XPATH
         } else {
             // Named selectors uses arrays as locators including the type of named selector.
             $locator = array($selectortype, $session->getSelectorsHandler()->xpathLiteral($element));
-            $selector = 'named';
+            $selector = 'named_partial';
         }
 
         return array($selector, $locator);
@@ -173,7 +173,7 @@ XPATH
     public static function register_moodle_selectors(Behat\Mink\Session $session) {
 
         foreach (self::get_moodle_selectors() as $name => $xpath) {
-            $session->getSelectorsHandler()->getSelector('named')->registerNamedXpath($name, $xpath);
+            $session->getSelectorsHandler()->getSelector('named_partial')->registerNamedXpath($name, $xpath);
         }
     }
 
index 69a9cbc..dc568b0 100644 (file)
@@ -58,10 +58,7 @@ class behat_form_checkbox extends behat_form_field {
             $this->field->click();
 
             // Trigger the onchange event as triggered when 'checking' the checkbox.
-            $this->session->getDriver()->triggerSynScript(
-                $this->field->getXPath(),
-                "Syn.trigger('change', {}, {{ELEMENT}})"
-            );
+            $this->trigger_on_change();
 
         } else if (empty($value) && $this->field->isChecked()) {
 
@@ -74,10 +71,7 @@ class behat_form_checkbox extends behat_form_field {
             $this->field->click();
 
             // Trigger the onchange event as triggered when 'checking' the checkbox.
-            $this->session->getDriver()->triggerSynScript(
-                $this->field->getXPath(),
-                "Syn.trigger('change', {}, {{ELEMENT}})"
-            );
+            $this->trigger_on_change();
         }
     }
 
@@ -110,4 +104,13 @@ class behat_form_checkbox extends behat_form_field {
         return false;
     }
 
+    /**
+     * Trigger on change event.
+     */
+    protected function trigger_on_change() {
+        $this->session->getDriver()->triggerSynScript(
+            $this->field->getXPath(),
+            "Syn.trigger('change', {}, {{ELEMENT}})"
+        );
+    }
 }
index 8304133..22e064c 100644 (file)
@@ -59,7 +59,7 @@ class behat_form_radio extends behat_form_checkbox {
      * @return string The value attribute
      */
     public function get_value() {
-        return (bool)$this->field->getAttribute('checked');
+        return $this->field->isSelected();
     }
 
     /**
@@ -75,20 +75,17 @@ class behat_form_radio extends behat_form_checkbox {
     public function set_value($value) {
 
         if ($this->running_javascript()) {
-            parent::set_value($value);
+            // Check on radio button.
+            $this->field->click();
+
+            // Trigger the onchange event as triggered when 'selecting' the radio.
+            if (!empty($value) && !$this->field->isSelected()) {
+                $this->trigger_on_change();
+            }
         } else {
             // Goutte does not accept a check nor a click in an input[type=radio].
             $this->field->setValue($this->field->getAttribute('value'));
         }
     }
 
-    /**
-     * Returns whether the provided value matches the current value or not.
-     *
-     * @param string $expectedvalue
-     * @return bool
-     */
-    public function matches($expectedvalue = false) {
-        return $this->text_matches($expectedvalue);
-    }
 }
index 6bc66a8..880d061 100644 (file)
@@ -49,28 +49,14 @@ class behat_form_select extends behat_form_field {
      */
     public function set_value($value) {
 
-        // In some browsers we select an option and it triggers all the
-        // autosubmits and works as expected but not in all of them, so we
-        // try to catch all the possibilities to make this function work as
-        // expected.
-
-        // Get the internal id of the element we are going to click.
-        // This kind of internal IDs are only available in the selenium wire
-        // protocol, so only available using selenium drivers, phantomjs and family.
-        if ($this->running_javascript()) {
-            $currentelementid = $this->get_internal_field_id();
-        }
-
         // Is the select multiple?
         $multiple = $this->field->hasAttribute('multiple');
-
-        // By default, assume the passed value is a non-multiple option.
-        $options = array(trim($value));
+        $singleselect = ($this->field->hasClass('singleselect') || $this->field->hasClass('urlselect'));
 
         // Here we select the option(s).
         if ($multiple) {
             // Split and decode values. Comma separated list of values allowed. With valuable commas escaped with backslash.
-            $options = preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $value));
+            $options = preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', trim($value)));
             // This is a multiple select, let's pass the multiple flag after first option.
             $afterfirstoption = false;
             foreach ($options as $option) {
@@ -78,126 +64,21 @@ class behat_form_select extends behat_form_field {
                 $afterfirstoption = true;
             }
         } else {
-            // If value is already set then don't set it again.
-            if ($this->field->getValue() == $value) {
-                return;
-            } else {
-                $opt = $this->field->find('named', array(
-                    'option', $this->field->getSession()->getSelectorsHandler()->xpathLiteral($value)
-                ));
-                if ($opt && ($this->field->getValue() == $opt->getValue())) {
-                    return;
-                }
-            }
-
-            // If not running JS or not a singleselect then use selectOption.
-            // For singleselect only click event is enough.
-            if (!$this->running_javascript() ||
-                !($this->field->hasClass('singleselect') || $this->field->hasClass('urlselect'))) {
-
-                // This is a single select, let's pass the last one specified.
-                $this->field->selectOption(end($options));
-            }
-        }
-
-        // With JS disabled this is enough and we finish here.
-        if (!$this->running_javascript()) {
-            return;
-        }
-
-        // With JS enabled we add more clicks as some selenium
-        // drivers requires it to fire JS events.
-
-        // In some browsers the selectOption actions can perform a form submit or reload page
-        // so we need to ensure the element is still available to continue interacting
-        // with it. We don't wait here.
-        // getXpath() does not send a query to selenium, so we don't need to wrap it in a try & catch.
-        $selectxpath = $this->field->getXpath();
-        if (!$this->session->getDriver()->find($selectxpath)) {
-            return;
-        }
-
-        // We also check the selenium internal element id, if it have changed
-        // we are dealing with an autosubmit that was already executed, and we don't to
-        // execute anything else as the action we wanted was already performed.
-        if ($currentelementid != $this->get_internal_field_id()) {
-            return;
-        }
+           // By default, assume the passed value is a non-multiple option.
+            $this->field->selectOption(trim($value));
+       }
 
         // Wait for all the possible AJAX requests that have been
         // already triggered by selectOption() to be finished.
-        $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
-
-        // Wrapped in try & catch as the element may disappear if an AJAX request was submitted.
-        try {
-            $multiple = $this->field->hasAttribute('multiple');
-        } catch (Exception $e) {
-            // We do not specify any specific Exception type as there are
-            // different exceptions that can be thrown by the driver and
-            // we can not control them all, also depending on the selenium
-            // version the exception type can change.
-            return;
-        }
-
-        // Single select sometimes needs an extra click in the option.
-        if (!$multiple) {
-
-            // Var $options only contains 1 option.
-            $optionxpath = $this->get_option_xpath(end($options), $selectxpath);
-
-            // Using the driver direcly because Element methods are messy when dealing
-            // with elements inside containers.
-            if ($optionnodes = $this->session->getDriver()->find($optionxpath)) {
-
-                // Wrapped in a try & catch as we can fall into race conditions
-                // and the element may not be there.
-                try {
-                    current($optionnodes)->click();
-                } catch (Exception $e) {
-                    // We continue and return as this means that the element is not there or it is not the same.
-                    return;
-                }
-            }
-
-        } else {
-
-            // Wrapped in a try & catch as we can fall into race conditions
-            // and the element may not be there.
-            try {
-                // Multiple ones needs the click in the select.
-                $this->field->click();
-            } catch (Exception $e) {
-                // We continue and return as this means that the element is not there or it is not the same.
-                return;
-            }
-
-            // We also check that the option(s) are still there. We neither wait.
-            foreach ($options as $option) {
-                $optionxpath = $this->get_option_xpath($option, $selectxpath);
-                if (!$this->session->getDriver()->find($optionxpath)) {
-                    return;
-                }
+        if ($this->running_javascript()) {
+            // Trigger change event as this is needed by some drivers (Phantomjs). Don't do it for
+            // Singleselect as this will cause multiple event fire and lead to race-around condition.
+            $browser = \Moodle\BehatExtension\Driver\MoodleSelenium2Driver::getBrowser();
+            if (!$singleselect && ($browser == 'phantomjs')) {
+                $script = "Syn.trigger('change', {}, {{ELEMENT}})";
+                $this->session->getDriver()->triggerSynScript($this->field->getXpath(), $script);
             }
-
-            // Wait for all the possible AJAX requests that have been
-            // already triggered by clicking on the field to be finished.
             $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
-
-            // Wrapped in a try & catch as we can fall into race conditions
-            // and the element may not be there.
-            try {
-
-                // Repeating the select(s) as some drivers (chrome that I know) are moving
-                // to another option after the general select field click above.
-                $afterfirstoption = false;
-                foreach ($options as $option) {
-                    $this->field->selectOption(trim($option), $afterfirstoption);
-                    $afterfirstoption = true;
-                }
-            } catch (Exception $e) {
-                // We continue and return as this means that the element is not there or it is not the same.
-                return;
-            }
         }
     }
 
@@ -233,9 +114,6 @@ class behat_form_select extends behat_form_field {
 
         // We are dealing with a multi-select.
 
-        // Can pass multiple comma separated, with valuable commas escaped with backslash.
-        $expectedarr = array(); // Array of passed text options to test.
-
         // Unescape + trim all options and flip it to have the expected values as keys.
         $expectedoptions = $this->get_unescaped_options($expectedvalue);
 
@@ -306,45 +184,23 @@ class behat_form_select extends behat_form_field {
 
         $selectedoptions = array(); // To accumulate found selected options.
 
-        // Selenium getValue() implementation breaks - separates - values having
-        // commas within them, so we'll be looking for options with the 'selected' attribute instead.
-        if ($this->running_javascript()) {
-            // Get all the options in the select and extract their value/text pairs.
-            $alloptions = $this->field->findAll('xpath', '//option');
-            foreach ($alloptions as $option) {
-                // Is it selected?
-                if ($option->hasAttribute('selected')) {
-                    if ($multiple) {
-                        // If the select is multiple, text commas must be encoded.
-                        $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
-                    } else {
-                        $selectedoptions[] = trim($option->{$method}());
-                    }
-                }
-            }
-
-        } else {
-            // Goutte does not keep the 'selected' attribute updated, but its getValue() returns
-            // the selected elements correctly, also those having commas within them.
-
-            // Goutte returns the values as an array or as a string depending
-            // on whether multiple options are selected or not.
-            $values = $this->field->getValue();
-            if (!is_array($values)) {
-                $values = array($values);
-            }
+        // Driver returns the values as an array or as a string depending
+        // on whether multiple options are selected or not.
+        $values = $this->field->getValue();
+        if (!is_array($values)) {
+            $values = array($values);
+        }
 
-            // Get all the options in the select and extract their value/text pairs.
-            $alloptions = $this->field->findAll('xpath', '//option');
-            foreach ($alloptions as $option) {
-                // Is it selected?
-                if (in_array($option->getValue(), $values)) {
-                    if ($multiple) {
-                        // If the select is multiple, text commas must be encoded.
-                        $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
-                    } else {
-                        $selectedoptions[] = trim($option->{$method}());
-                    }
+        // Get all the options in the select and extract their value/text pairs.
+        $alloptions = $this->field->findAll('xpath', '//option');
+        foreach ($alloptions as $option) {
+            // Is it selected?
+            if (in_array($option->getValue(), $values)) {
+                if ($multiple) {
+                    // If the select is multiple, text commas must be encoded.
+                    $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
+                } else {
+                    $selectedoptions[] = trim($option->{$method}());
                 }
             }
         }
index 7e9ea69..3d560cf 100644 (file)
@@ -57,7 +57,7 @@ class recordset_walk implements \Iterator {
     protected $callback;
 
     /**
-     * @var array|false Extra params for the callback.
+     * @var mixed|null Extra param for the callback.
      */
     protected $callbackextra;
 
@@ -66,9 +66,9 @@ class recordset_walk implements \Iterator {
      *
      * @param \moodle_recordset $recordset Recordset to iterate.
      * @param callable $callback Apply this function to each record. If using a method, it should be public.
-     * @param array $callbackextra Array of arguments to pass to the callback.
+     * @param mixed $callbackextra An extra single parameter to pass to the callback. Use a container to pass multiple values.
      */
-    public function __construct(\moodle_recordset $recordset, callable $callback, $callbackextra = false) {
+    public function __construct(\moodle_recordset $recordset, callable $callback, $callbackextra = null) {
         $this->recordset = $recordset;
         $this->callback = $callback;
         $this->callbackextra = $callbackextra;
@@ -99,10 +99,10 @@ class recordset_walk implements \Iterator {
         }
 
         // Apply callback and return.
-        if ($this->callbackextra) {
-            return call_user_func($this->callback, $record);
-        } else {
+        if (!is_null($this->callbackextra)) {
             return call_user_func($this->callback, $record, $this->callbackextra);
+        } else {
+            return call_user_func($this->callback, $record);
         }
     }
 
index 7f4fc16..6dbdc1e 100644 (file)
@@ -271,4 +271,16 @@ $definitions = array(
         'staticaccelerationsize' => 3
     ),
 
+    // Grade categories. Stored at request level as invalidation is very aggressive.
+    'grade_categories' => array(
+        'mode' => cache_store::MODE_REQUEST,
+        'simplekeys' => true,
+    ),
+
+    // Store temporary tables information.
+    'temp_tables' => array(
+        'mode' => cache_store::MODE_REQUEST,
+        'simplekeys' => true,
+        'simpledata' => true
+    )
 );
index a909f33..aa01e3c 100644 (file)
@@ -70,10 +70,11 @@ class database_manager {
      * This function will execute an array of SQL commands.
      *
      * @param string[] $sqlarr Array of sql statements to execute.
+     * @param array|null $tablenames an array of xmldb table names affected by this request.
      * @throws ddl_change_structure_exception This exception is thrown if any error is found.
      */
-    protected function execute_sql_arr(array $sqlarr) {
-        $this->mdb->change_database_structure($sqlarr);
+    protected function execute_sql_arr(array $sqlarr, $tablenames = null) {
+        $this->mdb->change_database_structure($sqlarr, $tablenames);
     }
 
     /**
@@ -107,6 +108,12 @@ class database_manager {
     public function reset_sequence($table) {
         if (!is_string($table) and !($table instanceof xmldb_table)) {
             throw new ddl_exception('ddlunknownerror', NULL, 'incorrect table parameter!');
+        } else {
+            if ($table instanceof xmldb_table) {
+                $tablename = $table->getName();
+            } else {
+                $tablename = $table;
+            }
         }
 
         // Do not test if table exists because it is slow
@@ -115,7 +122,7 @@ class database_manager {
             throw new ddl_exception('ddlunknownerror', null, 'table reset sequence sql not generated');
         }
 
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($tablename));
     }
 
     /**
@@ -322,8 +329,7 @@ class database_manager {
         if (!$sqlarr = $this->generator->getDropTableSQL($xmldb_table)) {
             throw new ddl_exception('ddlunknownerror', null, 'table drop sql not generated');
         }
-
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -409,7 +415,14 @@ class database_manager {
         if (!$sqlarr = $this->generator->getCreateStructureSQL($xmldb_structure)) {
             return; // nothing to do
         }
-        $this->execute_sql_arr($sqlarr);
+
+        $tablenames = array();
+        foreach ($xmldb_structure as $xmldb_table) {
+            if ($xmldb_table instanceof xmldb_table) {
+                $tablenames[] = $xmldb_table->getName();
+            }
+        }
+        $this->execute_sql_arr($sqlarr, $tablenames);
     }
 
     /**
@@ -428,7 +441,7 @@ class database_manager {
         if (!$sqlarr = $this->generator->getCreateTableSQL($xmldb_table)) {
             throw new ddl_exception('ddlunknownerror', null, 'table create sql not generated');
         }
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -451,8 +464,7 @@ class database_manager {
         if (!$sqlarr = $this->generator->getCreateTempTableSQL($xmldb_table)) {
             throw new ddl_exception('ddlunknownerror', null, 'temp table create sql not generated');
         }
-
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -530,7 +542,7 @@ class database_manager {
         if (!$sqlarr = $this->generator->getAddFieldSQL($xmldb_table, $xmldb_field)) {
             throw new ddl_exception('ddlunknownerror', null, 'addfield sql not generated');
         }
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -555,7 +567,7 @@ class database_manager {
             throw new ddl_exception('ddlunknownerror', null, 'drop_field sql not generated');
         }
 
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -580,7 +592,7 @@ class database_manager {
             return; // probably nothing to do
         }
 
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -643,7 +655,7 @@ class database_manager {
             return; //Empty array = nothing to do = no error
         }
 
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -687,7 +699,7 @@ class database_manager {
             return; //Empty array = nothing to do = no error
         }
 
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -741,7 +753,7 @@ class database_manager {
             return; //Empty array = nothing to do = no error
         }
 
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -760,7 +772,7 @@ class database_manager {
             return; //Empty array = nothing to do = no error
         }
 
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -784,7 +796,7 @@ class database_manager {
             throw new ddl_exception('ddlunknownerror', null, 'Some DBs do not support key renaming (MySQL, PostgreSQL, MsSQL). Rename skipped');
         }
 
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -811,7 +823,7 @@ class database_manager {
             throw new ddl_exception('ddlunknownerror', null, 'add_index sql not generated');
         }
 
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -838,7 +850,7 @@ class database_manager {
             throw new ddl_exception('ddlunknownerror', null, 'drop_index sql not generated');
         }
 
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
@@ -870,7 +882,7 @@ class database_manager {
             throw new ddl_exception('ddlunknownerror', null, 'Some DBs do not support index renaming (MySQL). Rename skipped');
         }
 
-        $this->execute_sql_arr($sqlarr);
+        $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
     }
 
     /**
index 8e50fe1..356fb71 100644 (file)
@@ -226,9 +226,12 @@ abstract class sql_generator {
             $tablename = $table->getName();
         }
 
+        if ($this->temptables->is_temptable($tablename)) {
+            return true;
+        }
+
         // Get all tables in moodle database.
         $tables = $this->mdb->get_tables();
-
         return isset($tables[$tablename]);
     }
 
index 5b806b1..b787c16 100644 (file)
@@ -4408,3 +4408,54 @@ function get_clam_error_code($returncode) {
     $antivirus = \core\antivirus\manager::get_antivirus('clamav');
     return $antivirus->get_clam_error_code($returncode);
 }
+
+/**
+ * Returns the rename action.
+ *
+ * @deprecated since 3.1
+ * @param cm_info $mod The module to produce editing buttons for
+ * @param int $sr The section to link back to (used for creating the links)
+ * @return The markup for the rename action, or an empty string if not available.
+ */
+function course_get_cm_rename_action(cm_info $mod, $sr = null) {
+    global $COURSE, $OUTPUT;
+
+    static $str;
+    static $baseurl;
+
+    debugging('Function course_get_cm_rename_action() is deprecated. Please use inplace_editable ' .
+        'https://docs.moodle.org/dev/Inplace_editable', DEBUG_DEVELOPER);
+
+    $modcontext = context_module::instance($mod->id);
+    $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
+
+    if (!isset($str)) {
+        $str = get_strings(array('edittitle'));
+    }
+
+    if (!isset($baseurl)) {
+        $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
+    }
+
+    if ($sr !== null) {
+        $baseurl->param('sr', $sr);
+    }
+
+    // AJAX edit title.
+    if ($mod->has_view() && $hasmanageactivities && course_ajax_enabled($COURSE) &&
+        (($mod->course == $COURSE->id) || ($mod->course == SITEID))) {
+        // we will not display link if we are on some other-course page (where we should not see this module anyway)
+        return html_writer::span(
+            html_writer::link(
+                new moodle_url($baseurl, array('update' => $mod->id)),
+                $OUTPUT->pix_icon('t/editstring', '', 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
+                array(
+                    'class' => 'editing_title',
+                    'data-action' => 'edittitle',
+                    'title' => $str->edittitle,
+                )
+            )
+        );
+    }
+    return '';
+}
index 1573ef5..e78005d 100644 (file)
@@ -336,6 +336,17 @@ abstract class moodle_database {
         return cache::make('core', 'databasemeta', $properties);
     }
 
+    /**
+     * Handle the creation and caching of the temporary tables.
+     *
+     * @return cache_application The temp_tables cachestore to complete operations on.
+     */
+    protected function get_temp_tables_cache() {
+        // Using connection data to prevent collisions when using the same temp table name with different db connections.
+        $properties = array('dbfamily' => $this->get_dbfamily(), 'settings' => $this->get_settings_hash());
+        return cache::make('core', 'temp_tables', $properties);
+    }
+
     /**
      * Attempt to create the database
      * @param string $dbhost The database host.
@@ -1040,13 +1051,30 @@ abstract class moodle_database {
 
     /**
      * Resets the internal column details cache
+     *
+     * @param array|null $tablenames an array of xmldb table names affected by this request.
      * @return void
      */
-    public function reset_caches() {
-        $this->tables = null;
-        // Purge MUC as well.
-        $this->get_metacache()->purge();
-        $this->metacache = null;
+    public function reset_caches($tablenames = null) {
+        if (!empty($tablenames)) {
+            $dbmetapurged = false;
+            foreach ($tablenames as $tablename) {
+                if ($this->temptables->is_temptable($tablename)) {
+                    $this->get_temp_tables_cache()->delete($tablename);
+                } else if ($dbmetapurged === false) {
+                    $this->tables = null;
+                    $this->get_metacache()->purge();
+                    $this->metacache = null;
+                    $dbmetapurged = true;
+                }
+            }
+        } else {
+            $this->get_temp_tables_cache()->purge();
+            $this->tables = null;
+            // Purge MUC as well.
+            $this->get_metacache()->purge();
+            $this->metacache = null;
+        }
     }
 
     /**
@@ -1115,10 +1143,11 @@ abstract class moodle_database {
     /**
      * Do NOT use in code, this is for use by database_manager only!
      * @param string|array $sql query or array of queries
+     * @param array|null $tablenames an array of xmldb table names affected by this request.
      * @return bool true
      * @throws ddl_change_structure_exception A DDL specific exception is thrown for any errors.
      */
-    public&n