Merge branch 'MDL-66135' of https://github.com/paulholden/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Fri, 1 May 2020 14:07:56 +0000 (16:07 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Fri, 1 May 2020 14:07:56 +0000 (16:07 +0200)
315 files changed:
.eslintignore
.github/FUNDING.yml [new file with mode: 0644]
.stylelintignore
.travis.yml
admin/message.php
admin/renderer.php
admin/settings/development.php
admin/tool/messageinbound/lang/en/tool_messageinbound.php
admin/tool/recyclebin/tests/behat/basic_functionality.feature
admin/tool/task/lang/en/tool_task.php
admin/tool/xmldb/actions/check_indexes/check_indexes.class.php
admin/tool/xmldb/lang/en/tool_xmldb.php
blocks/site_main_menu/tests/behat/add_url.feature
cache/classes/helper.php
cache/classes/loaders.php
cache/classes/store.php
cache/tests/cache_test.php
cache/upgrade.txt
composer.json
composer.lock
config-dist.php
contentbank/amd/build/actions.min.js [new file with mode: 0644]
contentbank/amd/build/actions.min.js.map [new file with mode: 0644]
contentbank/amd/src/actions.js [new file with mode: 0644]
contentbank/classes/content.php
contentbank/classes/contentbank.php
contentbank/classes/contenttype.php
contentbank/classes/external/delete_content.php [new file with mode: 0644]
contentbank/classes/privacy/provider.php
contentbank/contenttype/h5p/classes/content.php
contentbank/contenttype/h5p/classes/contenttype.php
contentbank/contenttype/h5p/lang/en/contenttype_h5p.php
contentbank/contenttype/h5p/tests/behat/admin_upload_content.feature
contentbank/contenttype/h5p/tests/contenttype_h5p_test.php
contentbank/index.php
contentbank/templates/bankcontent.mustache
contentbank/templates/toolbar.mustache
contentbank/tests/behat/delete_content.feature [new file with mode: 0644]
contentbank/tests/contentbank_test.php
contentbank/tests/contenttype_test.php
contentbank/tests/external/delete_content_test.php [new file with mode: 0644]
contentbank/tests/generator/lib.php [new file with mode: 0644]
contentbank/tests/privacy_test.php [new file with mode: 0644]
contentbank/view.php
course/classes/management/helper.php
course/tests/behat/activity_chooser.feature
grade/grading/form/guide/lang/en/gradingform_guide.php
grade/grading/form/rubric/renderer.php
grade/grading/form/rubric/styles.css
h5p/classes/api.php
h5p/classes/helper.php
h5p/classes/player.php
h5p/h5plib/v124/lang/en/h5plib_v124.php
h5p/tests/api_test.php
h5p/tests/helper_test.php
install/lang/eu/install.php
install/lang/om/install.php [new file with mode: 0644]
install/lang/om/moodle.php [new file with mode: 0644]
install/lang/ps/langconfig.php
lang/en/admin.php
lang/en/cache.php
lang/en/contentbank.php
lang/en/course.php
lang/en/enrol.php
lang/en/error.php
lang/en/h5p.php
lang/en/moodle.php
lang/en/repository.php
lang/en/role.php
lib/ajax/getnavbranch.php
lib/ajax/service.php
lib/amd/build/chart_base.min.js
lib/amd/build/chart_base.min.js.map
lib/amd/build/chart_output_chartjs.min.js
lib/amd/build/chart_output_chartjs.min.js.map
lib/amd/build/chart_series.min.js
lib/amd/build/chart_series.min.js.map
lib/amd/build/custom_interaction_events.min.js
lib/amd/build/custom_interaction_events.min.js.map
lib/amd/build/form-autocomplete.min.js
lib/amd/build/form-autocomplete.min.js.map
lib/amd/src/chart_base.js
lib/amd/src/chart_output_chartjs.js
lib/amd/src/chart_series.js
lib/amd/src/custom_interaction_events.js
lib/amd/src/form-autocomplete.js
lib/antivirus/clamav/lang/en/antivirus_clamav.php
lib/behat/classes/behat_config_util.php
lib/behat/lib.php
lib/classes/chart_base.php
lib/classes/chart_series.php
lib/classes/component.php
lib/classes/filetypes.php
lib/classes/ip_utils.php
lib/classes/plugin_manager.php
lib/classes/plugininfo/contenttype.php
lib/classes/session/database.php
lib/classes/session/handler.php
lib/classes/session/manager.php
lib/classes/session/memcached.php
lib/classes/session/redis.php
lib/classes/task/manager.php
lib/classes/task/messaging_cleanup_task.php
lib/db/access.php
lib/db/services.php
lib/db/upgrade.php
lib/ddl/database_manager.php
lib/ddl/tests/ddl_test.php
lib/dtl/database_exporter.php
lib/dtl/database_importer.php
lib/externallib.php
lib/filelib.php
lib/filestorage/file_storage.php
lib/filestorage/file_system.php
lib/form/checkbox.php
lib/form/course.php
lib/form/dateselector.php
lib/form/datetimeselector.php
lib/form/duration.php
lib/form/tests/behat/autocomplete.feature
lib/form/tests/behat/behat_core_form.php [new file with mode: 0644]
lib/form/tests/behat/fixtures/repeat_defaults_form.php [new file with mode: 0644]
lib/form/tests/behat/repeat_defaults.feature [new file with mode: 0644]
lib/form/tests/fixtures/autocomplete-disabledif.php [new file with mode: 0644]
lib/formslib.php
lib/ldaplib.php
lib/moodlelib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/php-jwt/README.md
lib/php-jwt/composer.json
lib/php-jwt/src/BeforeValidException.php
lib/php-jwt/src/ExpiredException.php
lib/php-jwt/src/JWK.php [new file with mode: 0644]
lib/php-jwt/src/JWT.php
lib/php-jwt/src/SignatureInvalidException.php
lib/plist/LICENSE [new file with mode: 0644]
lib/plist/README.md [new file with mode: 0644]
lib/plist/classes/CFPropertyList/CFBinaryPropertyList.php [new file with mode: 0644]
lib/plist/classes/CFPropertyList/CFPropertyList.php [new file with mode: 0644]
lib/plist/classes/CFPropertyList/CFType.php [new file with mode: 0644]
lib/plist/classes/CFPropertyList/CFTypeDetector.php [new file with mode: 0644]
lib/plist/classes/CFPropertyList/IOException.php [new file with mode: 0644]
lib/plist/classes/CFPropertyList/PListException.php [new file with mode: 0644]
lib/plist/readme_moodle.txt [new file with mode: 0644]
lib/table/amd/build/dynamic.min.js
lib/table/amd/build/dynamic.min.js.map
lib/table/amd/build/local/dynamic/events.min.js [new file with mode: 0644]
lib/table/amd/build/local/dynamic/events.min.js.map [new file with mode: 0644]
lib/table/amd/build/local/dynamic/repository.min.js
lib/table/amd/build/local/dynamic/repository.min.js.map
lib/table/amd/build/local/dynamic/selectors.min.js
lib/table/amd/build/local/dynamic/selectors.min.js.map
lib/table/amd/src/dynamic.js
lib/table/amd/src/local/dynamic/events.js [moved from mod/quiz/accessrule/safebrowser/version.php with 70% similarity]
lib/table/amd/src/local/dynamic/repository.js
lib/table/amd/src/local/dynamic/selectors.js
lib/table/classes/external/dynamic/fetch.php
lib/table/tests/external/dynamic/fetch_test.php
lib/tablelib.php
lib/tcpdf/readme_moodle.txt
lib/tcpdf/tcpdf.php
lib/templates/single_select.mustache
lib/templates/url_select.mustache
lib/tests/behat/action_modal.feature
lib/tests/behat/alpha_chooser.feature
lib/tests/behat/behat_hooks.php
lib/tests/behat/behat_navigation.php
lib/tests/behat/largeforms.feature
lib/tests/behat/locking.feature
lib/tests/behat/permissionmanager.feature
lib/tests/behat/readonlyform.feature
lib/tests/behat/securelayout.feature
lib/tests/other/chartjstestpage.php
lib/tests/session_manager_test.php
lib/tests/session_redis_test.php
lib/thirdpartylibs.xml
lib/typo3/class.t3lib_cs.php
lib/typo3/readme_moodle.txt
lib/upgrade.txt
message/amd/build/message_preferences.min.js [new file with mode: 0644]
message/amd/build/message_preferences.min.js.map [new file with mode: 0644]
message/amd/src/message_preferences.js [new file with mode: 0644]
message/output/airnotifier/lang/en/message_airnotifier.php
message/output/lib.php
message/output/popup/db/services.php
message/output/popup/db/upgrade.php
message/output/popup/message_output_popup.php
message/output/popup/tests/messaging_cleanup_test.php [new file with mode: 0644]
message/output/popup/version.php
message/templates/message_preferences.mustache [new file with mode: 0644]
message/templates/message_preferences_component.mustache [new file with mode: 0644]
message/templates/message_preferences_notification_processor.mustache [new file with mode: 0644]
message/tests/behat/message_preferences.feature [new file with mode: 0644]
message/upgrade.txt
mod/assign/lang/en/assign.php
mod/forum/report/summary/templates/bulk_action_menu.mustache
mod/h5pactivity/lang/en/h5pactivity.php
mod/h5pactivity/pix/icon.png
mod/h5pactivity/pix/icon.svg
mod/h5pactivity/tests/behat/add_h5pactivity.feature
mod/h5pactivity/tests/behat/sending_attempt.feature
mod/h5pactivity/tests/privacy_test.php
mod/lesson/lang/en/lesson.php
mod/lti/db/caches.php [new file with mode: 0644]
mod/lti/edit_form.php
mod/lti/lang/en/lti.php
mod/lti/locallib.php
mod/lti/mod_form.php
mod/lti/tests/fixtures/test_keyset [new file with mode: 0644]
mod/lti/tests/locallib_test.php
mod/lti/token.php
mod/lti/version.php
mod/quiz/accessrule/safebrowser/rule.php [deleted file]
mod/quiz/accessrule/safebrowser/tests/rule_test.php [deleted file]
mod/quiz/accessrule/seb/amd/build/managetemplates.min.js [new file with mode: 0644]
mod/quiz/accessrule/seb/amd/build/managetemplates.min.js.map [new file with mode: 0644]
mod/quiz/accessrule/seb/amd/src/managetemplates.js [new file with mode: 0644]
mod/quiz/accessrule/seb/backup/moodle2/backup_quizaccess_seb_subplugin.class.php [new file with mode: 0644]
mod/quiz/accessrule/seb/backup/moodle2/restore_quizaccess_seb_subplugin.class.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/access_manager.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/config_key.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/event/access_prevented.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/event/template_created.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/event/template_deleted.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/event/template_disabled.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/event/template_enabled.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/event/template_updated.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/helper.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/hideif_rule.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/link_generator.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/local/form/template.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/local/table/template_list.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/privacy/provider.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/property_list.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/quiz_settings.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/settings_provider.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/template.php [new file with mode: 0644]
mod/quiz/accessrule/seb/classes/template_controller.php [new file with mode: 0644]
mod/quiz/accessrule/seb/config.php [new file with mode: 0644]
mod/quiz/accessrule/seb/db/access.php [new file with mode: 0644]
mod/quiz/accessrule/seb/db/caches.php [new file with mode: 0644]
mod/quiz/accessrule/seb/db/install.php [new file with mode: 0644]
mod/quiz/accessrule/seb/db/install.xml [new file with mode: 0644]
mod/quiz/accessrule/seb/db/upgrade.php [moved from mod/quiz/accessrule/safebrowser/lang/en/quizaccess_safebrowser.php with 53% similarity]
mod/quiz/accessrule/seb/lang/en/quizaccess_seb.php [new file with mode: 0644]
mod/quiz/accessrule/seb/lib.php [new file with mode: 0644]
mod/quiz/accessrule/seb/rule.php [new file with mode: 0644]
mod/quiz/accessrule/seb/settings.php [new file with mode: 0644]
mod/quiz/accessrule/seb/template.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/behat/edit_form.feature [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/generator/behat_quizaccess_seb_generator.php [moved from mod/quiz/accessrule/safebrowser/classes/privacy/provider.php with 53% similarity]
mod/quiz/accessrule/seb/tests/generator/lib.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/access_manager_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/backup_restore_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/base.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/config_key_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/event_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/helper_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/hideif_rule_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/link_generator_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/privacy_provider_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/property_list_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/quiz_settings_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/rule_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/sample_data/JSON_unencrypted_mac_001.txt [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/sample_data/encrypted.seb [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/sample_data/simpleunencrypted.seb [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/sample_data/simpleunencryptedwithoutoriginator.seb [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/sample_data/unencrypted.seb [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/sample_data/unencrypted_mac_001.seb [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/sample_data/unencrypted_win_223.seb [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/settings_provider_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/tests/phpunit/template_test.php [new file with mode: 0644]
mod/quiz/accessrule/seb/version.php [new file with mode: 0644]
mod/quiz/accessrule/timelimit/lang/en/quizaccess_timelimit.php
mod/quiz/backup/moodle2/restore_quiz_stepslib.php
mod/quiz/lang/en/quiz.php
mod/quiz/locallib.php
mod/quiz/report/responses/tests/fixtures/questions00.csv
mod/quiz/tests/attempt_walkthrough_from_csv_test.php
mod/quiz/tests/behat/attempt_begin.feature
mod/workshop/tests/behat/file_type_restriction.feature
privacy/classes/tests/request/content_writer.php
question/type/calculated/questiontype.php
question/type/calculated/tests/questiontype_test.php
question/type/ddimageortext/lang/en/qtype_ddimageortext.php
question/type/ddmarker/lang/en/qtype_ddmarker.php
question/type/multianswer/questiontype.php
question/type/multianswer/tests/questiontype_test.php
question/type/numerical/db/upgradelib.php
question/type/questiontypebase.php
question/type/random/questiontype.php
question/type/random/tests/questiontype_test.php
question/type/truefalse/questiontype.php
question/type/truefalse/tests/questiontype_test.php
theme/boost/scss/moodle/contentbank.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/grade.scss
theme/boost/scss/preset/default.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css
user/amd/build/participants.min.js
user/amd/build/participants.min.js.map
user/amd/build/repository.min.js [new file with mode: 0644]
user/amd/build/repository.min.js.map [new file with mode: 0644]
user/amd/build/status_field.min.js
user/amd/build/status_field.min.js.map
user/amd/src/participants.js
user/amd/src/repository.js [new file with mode: 0644]
user/amd/src/status_field.js
user/classes/table/participants.php [moved from user/classes/participants_table.php with 98% similarity]
user/index.php
user/tests/behat/course_preference.feature
version.php

index 2ef8a38..b9c0b6a 100644 (file)
@@ -65,6 +65,7 @@ lib/php-jwt/
 lib/babel-polyfill/
 lib/polyfills/
 lib/emoji-data/
+lib/plist/
 media/player/videojs/amd/src/video-lazy.js
 media/player/videojs/amd/src/Youtube-lazy.js
 media/player/videojs/videojs/
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644 (file)
index 0000000..adb6942
--- /dev/null
@@ -0,0 +1,2 @@
+# Primary donations pages.
+custom: ["https://moodle.com/donations/", moodle.org]
index f00e696..5d9e5c1 100644 (file)
@@ -66,6 +66,7 @@ lib/php-jwt/
 lib/babel-polyfill/
 lib/polyfills/
 lib/emoji-data/
+lib/plist/
 media/player/videojs/amd/src/video-lazy.js
 media/player/videojs/amd/src/Youtube-lazy.js
 media/player/videojs/videojs/
index 321c133..245d7fc 100644 (file)
@@ -2,8 +2,6 @@
 # process (which uses our internal CI system) this file is here for the benefit
 # of community developers git clones - see MDL-51458.
 
-sudo: required
-
 # We currently disable Travis notifications entirely until https://github.com/travis-ci/travis-ci/issues/4976
 # is fixed.
 notifications:
@@ -11,6 +9,8 @@ notifications:
 
 language: php
 
+os: linux
+
 dist: xenial
 
 services:
@@ -41,7 +41,7 @@ env:
     # Perform an upgrade test too.
     - DB=pgsql    TASK=UPGRADE
 
-matrix:
+jobs:
     # Enable fast finish.
     # This will fail the build if a single job fails (except those in allow_failures).
     # It will not stop the jobs from running.
index d004e9e..b5c31d7 100644 (file)
@@ -83,7 +83,7 @@ if (($form = data_submitted()) && confirm_sesskey()) {
                 }
             } else {
                 $newsettings = array();
-                if (array_key_exists($componentprovidersetting, $form)) {
+                if (property_exists($form, $componentprovidersetting)) {
                     // We must be processing loggedin or loggedoff checkboxes.
                     // Store defained comma-separated processors as setting value.
                     // Using array_filter eliminates elements set to 0 above.
index f904352..5931a26 100644 (file)
@@ -813,7 +813,7 @@ class core_admin_renderer extends plugin_renderer_base {
             }
         }
 
-        $updateinfo .= $this->container_start('checkforupdates');
+        $updateinfo .= $this->container_start('checkforupdates mt-1');
         $fetchurl = new moodle_url('/admin/index.php', array('fetchupdates' => 1, 'sesskey' => sesskey(), 'cache' => 0));
         $updateinfo .= $this->single_button($fetchurl, get_string('checkforupdates', 'core_plugin'));
         if ($fetch) {
@@ -902,7 +902,7 @@ class core_admin_renderer extends plugin_renderer_base {
      */
     protected function moodle_available_update_info(\core\update\info $updateinfo) {
 
-        $boxclasses = 'moodleupdateinfo';
+        $boxclasses = 'moodleupdateinfo mb-2';
         $info = array();
 
         if (isset($updateinfo->release)) {
@@ -922,7 +922,8 @@ class core_admin_renderer extends plugin_renderer_base {
         }
 
         if (isset($updateinfo->download)) {
-            $info[] = html_writer::link($updateinfo->download, get_string('download'), array('class' => 'info download'));
+            $info[] = html_writer::link($updateinfo->download, get_string('download'),
+                array('class' => 'info download btn btn-secondary'));
         }
 
         if (isset($updateinfo->url)) {
@@ -930,9 +931,9 @@ class core_admin_renderer extends plugin_renderer_base {
                 array('class' => 'info more'));
         }
 
-        $box  = $this->output->box_start($boxclasses);
-        $box .= $this->output->box(implode(html_writer::tag('span', ' ', array('class' => 'separator')), $info), '');
-        $box .= $this->output->box_end();
+        $box  = $this->output->container_start($boxclasses);
+        $box .= $this->output->container(implode(html_writer::tag('span', ' | ', array('class' => 'separator')), $info), '');
+        $box .= $this->output->container_end();
 
         return $box;
     }
index f8a5ceb..46206fc 100644 (file)
@@ -10,7 +10,6 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp = new admin_settingpage('experimentalsettings', new lang_string('experimentalsettings', 'admin'));
     //TODO: Re-enable cc-import once re-implemented in 2.0.x
     //$temp->add(new admin_setting_configcheckbox('enableimsccimport', new lang_string('enable_cc_import', 'imscc'), new lang_string('enable_cc_import_description', 'imscc'), 0));
-    $temp->add(new admin_setting_configcheckbox('enablesafebrowserintegration', new lang_string('enablesafebrowserintegration', 'admin'), new lang_string('configenablesafebrowserintegration', 'admin'), 0));
 
     $temp->add(new admin_setting_configcheckbox('dndallowtextandlinks', new lang_string('dndallowtextandlinks', 'admin'), new lang_string('configdndallowtextandlinks', 'admin'), 0));
 
index 8daab15..fd0b465 100644 (file)
@@ -24,7 +24,7 @@
 
 $string['classname'] = 'Class name';
 $string['component'] = 'Component';
-$string['configmessageinboundhost'] = 'The address of the server that Moodle should check mail against. To specify a non-default port, use [server]:[port], for example mail.example.com:587. If a port isn\'t specified, the default port for the type of mail server will be used.';
+$string['configmessageinboundhost'] = 'The address of the server that Moodle should check mail against. To specify a non-default port, use [server]:[port], for example mail.example.com:993. If a port isn\'t specified, the default port for the type of mail server will be used.';
 $string['defaultexpiration'] = 'Default address expiry period';
 $string['defaultexpiration_help'] = 'When an email address is generated by the handler, it can be set to automatically expire after a period of time, so that it can no longer be used. It is advisable to set an expiry period.';
 $string['description'] = 'Description';
index 4d0c7f2..ce2d8fd 100644 (file)
@@ -127,3 +127,36 @@ Feature: Basic recycle bin functionality
     And I press "Yes"
     And I should see "Recycle bin has been emptied"
     And I should see "There are no items in the recycle bin."
+
+  @javascript
+  Scenario: Show recycle bin on category action menu
+    Given I log in as "admin"
+    And I navigate to "Courses >  Manage courses and categories" in site administration
+    And I click on "Actions menu" "link"
+    And I click on "Recycle bin" "link"
+    Then I should see "There are no items in the recycle bin."
+
+  @javascript
+  Scenario: Not show recycle bin empty on category action menu whit autohide enable
+    Given I log in as "admin"
+    And the following config values are set as admin:
+      | categorybinenable | 0 | tool_recyclebin |
+    And I navigate to "Courses >  Manage courses and categories" in site administration
+    And I click on "Actions menu" "link"
+    Then I should not see "Recycle bin"
+
+  @javascript
+  Scenario: Show recycle bin not empty on category action menu whit autohide enable
+    Given I log in as "admin"
+    And the following config values are set as admin:
+      | autohide | 1 | tool_recyclebin |
+    And I navigate to "Courses >  Manage courses and categories" in site administration
+    And I click on "Actions menu" "link"
+    Then I should not see "Recycle bin"
+    And I click on "delete" action for "Course 2" in management course listing
+    And I press "Delete"
+    And I should see "Deleting C2"
+    And I should see "C2 has been completely deleted"
+    And I press "Continue"
+    When I click on "Actions menu" "link"
+    Then I should see "Recycle bin"
index 9ccd292..299603f 100644 (file)
  */
 
 $string['asap'] = 'ASAP';
-$string['adhocempty'] = 'Adhoc task queue is empty';
-$string['adhocqueuesize'] = 'Adhoc task queue has {$a} tasks';
+$string['adhocempty'] = 'Ad hoc task queue is empty';
+$string['adhocqueuesize'] = 'Ad hoc task queue has {$a} tasks';
 $string['adhocqueueold'] = 'Oldest task is {$a->age} which is more than {$a->max}';
 $string['backtoscheduledtasks'] = 'Back to scheduled tasks';
 $string['blocking'] = 'Blocking';
 $string['cannotfindthepathtothecli'] = 'Cannot find the path to the PHP CLI executable so task execution aborted. Set the \'Path to PHP CLI\' setting in Site administration / Server / System paths.';
-$string['checkadhocqueue'] = 'Adhoc task queue';
+$string['checkadhocqueue'] = 'Ad hoc task queue';
 $string['checkcronrunning'] = 'Cron running';
 $string['checkmaxfaildelay'] = 'Tasks max fail delay';
 $string['clearfaildelay_confirm'] = 'Are you sure you want to clear the fail delay for task \'{$a}\'? After clearing the delay, the task will run according to its normal schedule.';
@@ -58,7 +58,7 @@ $string['runpattern'] = 'Run pattern';
 $string['scheduledtasks'] = 'Scheduled tasks';
 $string['scheduledtaskchangesdisabled'] = 'Modifications to the list of scheduled tasks have been prevented in Moodle configuration';
 $string['taskdisabled'] = 'Task disabled';
-$string['taskfailures'] = 'There are {$a} task(s) failing';
+$string['taskfailures'] = '{$a} task(s) failing';
 $string['tasklogs'] = 'Task logs';
 $string['tasknofailures'] = 'There are no tasks failing';
 $string['taskscheduleday'] = 'Day';
index ba03bac..b7c175c 100644 (file)
@@ -44,11 +44,13 @@ class check_indexes extends XMLDBCheckAction {
 
         // Get needed strings
         $this->loadStrings(array(
+            'extraindexesfound' => 'tool_xmldb',
             'missing' => 'tool_xmldb',
             'key' => 'tool_xmldb',
             'index' => 'tool_xmldb',
             'missingindexes' => 'tool_xmldb',
-            'nomissingindexesfound' => 'tool_xmldb',
+            'nomissingorextraindexesfound' => 'tool_xmldb',
+            'yesextraindexesfound' => 'tool_xmldb',
             'yesmissingindexesfound' => 'tool_xmldb',
         ));
     }
@@ -58,6 +60,7 @@ class check_indexes extends XMLDBCheckAction {
         $dbman = $DB->get_manager();
 
         $o = '';
+        $dbindexes = $DB->get_indexes($xmldb_table->getName());
         $missing_indexes = array();
 
         // Keys
@@ -88,6 +91,7 @@ class check_indexes extends XMLDBCheckAction {
                     // Check if the index exists in DB
                     if ($dbman->index_exists($xmldb_table, $xmldb_index)) {
                         $o.='<font color="green">' . $this->str['ok'] . '</font>';
+                        $this->remove_index_from_dbindex($dbindexes, $xmldb_index);
                     } else {
                         $o.='<font color="red">' . $this->str['missing'] . '</font>';
                         // Add the missing index to the list
@@ -109,6 +113,7 @@ class check_indexes extends XMLDBCheckAction {
                 // Check if the index exists in DB
                 if ($dbman->index_exists($xmldb_table, $xmldb_index)) {
                     $o.='<font color="green">' . $this->str['ok'] . '</font>';
+                    $this->remove_index_from_dbindex($dbindexes, $xmldb_index);
                 } else {
                     $o.='<font color="red">' . $this->str['missing'] . '</font>';
                     // Add the missing index to the list
@@ -122,6 +127,14 @@ class check_indexes extends XMLDBCheckAction {
             $o.='        </ul>';
         }
 
+        // Hack - skip for table 'search_simpledb_index' as this plugin adds indexes dynamically on install
+        // which are not included in install.xml. See search/engine/simpledb/db/install.php.
+        if ($xmldb_table->getName() != 'search_simpledb_index') {
+            foreach ($dbindexes as $indexname => $index) {
+                $missing_indexes[] = $indexname;
+            }
+        }
+
         return array($o, $missing_indexes);
     }
 
@@ -129,33 +142,56 @@ class check_indexes extends XMLDBCheckAction {
         global $DB;
         $dbman = $DB->get_manager();
 
+        $missingindexes = [];
+        $extraindexes = [];
+
+        foreach ($missing_indexes as $missingindex) {
+            if (is_object($missingindex)) {
+                $missingindexes[] = $missingindex;
+            } else {
+                $extraindexes[] = $missingindex;
+            }
+        }
+
         $s = '';
         $r = '<table class="generaltable boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
         $r.= '  <tr><td class="generalboxcontent">';
         $r.= '    <h2 class="main">' . $this->str['searchresults'] . '</h2>';
-        $r.= '    <p class="centerpara">' . $this->str['missingindexes'] . ': ' . count($missing_indexes) . '</p>';
+        $r .= '    <p class="centerpara">' . $this->str['missingindexes'] . ': ' . count($missingindexes) . '</p>';
+        $r .= '    <p class="centerpara">' . $this->str['extraindexesfound'] . ': ' . count($extraindexes) . '</p>';
         $r.= '  </td></tr>';
         $r.= '  <tr><td class="generalboxcontent">';
 
-        // If we have found missing indexes inform about them
-        if (count($missing_indexes)) {
-            $r.= '    <p class="centerpara">' . $this->str['yesmissingindexesfound'] . '</p>';
-            $r.= '        <ul>';
-            foreach ($missing_indexes as $obj) {
-                $xmldb_table = $obj->table;
-                $xmldb_index = $obj->index;
-                $sqlarr = $dbman->generator->getAddIndexSQL($xmldb_table, $xmldb_index);
-                $r.= '            <li>' . $this->str['table'] . ': ' . $xmldb_table->getName() . '. ' .
-                                          $this->str['index'] . ': ' . $xmldb_index->readableInfo() . '</li>';
-                $sqlarr = $dbman->generator->getEndedStatements($sqlarr);
-                $s.= '<code>' . str_replace("\n", '<br />', implode('<br />', $sqlarr)) . '</code><br />';
+        // If we have found missing indexes or extra indexes inform the user about them.
+        if (!empty($missingindexes) || !empty($extraindexes)) {
+            if ($missingindexes) {
+                $r.= '    <p class="centerpara">' . $this->str['yesmissingindexesfound'] . '</p>';
+                $r.= '        <ul>';
+                foreach ($missingindexes as $obj) {
+                    $xmldb_table = $obj->table;
+                    $xmldb_index = $obj->index;
+                    $sqlarr = $dbman->generator->getAddIndexSQL($xmldb_table, $xmldb_index);
+                    $r.= '            <li>' . $this->str['table'] . ': ' . $xmldb_table->getName() . '. ' .
+                                              $this->str['index'] . ': ' . $xmldb_index->readableInfo() . '</li>';
+                    $sqlarr = $dbman->generator->getEndedStatements($sqlarr);
+                    $s.= '<code>' . str_replace("\n", '<br />', implode('<br />', $sqlarr)) . '</code><br />';
 
+                }
+                $r.= '        </ul>';
+                // Add the SQL statements (all together)
+                $r.= '<hr />' . $s;
+            }
+            if ($extraindexes) {
+                $r .= '<p class="centerpara">' . $this->str['yesextraindexesfound'] . '</p>';
+                $r .= '<ul>';
+                foreach ($extraindexes as $ei) {
+                    $r .= '<li>' . $ei . '</li>';
+                }
+                $r .= '</ul>';
+                $r .= '<hr />';
             }
-            $r.= '        </ul>';
-            // Add the SQL statements (all together)
-            $r.= '<hr />' . $s;
         } else {
-            $r.= '    <p class="centerpara">' . $this->str['nomissingindexesfound'] . '</p>';
+            $r .= '<p class="centerpara">' . $this->str['nomissingorextraindexesfound'] . '</p>';
         }
         $r.= '  </td></tr>';
         $r.= '  <tr><td class="generalboxcontent">';
@@ -166,4 +202,18 @@ class check_indexes extends XMLDBCheckAction {
 
         return $r;
     }
+
+    /**
+     * Removes an index from the array $dbindexes if it is found.
+     *
+     * @param array $dbindexes
+     * @param xmldb_index $index
+     */
+    private function remove_index_from_dbindex(array &$dbindexes, xmldb_index $index) {
+        foreach ($dbindexes as $key => $dbindex) {
+            if ($dbindex['columns'] == $index->getFields()) {
+                unset($dbindexes[$key]);
+            }
+        }
+    }
 }
index c465ed1..e879835 100644 (file)
@@ -99,6 +99,7 @@ $string['edit_xml_file'] = 'Edit XML file';
 $string['enumvaluesincorrect'] = 'Incorrect values for enum field';
 $string['expected'] = 'Expected';
 $string['extensionrequired'] = 'Sorry - the PHP extension \'{$a}\' is required for this action. Please install the extension if you want to use this feature.';
+$string['extraindexesfound'] = 'Extra indexes found';
 $string['field'] = 'Field';
 $string['fieldnameempty'] = 'Name field empty';
 $string['fields'] = 'Fields';
@@ -157,7 +158,7 @@ $string['newtablefrommysql'] = 'New table from MySQL';
 $string['new_table_from_mysql'] = 'New table from MySQL';
 $string['nofieldsspecified'] = 'No fields specified';
 $string['nomasterprimaryuniquefound'] = 'The column(s) that your foreign key references must be included in a primary or unique KEY in the referenced table. Note that the column being in a UNIQUE INDEX is not good enough.';
-$string['nomissingindexesfound'] = 'No missing indexes have been found, your DB doesn\'t need further actions.';
+$string['nomissingorextraindexesfound'] = 'No missing or extra indexes have been found, so no further action is required.';
 $string['noreffieldsspecified'] = 'No reference fields specified';
 $string['noreftablespecified'] = 'Specified reference table not found';
 $string['noviolatedforeignkeysfound'] = 'No violated foreign keys found';
@@ -215,6 +216,7 @@ $string['wronglengthforenum'] = 'Incorrect length for enum field';
 $string['wrongnumberofreffields'] = 'Wrong number of reference fields';
 $string['wrongreservedwords'] = 'Currently used reserved words<br />(note that table names aren\'t important if using $CFG->prefix)';
 $string['wrongoraclesemantics'] = 'Wrong Oracle BYTE semantics found';
+$string['yesextraindexesfound'] = 'The following additional indexes were found.';
 $string['yesmissingindexesfound'] = '<p>Some missing indexes have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to create all of them. Remember to backup your data first!</p>
 <p>After doing that, it\'s highly recommended to execute this utility again to check that no more missing indexes are found.</p>';
 $string['yeswrongdefaultsfound'] = '<p>Some inconsistent defaults have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to fix them all. Remember to backup your data first!</p>
index 1b2a7df..ac42715 100644 (file)
@@ -16,4 +16,4 @@ Feature: Add URL to main menu block
       | External URL | http://www.google.com |
       | id_display | In pop-up |
     Then "google" "link" should exist in the "Main menu" "block"
-    And "Add an activity or resource" "button" should exist in the "Main menu" "block"
+    And "Add an activity" "button" should exist in the "Main menu" "block"
index 2776339..dc4821b 100644 (file)
@@ -361,20 +361,23 @@ class cache_helper {
     /**
      * Ensure that the stats array is ready to collect information for the given store and definition.
      * @param string $store
+     * @param string $storeclass
      * @param string $definition A string that identifies the definition.
      * @param int $mode One of cache_store::MODE_*. Since 2.9.
      */
-    protected static function ensure_ready_for_stats($store, $definition, $mode = cache_store::MODE_APPLICATION) {
+    protected static function ensure_ready_for_stats($store, $storeclass, $definition, $mode = cache_store::MODE_APPLICATION) {
         // This function is performance-sensitive, so exit as quickly as possible
         // if we do not need to do anything.
         if (isset(self::$stats[$definition]['stores'][$store])) {
             return;
         }
+
         if (!array_key_exists($definition, self::$stats)) {
             self::$stats[$definition] = array(
                 'mode' => $mode,
                 'stores' => array(
                     $store => array(
+                        'class' => $storeclass,
                         'hits' => 0,
                         'misses' => 0,
                         'sets' => 0,
@@ -383,6 +386,7 @@ class cache_helper {
             );
         } else if (!array_key_exists($store, self::$stats[$definition]['stores'])) {
             self::$stats[$definition]['stores'][$store] = array(
+                'class' => $storeclass,
                 'hits' => 0,
                 'misses' => 0,
                 'sets' => 0,
@@ -418,15 +422,22 @@ class cache_helper {
      * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
      * cache_definition instance. It is preferable to pass a cache definition instance.
      *
+     * In Moodle 3.9 the first argument changed to also accept a cache_store.
+     *
      * @internal
-     * @param cache_definition $store
+     * @param string|cache_store $store
      * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
      *      actual cache_definition object now.
      * @param int $hits The number of hits to record (by default 1)
      */
     public static function record_cache_hit($store, $definition, $hits = 1) {
+        $storeclass = '';
+        if ($store instanceof cache_store) {
+            $storeclass = get_class($store);
+            $store = $store->my_name();
+        }
         list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
-        self::ensure_ready_for_stats($store, $definitionstr, $mode);
+        self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
         self::$stats[$definitionstr]['stores'][$store]['hits'] += $hits;
     }
 
@@ -436,15 +447,22 @@ class cache_helper {
      * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
      * cache_definition instance. It is preferable to pass a cache definition instance.
      *
+     * In Moodle 3.9 the first argument changed to also accept a cache_store.
+     *
      * @internal
-     * @param string $store
+     * @param string|cache_store $store
      * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
      *      actual cache_definition object now.
      * @param int $misses The number of misses to record (by default 1)
      */
     public static function record_cache_miss($store, $definition, $misses = 1) {
+        $storeclass = '';
+        if ($store instanceof cache_store) {
+            $storeclass = get_class($store);
+            $store = $store->my_name();
+        }
         list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
-        self::ensure_ready_for_stats($store, $definitionstr, $mode);
+        self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
         self::$stats[$definitionstr]['stores'][$store]['misses'] += $misses;
     }
 
@@ -454,15 +472,22 @@ class cache_helper {
      * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
      * cache_definition instance. It is preferable to pass a cache definition instance.
      *
+     * In Moodle 3.9 the first argument changed to also accept a cache_store.
+     *
      * @internal
-     * @param string $store
+     * @param string|cache_store $store
      * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
      *      actual cache_definition object now.
      * @param int $sets The number of sets to record (by default 1)
      */
     public static function record_cache_set($store, $definition, $sets = 1) {
+        $storeclass = '';
+        if ($store instanceof cache_store) {
+            $storeclass = get_class($store);
+            $store = $store->my_name();
+        }
         list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
-        self::ensure_ready_for_stats($store, $definitionstr, $mode);
+        self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
         self::$stats[$definitionstr]['stores'][$store]['sets'] += $sets;
     }
 
index 8cd7914..6236cb0 100644 (file)
@@ -414,7 +414,7 @@ class cache implements cache_loader {
         $setaftervalidation = false;
         if ($result === false) {
             if ($this->perfdebug) {
-                cache_helper::record_cache_miss($this->storetype, $this->definition);
+                cache_helper::record_cache_miss($this->store, $this->definition);
             }
             if ($this->loader !== false) {
                 // We must pass the original (unparsed) key to the next loader in the chain.
@@ -426,7 +426,7 @@ class cache implements cache_loader {
             }
             $setaftervalidation = ($result !== false);
         } else if ($this->perfdebug) {
-            cache_helper::record_cache_hit($this->storetype, $this->definition);
+            cache_helper::record_cache_hit($this->store, $this->definition);
         }
         // 5. Validate strictness.
         if ($strictness === MUST_EXIST && $result === false) {
@@ -580,8 +580,8 @@ class cache implements cache_loader {
                     $hits++;
                 }
             }
-            cache_helper::record_cache_hit($this->storetype, $this->definition, $hits);
-            cache_helper::record_cache_miss($this->storetype, $this->definition, $misses);
+            cache_helper::record_cache_hit($this->store, $this->definition, $hits);
+            cache_helper::record_cache_miss($this->store, $this->definition, $misses);
         }
 
         // Return the result. Phew!
@@ -607,7 +607,7 @@ class cache implements cache_loader {
      */
     public function set($key, $data) {
         if ($this->perfdebug) {
-            cache_helper::record_cache_set($this->storetype, $this->definition);
+            cache_helper::record_cache_set($this->store, $this->definition);
         }
         if ($this->loader !== false) {
             // We have a loader available set it there as well.
@@ -762,7 +762,7 @@ class cache implements cache_loader {
         }
         $successfullyset = $this->store->set_many($data);
         if ($this->perfdebug && $successfullyset) {
-            cache_helper::record_cache_set($this->storetype, $this->definition, $successfullyset);
+            cache_helper::record_cache_set($this->store, $this->definition, $successfullyset);
         }
         return $successfullyset;
     }
@@ -1112,7 +1112,7 @@ class cache implements cache_loader {
         }
         if ($result !== false) {
             if ($this->perfdebug) {
-                cache_helper::record_cache_hit('** static acceleration **', $this->definition);
+                cache_helper::record_cache_hit(cache_store::STATIC_ACCEL, $this->definition);
             }
             if ($this->staticaccelerationsize > 1 && $this->staticaccelerationcount > 1) {
                 // Check to see if this is the last item on the static acceleration keys array.
@@ -1126,7 +1126,7 @@ class cache implements cache_loader {
             return $result;
         } else {
             if ($this->perfdebug) {
-                cache_helper::record_cache_miss('** static acceleration **', $this->definition);
+                cache_helper::record_cache_miss(cache_store::STATIC_ACCEL, $this->definition);
             }
             return false;
         }
@@ -1830,7 +1830,7 @@ class cache_session extends cache {
         // 4. Load if from the loader/datasource if we don't already have it.
         if ($result === false) {
             if ($this->perfdebug) {
-                cache_helper::record_cache_miss($this->storetype, $this->get_definition());
+                cache_helper::record_cache_miss($this->get_store(), $this->get_definition());
             }
             if ($this->get_loader() !== false) {
                 // We must pass the original (unparsed) key to the next loader in the chain.
@@ -1845,7 +1845,7 @@ class cache_session extends cache {
                 $this->set($key, $result);
             }
         } else if ($this->perfdebug) {
-            cache_helper::record_cache_hit($this->storetype, $this->get_definition());
+            cache_helper::record_cache_hit($this->get_store(), $this->get_definition());
         }
         // 5. Validate strictness.
         if ($strictness === MUST_EXIST && $result === false) {
@@ -1889,7 +1889,7 @@ class cache_session extends cache {
             $loader->set($key, $data);
         }
         if ($this->perfdebug) {
-            cache_helper::record_cache_set($this->storetype, $this->get_definition());
+            cache_helper::record_cache_set($this->get_store(), $this->get_definition());
         }
         if (is_object($data) && $data instanceof cacheable_object) {
             $data = new cache_cached_object($data);
@@ -2019,8 +2019,8 @@ class cache_session extends cache {
                     $hits++;
                 }
             }
-            cache_helper::record_cache_hit($this->storetype, $this->get_definition(), $hits);
-            cache_helper::record_cache_miss($this->storetype, $this->get_definition(), $misses);
+            cache_helper::record_cache_hit($this->get_store(), $this->get_definition(), $hits);
+            cache_helper::record_cache_miss($this->get_store(), $this->get_definition(), $misses);
         }
         return $return;
 
@@ -2097,7 +2097,7 @@ class cache_session extends cache {
         }
         $successfullyset = $this->get_store()->set_many($data);
         if ($this->perfdebug && $successfullyset) {
-            cache_helper::record_cache_set($this->storetype, $this->get_definition(), $successfullyset);
+            cache_helper::record_cache_set($this->store, $this->get_definition(), $successfullyset);
         }
         return $successfullyset;
     }
index 4fcb03f..a2cfe3e 100644 (file)
@@ -144,6 +144,10 @@ abstract class cache_store implements cache_store_interface {
      * Request caches. Static caches really.
      */
     const MODE_REQUEST = 4;
+    /**
+     * Static caches.
+     */
+    const STATIC_ACCEL = '** static accel. **';
 
     /**
      * Constructs an instance of the cache store.
index 9c0f1d9..0e6b203 100644 (file)
@@ -2092,15 +2092,15 @@ class core_cache_testcase extends advanced_testcase {
         $this->assertFalse($request->get('missMe'));
 
         $endstats = cache_helper::get_stats();
-        $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['misses']);
-        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['hits']);
-        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['sets']);
-        $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['misses']);
-        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['hits']);
-        $this->assertEquals(1, $endstats[$sessionid]['stores']['cachestore_session']['sets']);
-        $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['misses']);
-        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['hits']);
-        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets']);
+        $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['misses']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['hits']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['sets']);
+        $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['misses']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['hits']);
+        $this->assertEquals(1, $endstats[$sessionid]['stores']['default_session']['sets']);
+        $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['misses']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['hits']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['sets']);
 
         $startstats = cache_helper::get_stats();
 
@@ -2116,24 +2116,24 @@ class core_cache_testcase extends advanced_testcase {
         $this->assertTrue($request->set('setMe4', 4));
 
         $endstats = cache_helper::get_stats();
-        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['misses'] -
-            $startstats[$applicationid]['stores']['cachestore_file']['misses']);
-        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['hits'] -
-            $startstats[$applicationid]['stores']['cachestore_file']['hits']);
-        $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['sets'] -
-            $startstats[$applicationid]['stores']['cachestore_file']['sets']);
-        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['misses'] -
-            $startstats[$sessionid]['stores']['cachestore_session']['misses']);
-        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['hits'] -
-            $startstats[$sessionid]['stores']['cachestore_session']['hits']);
-        $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['sets'] -
-            $startstats[$sessionid]['stores']['cachestore_session']['sets']);
-        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['misses'] -
-            $startstats[$requestid]['stores']['cachestore_static']['misses']);
-        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['hits'] -
-            $startstats[$requestid]['stores']['cachestore_static']['hits']);
-        $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
-            $startstats[$requestid]['stores']['cachestore_static']['sets']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
+                             $startstats[$applicationid]['stores']['default_application']['misses']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['hits'] -
+                             $startstats[$applicationid]['stores']['default_application']['hits']);
+        $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['sets'] -
+                             $startstats[$applicationid]['stores']['default_application']['sets']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
+                             $startstats[$sessionid]['stores']['default_session']['misses']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['hits'] -
+                             $startstats[$sessionid]['stores']['default_session']['hits']);
+        $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['sets'] -
+                             $startstats[$sessionid]['stores']['default_session']['sets']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
+                             $startstats[$requestid]['stores']['default_request']['misses']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['hits'] -
+                             $startstats[$requestid]['stores']['default_request']['hits']);
+        $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['sets'] -
+                             $startstats[$requestid]['stores']['default_request']['sets']);
 
         $startstats = cache_helper::get_stats();
 
@@ -2149,24 +2149,24 @@ class core_cache_testcase extends advanced_testcase {
         $this->assertEquals($request->get('setMe4'), 4);
 
         $endstats = cache_helper::get_stats();
-        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['misses'] -
-            $startstats[$applicationid]['stores']['cachestore_file']['misses']);
-        $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['hits'] -
-            $startstats[$applicationid]['stores']['cachestore_file']['hits']);
-        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['sets'] -
-            $startstats[$applicationid]['stores']['cachestore_file']['sets']);
-        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['misses'] -
-            $startstats[$sessionid]['stores']['cachestore_session']['misses']);
-        $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['hits'] -
-            $startstats[$sessionid]['stores']['cachestore_session']['hits']);
-        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['sets'] -
-            $startstats[$sessionid]['stores']['cachestore_session']['sets']);
-        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['misses'] -
-            $startstats[$requestid]['stores']['cachestore_static']['misses']);
-        $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['hits'] -
-            $startstats[$requestid]['stores']['cachestore_static']['hits']);
-        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
-            $startstats[$requestid]['stores']['cachestore_static']['sets']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
+                             $startstats[$applicationid]['stores']['default_application']['misses']);
+        $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['hits'] -
+                             $startstats[$applicationid]['stores']['default_application']['hits']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['sets'] -
+                             $startstats[$applicationid]['stores']['default_application']['sets']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
+                             $startstats[$sessionid]['stores']['default_session']['misses']);
+        $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['hits'] -
+                             $startstats[$sessionid]['stores']['default_session']['hits']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['sets'] -
+                             $startstats[$sessionid]['stores']['default_session']['sets']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
+                             $startstats[$requestid]['stores']['default_request']['misses']);
+        $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['hits'] -
+                             $startstats[$requestid]['stores']['default_request']['hits']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['sets'] -
+                             $startstats[$requestid]['stores']['default_request']['sets']);
 
         $startstats = cache_helper::get_stats();
 
@@ -2176,24 +2176,24 @@ class core_cache_testcase extends advanced_testcase {
         $request->get_many(array('setMe1', 'setMe2', 'setMe3', 'setMe4'));
 
         $endstats = cache_helper::get_stats();
-        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['misses'] -
-            $startstats[$applicationid]['stores']['cachestore_file']['misses']);
-        $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['hits'] -
-            $startstats[$applicationid]['stores']['cachestore_file']['hits']);
-        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['sets'] -
-            $startstats[$applicationid]['stores']['cachestore_file']['sets']);
-        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['misses'] -
-            $startstats[$sessionid]['stores']['cachestore_session']['misses']);
-        $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['hits'] -
-            $startstats[$sessionid]['stores']['cachestore_session']['hits']);
-        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['sets'] -
-            $startstats[$sessionid]['stores']['cachestore_session']['sets']);
-        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['misses'] -
-            $startstats[$requestid]['stores']['cachestore_static']['misses']);
-        $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['hits'] -
-            $startstats[$requestid]['stores']['cachestore_static']['hits']);
-        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
-            $startstats[$requestid]['stores']['cachestore_static']['sets']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
+                             $startstats[$applicationid]['stores']['default_application']['misses']);
+        $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['hits'] -
+                             $startstats[$applicationid]['stores']['default_application']['hits']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['sets'] -
+                             $startstats[$applicationid]['stores']['default_application']['sets']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
+                             $startstats[$sessionid]['stores']['default_session']['misses']);
+        $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['hits'] -
+                             $startstats[$sessionid]['stores']['default_session']['hits']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['sets'] -
+                             $startstats[$sessionid]['stores']['default_session']['sets']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
+                             $startstats[$requestid]['stores']['default_request']['misses']);
+        $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['hits'] -
+                             $startstats[$requestid]['stores']['default_request']['hits']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['sets'] -
+                             $startstats[$requestid]['stores']['default_request']['sets']);
     }
 
     public function test_static_cache() {
@@ -2225,8 +2225,8 @@ class core_cache_testcase extends advanced_testcase {
 
         // Check that the static acceleration worked, even on empty arrays and the number 0.
         $endstats = cache_helper::get_stats();
-        $this->assertEquals(0, $endstats[$applicationid]['stores']['** static acceleration **']['misses']);
-        $this->assertEquals(3, $endstats[$applicationid]['stores']['** static acceleration **']['hits']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['** static accel. **']['misses']);
+        $this->assertEquals(3, $endstats[$applicationid]['stores']['** static accel. **']['hits']);
     }
 
     public function test_performance_debug_off() {
index eaf6344..076bd53 100644 (file)
@@ -1,6 +1,9 @@
 This files describes API changes in /cache/stores/* - cache store plugins.
 Information provided here is intended especially for developers.
 
+=== 3.9 ===
+* The record_cache_hit/miss/set methods now take a cache_store instead of a cache_definition object
+
 === 3.8 ===
 * The Redis cache store can now make use of the Zstandard compression algorithm (see MDL-66428).
 
index b58e8c0..3b08693 100644 (file)
@@ -13,7 +13,7 @@
     "require-dev": {
         "phpunit/phpunit": "7.5.*",
         "phpunit/dbunit": "4.0.*",
-        "moodlehq/behat-extension": "3.39.1",
+        "moodlehq/behat-extension": "3.39.3",
         "mikey179/vfsstream": "^1.6",
         "instaclick/php-webdriver": "dev-local as 1.x-dev"
     }
index bd2613c..11dbff6 100644 (file)
@@ -4,42 +4,44 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "7d4095e9af1e9ef59e2a74273a5c55b2",
+    "content-hash": "b1953ceec577434625a7aee12f650daa",
     "packages": [],
     "packages-dev": [
         {
             "name": "behat/behat",
-            "version": "v3.5.0",
+            "version": "v3.6.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/Behat.git",
-                "reference": "e4bce688be0c2029dc1700e46058d86428c63cab"
+                "reference": "9bfe195b4745c32e068af03fa4df9558b4916d30"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Behat/zipball/e4bce688be0c2029dc1700e46058d86428c63cab",
-                "reference": "e4bce688be0c2029dc1700e46058d86428c63cab",
+                "url": "https://api.github.com/repos/Behat/Behat/zipball/9bfe195b4745c32e068af03fa4df9558b4916d30",
+                "reference": "9bfe195b4745c32e068af03fa4df9558b4916d30",
                 "shasum": ""
             },
             "require": {
-                "behat/gherkin": "^4.5.1",
+                "behat/gherkin": "^4.6.0",
                 "behat/transliterator": "^1.2",
                 "container-interop/container-interop": "^1.2",
                 "ext-mbstring": "*",
                 "php": ">=5.3.3",
                 "psr/container": "^1.0",
-                "symfony/class-loader": "~2.1||~3.0",
-                "symfony/config": "~2.3||~3.0||~4.0",
-                "symfony/console": "~2.7.40||^2.8.33||~3.3.15||^3.4.3||^4.0.3",
-                "symfony/dependency-injection": "~2.1||~3.0||~4.0",
-                "symfony/event-dispatcher": "~2.1||~3.0||~4.0",
-                "symfony/translation": "~2.3||~3.0||~4.0",
-                "symfony/yaml": "~2.1||~3.0||~4.0"
+                "symfony/config": "^2.7.51 || ^3.0 || ^4.0 || ^5.0",
+                "symfony/console": "^2.7.51 || ^2.8.33 || ^3.3.15 || ^3.4.3 || ^4.0.3 || ^5.0",
+                "symfony/dependency-injection": "^2.7.51 || ^3.0 || ^4.0 || ^5.0",
+                "symfony/event-dispatcher": "^2.7.51 || ^3.0 || ^4.0 || ^5.0",
+                "symfony/translation": "^2.7.51 || ^3.0 || ^4.0 || ^5.0",
+                "symfony/yaml": "^2.7.51 || ^3.0 || ^4.0 || ^5.0"
             },
             "require-dev": {
                 "herrera-io/box": "~1.6.1",
-                "phpunit/phpunit": "^4.8.36|^6.3",
-                "symfony/process": "~2.5|~3.0|~4.0"
+                "phpunit/phpunit": "^4.8.36 || ^6.3",
+                "symfony/process": "~2.5 || ^3.0 || ^4.0 || ^5.0"
+            },
+            "suggest": {
+                "ext-dom": "Needed to output test results in JUnit format."
             },
             "bin": [
                 "bin/behat"
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.5.x-dev"
+                    "dev-master": "3.6.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Behat\\Behat": "src/",
-                    "Behat\\Testwork": "src/"
+                "psr-4": {
+                    "Behat\\Behat\\": "src/Behat/Behat/",
+                    "Behat\\Testwork\\": "src/Behat/Testwork/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 "symfony",
                 "testing"
             ],
-            "time": "2018-08-10T18:56:51+00:00"
+            "time": "2020-02-06T09:54:48+00:00"
         },
         {
             "name": "behat/gherkin",
-            "version": "v4.6.0",
+            "version": "v4.6.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/Gherkin.git",
-                "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07"
+                "reference": "51ac4500c4dc30cbaaabcd2f25694299df666a31"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Gherkin/zipball/ab0a02ea14893860bca00f225f5621d351a3ad07",
-                "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07",
+                "url": "https://api.github.com/repos/Behat/Gherkin/zipball/51ac4500c4dc30cbaaabcd2f25694299df666a31",
+                "reference": "51ac4500c4dc30cbaaabcd2f25694299df666a31",
                 "shasum": ""
             },
             "require": {
                 "gherkin",
                 "parser"
             ],
-            "time": "2019-01-16T14:22:17+00:00"
+            "time": "2020-03-17T14:03:26+00:00"
         },
         {
             "name": "behat/mink",
-            "version": "v1.7.1",
+            "version": "v1.8.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/minkphp/Mink.git",
-                "reference": "e6930b9c74693dff7f4e58577e1b1743399f3ff9"
+                "reference": "07c6a9fe3fa98c2de074b25d9ed26c22904e3887"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/minkphp/Mink/zipball/e6930b9c74693dff7f4e58577e1b1743399f3ff9",
-                "reference": "e6930b9c74693dff7f4e58577e1b1743399f3ff9",
+                "url": "https://api.github.com/repos/minkphp/Mink/zipball/07c6a9fe3fa98c2de074b25d9ed26c22904e3887",
+                "reference": "07c6a9fe3fa98c2de074b25d9ed26c22904e3887",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.1",
-                "symfony/css-selector": "~2.1|~3.0"
+                "symfony/css-selector": "^2.7|^3.0|^4.0|^5.0"
             },
             "require-dev": {
-                "symfony/phpunit-bridge": "~2.7|~3.0"
+                "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20",
+                "symfony/debug": "^2.7|^3.0|^4.0",
+                "symfony/phpunit-bridge": "^3.4.38 || ^5.0.5"
             },
             "suggest": {
                 "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)",
                 "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation",
                 "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)",
-                "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)"
+                "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)",
+                "dmore/chrome-mink-driver": "fast and JS-enabled driver for any app (requires chromium or google chrome)"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.7.x-dev"
+                    "dev-master": "1.8.x-dev"
                 }
             },
             "autoload": {
                 "testing",
                 "web"
             ],
-            "time": "2016-03-05T08:26:18+00:00"
+            "time": "2020-03-11T15:45:53+00:00"
         },
         {
             "name": "behat/mink-browserkit-driver",
-            "version": "1.3.3",
+            "version": "v1.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/minkphp/MinkBrowserKitDriver.git",
-                "reference": "1b9a7ce903cfdaaec5fb32bfdbb26118343662eb"
+                "reference": "e3b90840022ebcd544c7b394a3c9597ae242cbee"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/1b9a7ce903cfdaaec5fb32bfdbb26118343662eb",
-                "reference": "1b9a7ce903cfdaaec5fb32bfdbb26118343662eb",
+                "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/e3b90840022ebcd544c7b394a3c9597ae242cbee",
+                "reference": "e3b90840022ebcd544c7b394a3c9597ae242cbee",
                 "shasum": ""
             },
             "require": {
             },
             "require-dev": {
                 "mink/driver-testsuite": "dev-master",
+                "symfony/debug": "^2.7|^3.0|^4.0",
                 "symfony/http-kernel": "~2.3|~3.0|~4.0"
             },
             "type": "mink-driver",
                 "browser",
                 "testing"
             ],
-            "time": "2018-05-02T09:25:31+00:00"
+            "time": "2020-03-11T09:49:45+00:00"
         },
         {
             "name": "behat/mink-extension",
         },
         {
             "name": "behat/mink-selenium2-driver",
-            "version": "v1.3.1",
+            "version": "v1.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/minkphp/MinkSelenium2Driver.git",
-                "reference": "473a9f3ebe0c134ee1e623ce8a9c852832020288"
+                "reference": "312a967dd527f28980cce40850339cd5316da092"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/473a9f3ebe0c134ee1e623ce8a9c852832020288",
-                "reference": "473a9f3ebe0c134ee1e623ce8a9c852832020288",
+                "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/312a967dd527f28980cce40850339cd5316da092",
+                "reference": "312a967dd527f28980cce40850339cd5316da092",
                 "shasum": ""
             },
             "require": {
                 "behat/mink": "~1.7@dev",
                 "instaclick/php-webdriver": "~1.1",
-                "php": ">=5.3.1"
+                "php": ">=5.4"
             },
             "require-dev": {
-                "symfony/phpunit-bridge": "~2.7"
+                "mink/driver-testsuite": "dev-master"
             },
             "type": "mink-driver",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.3.x-dev"
+                    "dev-master": "1.4.x-dev"
                 }
             },
             "autoload": {
                 "MIT"
             ],
             "authors": [
-                {
-                    "name": "Konstantin Kudryashov",
-                    "email": "ever.zet@gmail.com",
-                    "homepage": "http://everzet.com"
-                },
                 {
                     "name": "Pete Otaqui",
                     "email": "pete@otaqui.com",
                     "homepage": "https://github.com/pete-otaqui"
+                },
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
                 }
             ],
             "description": "Selenium2 (WebDriver) driver for Mink framework",
                 "testing",
                 "webdriver"
             ],
-            "time": "2016-03-05T09:10:18+00:00"
+            "time": "2020-03-11T14:43:21+00:00"
         },
         {
             "name": "behat/transliterator",
-            "version": "v1.2.0",
+            "version": "v1.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/Transliterator.git",
-                "reference": "826ce7e9c2a6664c0d1f381cbb38b1fb80a7ee2c"
+                "reference": "3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Transliterator/zipball/826ce7e9c2a6664c0d1f381cbb38b1fb80a7ee2c",
-                "reference": "826ce7e9c2a6664c0d1f381cbb38b1fb80a7ee2c",
+                "url": "https://api.github.com/repos/Behat/Transliterator/zipball/3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc",
+                "reference": "3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc",
                 "shasum": ""
             },
             "require": {
             },
             "require-dev": {
                 "chuyskywalker/rolling-curl": "^3.1",
-                "php-yaoi/php-yaoi": "^1.0"
+                "php-yaoi/php-yaoi": "^1.0",
+                "phpunit/phpunit": "^4.8.36|^6.3"
             },
             "type": "library",
             "extra": {
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Behat\\Transliterator": "src/"
+                "psr-4": {
+                    "Behat\\Transliterator\\": "src/Behat/Transliterator"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 "slug",
                 "transliterator"
             ],
-            "time": "2017-04-04T11:38:05+00:00"
+            "time": "2020-01-14T16:39:13+00:00"
         },
         {
             "name": "container-interop/container-interop",
         },
         {
             "name": "fabpot/goutte",
-            "version": "v3.2.3",
+            "version": "v3.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/FriendsOfPHP/Goutte.git",
-                "reference": "3f0eaf0a40181359470651f1565b3e07e3dd31b8"
+                "reference": "4ab5199e3ec0ffde0ee0b5ecf568a4fb8398dbae"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/3f0eaf0a40181359470651f1565b3e07e3dd31b8",
-                "reference": "3f0eaf0a40181359470651f1565b3e07e3dd31b8",
+                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/4ab5199e3ec0ffde0ee0b5ecf568a4fb8398dbae",
+                "reference": "4ab5199e3ec0ffde0ee0b5ecf568a4fb8398dbae",
                 "shasum": ""
             },
             "require": {
                 "guzzlehttp/guzzle": "^6.0",
-                "php": ">=5.5.0",
-                "symfony/browser-kit": "~2.1|~3.0|~4.0",
-                "symfony/css-selector": "~2.1|~3.0|~4.0",
-                "symfony/dom-crawler": "~2.1|~3.0|~4.0"
+                "php": "^7.1.3",
+                "symfony/browser-kit": "^4.4|^5.0",
+                "symfony/css-selector": "^4.4|^5.0",
+                "symfony/dom-crawler": "^4.4|^5.0"
             },
             "require-dev": {
-                "symfony/phpunit-bridge": "^3.3 || ^4"
+                "symfony/phpunit-bridge": "^5.0"
             },
             "type": "application",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.2-dev"
+                    "dev-master": "3.3-dev"
                 }
             },
             "autoload": {
             "keywords": [
                 "scraper"
             ],
-            "time": "2018-06-29T15:13:57+00:00"
+            "time": "2019-12-06T13:11:18+00:00"
         },
         {
             "name": "guzzlehttp/guzzle",
-            "version": "6.5.0",
+            "version": "6.5.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "dbc2bc3a293ed6b1ae08a3651e2bfd213d19b6a5"
+                "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/dbc2bc3a293ed6b1ae08a3651e2bfd213d19b6a5",
-                "reference": "dbc2bc3a293ed6b1ae08a3651e2bfd213d19b6a5",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/aab4ebd862aa7d04f01a4b51849d657db56d882e",
+                "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e",
                 "shasum": ""
             },
             "require": {
                 "ext-json": "*",
                 "guzzlehttp/promises": "^1.0",
                 "guzzlehttp/psr7": "^1.6.1",
-                "php": ">=5.5"
+                "php": ">=5.5",
+                "symfony/polyfill-intl-idn": "^1.11"
             },
             "require-dev": {
                 "ext-curl": "*",
                 "psr/log": "^1.1"
             },
             "suggest": {
-                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
                 "psr/log": "Required for using the Log middleware"
             },
             "type": "library",
                 "rest",
                 "web service"
             ],
-            "time": "2019-12-07T18:20:45+00:00"
+            "time": "2020-04-18T10:38:46+00:00"
         },
         {
             "name": "guzzlehttp/promises",
         },
         {
             "name": "moodlehq/behat-extension",
-            "version": "v3.39.1",
+            "version": "v3.39.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/moodlehq/moodle-behat-extension.git",
-                "reference": "e61855d292200e7d324241f49ae4c03bad607e70"
+                "reference": "d05ea443ff24f90edb9b31c92e4dfe67c58a0b4b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/e61855d292200e7d324241f49ae4c03bad607e70",
-                "reference": "e61855d292200e7d324241f49ae4c03bad607e70",
+                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/d05ea443ff24f90edb9b31c92e4dfe67c58a0b4b",
+                "reference": "d05ea443ff24f90edb9b31c92e4dfe67c58a0b4b",
                 "shasum": ""
             },
             "require": {
-                "behat/behat": "3.5.*",
-                "behat/mink": "~1.7",
-                "behat/mink-extension": "~2.2",
+                "behat/behat": "3.6.*",
+                "behat/mink": "~1.8",
+                "behat/mink-extension": "~2.3",
                 "behat/mink-goutte-driver": "~1.2",
-                "behat/mink-selenium2-driver": "~1.3",
+                "behat/mink-selenium2-driver": "~1.4",
                 "php": ">=7.2.0",
-                "symfony/process": "2.8.*"
+                "symfony/process": "^4.0 || ^5.0"
             },
             "type": "library",
             "autoload": {
                 "Behat",
                 "moodle"
             ],
-            "time": "2019-12-05T23:18:23+00:00"
+            "time": "2020-04-20T09:32:44+00:00"
         },
         {
             "name": "myclabs/deep-copy",
-            "version": "1.9.4",
+            "version": "1.9.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/myclabs/DeepCopy.git",
-                "reference": "579bb7356d91f9456ccd505f24ca8b667966a0a7"
+                "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/579bb7356d91f9456ccd505f24ca8b667966a0a7",
-                "reference": "579bb7356d91f9456ccd505f24ca8b667966a0a7",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef",
+                "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef",
                 "shasum": ""
             },
             "require": {
                 "object",
                 "object graph"
             ],
-            "time": "2019-12-15T19:12:40+00:00"
+            "time": "2020-01-17T21:11:47+00:00"
         },
         {
             "name": "phar-io/manifest",
         },
         {
             "name": "phpdocumentor/reflection-docblock",
-            "version": "4.3.2",
+            "version": "5.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
-                "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e"
+                "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e",
-                "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e",
+                "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.0",
-                "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0",
-                "phpdocumentor/type-resolver": "~0.4 || ^1.0.0",
-                "webmozart/assert": "^1.0"
+                "ext-filter": "^7.1",
+                "php": "^7.2",
+                "phpdocumentor/reflection-common": "^2.0",
+                "phpdocumentor/type-resolver": "^1.0",
+                "webmozart/assert": "^1"
             },
             "require-dev": {
-                "doctrine/instantiator": "^1.0.5",
-                "mockery/mockery": "^1.0",
-                "phpunit/phpunit": "^6.4"
+                "doctrine/instantiator": "^1",
+                "mockery/mockery": "^1"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "4.x-dev"
+                    "dev-master": "5.x-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "phpDocumentor\\Reflection\\": [
-                        "src/"
-                    ]
+                    "phpDocumentor\\Reflection\\": "src"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 {
                     "name": "Mike van Riel",
                     "email": "me@mikevanriel.com"
+                },
+                {
+                    "name": "Jaap van Otterdijk",
+                    "email": "account@ijaap.nl"
                 }
             ],
             "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
-            "time": "2019-09-12T14:27:41+00:00"
+            "time": "2020-02-22T12:28:44+00:00"
         },
         {
             "name": "phpdocumentor/type-resolver",
-            "version": "1.0.1",
+            "version": "1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/TypeResolver.git",
-                "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9"
+                "reference": "7462d5f123dfc080dfdf26897032a6513644fc95"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
-                "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
+                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95",
+                "reference": "7462d5f123dfc080dfdf26897032a6513644fc95",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.1",
+                "php": "^7.2",
                 "phpdocumentor/reflection-common": "^2.0"
             },
             "require-dev": {
-                "ext-tokenizer": "^7.1",
-                "mockery/mockery": "~1",
-                "phpunit/phpunit": "^7.0"
+                "ext-tokenizer": "^7.2",
+                "mockery/mockery": "~1"
             },
             "type": "library",
             "extra": {
                 }
             ],
             "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
-            "time": "2019-08-22T18:11:29+00:00"
+            "time": "2020-02-18T18:59:58+00:00"
         },
         {
             "name": "phpspec/prophecy",
-            "version": "1.10.0",
+            "version": "v1.10.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "d638ebbb58daba25a6a0dc7969e1358a0e3c6682"
+                "reference": "451c3cd1418cf640de218914901e51b064abb093"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d638ebbb58daba25a6a0dc7969e1358a0e3c6682",
-                "reference": "d638ebbb58daba25a6a0dc7969e1358a0e3c6682",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093",
+                "reference": "451c3cd1418cf640de218914901e51b064abb093",
                 "shasum": ""
             },
             "require": {
                 "doctrine/instantiator": "^1.0.2",
                 "php": "^5.3|^7.0",
                 "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
-                "sebastian/comparator": "^1.2.3|^2.0|^3.0",
-                "sebastian/recursion-context": "^1.0|^2.0|^3.0"
+                "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
+                "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
             },
             "require-dev": {
                 "phpspec/phpspec": "^2.5 || ^3.2",
                 "spy",
                 "stub"
             ],
-            "time": "2019-12-17T16:54:23+00:00"
+            "time": "2020-03-05T15:02:03+00:00"
         },
         {
             "name": "phpunit/dbunit",
         },
         {
             "name": "phpunit/phpunit",
-            "version": "7.5.18",
+            "version": "7.5.20",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "fcf6c4bfafaadc07785528b06385cce88935474d"
+                "reference": "9467db479d1b0487c99733bb1e7944d32deded2c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fcf6c4bfafaadc07785528b06385cce88935474d",
-                "reference": "fcf6c4bfafaadc07785528b06385cce88935474d",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c",
+                "reference": "9467db479d1b0487c99733bb1e7944d32deded2c",
                 "shasum": ""
             },
             "require": {
                 "testing",
                 "xunit"
             ],
-            "time": "2019-12-06T05:14:37+00:00"
+            "time": "2020-01-08T08:45:45+00:00"
         },
         {
             "name": "psr/container",
             "time": "2017-02-14T16:28:37+00:00"
         },
         {
-            "name": "psr/http-message",
-            "version": "1.0.1",
+            "name": "psr/event-dispatcher",
+            "version": "1.0.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/php-fig/http-message.git",
-                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+                "url": "https://github.com/php-fig/event-dispatcher.git",
+                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
-                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.0"
+                "php": ">=7.2.0"
             },
             "type": "library",
             "extra": {
             },
             "autoload": {
                 "psr-4": {
-                    "Psr\\Http\\Message\\": "src/"
+                    "Psr\\EventDispatcher\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                     "homepage": "http://www.php-fig.org/"
                 }
             ],
-            "description": "Common interface for HTTP messages",
-            "homepage": "https://github.com/php-fig/http-message",
+            "description": "Standard interfaces for event handling.",
             "keywords": [
-                "http",
-                "http-message",
+                "events",
                 "psr",
-                "psr-7",
-                "request",
-                "response"
+                "psr-14"
             ],
-            "time": "2016-08-06T14:39:51+00:00"
+            "time": "2019-01-08T18:20:26+00:00"
         },
         {
-            "name": "psr/log",
-            "version": "1.1.2",
+            "name": "psr/http-message",
+            "version": "1.0.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/php-fig/log.git",
-                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801"
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801",
-                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1.x-dev"
+                    "dev-master": "1.0.x-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Psr\\Log\\": "Psr/Log/"
+                    "Psr\\Http\\Message\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                     "homepage": "http://www.php-fig.org/"
                 }
             ],
-            "description": "Common interface for logging libraries",
-            "homepage": "https://github.com/php-fig/log",
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
             "keywords": [
-                "log",
+                "http",
+                "http-message",
                 "psr",
-                "psr-3"
+                "psr-7",
+                "request",
+                "response"
             ],
-            "time": "2019-11-01T11:05:21+00:00"
+            "time": "2016-08-06T14:39:51+00:00"
         },
         {
             "name": "ralouphie/getallheaders",
         },
         {
             "name": "symfony/browser-kit",
-            "version": "v4.4.1",
+            "version": "v4.4.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/browser-kit.git",
-                "reference": "e19e465c055137938afd40cfddd687e7511bbbf0"
+                "reference": "e4b0dc1b100bf75b5717c5b451397f230a618a42"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/e19e465c055137938afd40cfddd687e7511bbbf0",
-                "reference": "e19e465c055137938afd40cfddd687e7511bbbf0",
+                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/e4b0dc1b100bf75b5717c5b451397f230a618a42",
+                "reference": "e4b0dc1b100bf75b5717c5b451397f230a618a42",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony BrowserKit Component",
             "homepage": "https://symfony.com",
-            "time": "2019-10-28T20:30:34+00:00"
-        },
-        {
-            "name": "symfony/class-loader",
-            "version": "v3.4.36",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/class-loader.git",
-                "reference": "e212b06996819a2bce026a63da03b7182d05a690"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/class-loader/zipball/e212b06996819a2bce026a63da03b7182d05a690",
-                "reference": "e212b06996819a2bce026a63da03b7182d05a690",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^5.5.9|>=7.0.8"
-            },
-            "require-dev": {
-                "symfony/finder": "~2.8|~3.0|~4.0",
-                "symfony/polyfill-apcu": "~1.1"
-            },
-            "suggest": {
-                "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.4-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\ClassLoader\\": ""
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
                 },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
                 {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
                 },
                 {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
                 }
             ],
-            "description": "Symfony ClassLoader Component",
-            "homepage": "https://symfony.com",
-            "time": "2019-08-20T13:31:17+00:00"
+            "time": "2020-03-28T10:15:50+00:00"
         },
         {
             "name": "symfony/config",
-            "version": "v4.4.1",
+            "version": "v4.4.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/config.git",
-                "reference": "7aa5817f1b7a8ed377752b90fcc47dfb3c67b40c"
+                "reference": "3f4a3de1af498ed0ea653d4dc2317794144e6ca4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/7aa5817f1b7a8ed377752b90fcc47dfb3c67b40c",
-                "reference": "7aa5817f1b7a8ed377752b90fcc47dfb3c67b40c",
+                "url": "https://api.github.com/repos/symfony/config/zipball/3f4a3de1af498ed0ea653d4dc2317794144e6ca4",
+                "reference": "3f4a3de1af498ed0ea653d4dc2317794144e6ca4",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Config Component",
             "homepage": "https://symfony.com",
-            "time": "2019-12-01T10:50:45+00:00"
+            "time": "2020-03-27T16:54:36+00:00"
         },
         {
             "name": "symfony/console",
-            "version": "v3.3.18",
+            "version": "v5.0.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "af7ec995de93671c03cc1b4e3176c8588bc79dcc"
+                "reference": "5fa1caadc8cdaa17bcfb25219f3b53fe294a9935"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/af7ec995de93671c03cc1b4e3176c8588bc79dcc",
-                "reference": "af7ec995de93671c03cc1b4e3176c8588bc79dcc",
+                "url": "https://api.github.com/repos/symfony/console/zipball/5fa1caadc8cdaa17bcfb25219f3b53fe294a9935",
+                "reference": "5fa1caadc8cdaa17bcfb25219f3b53fe294a9935",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5.9|>=7.0.8",
-                "symfony/debug": "~2.8|~3.0",
-                "symfony/polyfill-mbstring": "~1.0"
+                "php": "^7.2.5",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php73": "^1.8",
+                "symfony/service-contracts": "^1.1|^2"
             },
             "conflict": {
-                "symfony/dependency-injection": "<3.3"
+                "symfony/dependency-injection": "<4.4",
+                "symfony/event-dispatcher": "<4.4",
+                "symfony/lock": "<4.4",
+                "symfony/process": "<4.4"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0"
             },
             "require-dev": {
                 "psr/log": "~1.0",
-                "symfony/config": "~3.3",
-                "symfony/dependency-injection": "~3.3",
-                "symfony/event-dispatcher": "~2.8|~3.0",
-                "symfony/filesystem": "~2.8|~3.0",
-                "symfony/process": "~2.8|~3.0"
+                "symfony/config": "^4.4|^5.0",
+                "symfony/dependency-injection": "^4.4|^5.0",
+                "symfony/event-dispatcher": "^4.4|^5.0",
+                "symfony/lock": "^4.4|^5.0",
+                "symfony/process": "^4.4|^5.0",
+                "symfony/var-dumper": "^4.4|^5.0"
             },
             "suggest": {
                 "psr/log": "For using the console logger",
                 "symfony/event-dispatcher": "",
-                "symfony/filesystem": "",
+                "symfony/lock": "",
                 "symfony/process": ""
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "5.0-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2018-01-29T09:02:23+00:00"
+            "time": "2020-03-30T11:42:42+00:00"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v3.4.36",
+            "version": "v5.0.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "f819f71ae3ba6f396b4c015bd5895de7d2f1f85f"
+                "reference": "5f8d5271303dad260692ba73dfa21777d38e124e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/f819f71ae3ba6f396b4c015bd5895de7d2f1f85f",
-                "reference": "f819f71ae3ba6f396b4c015bd5895de7d2f1f85f",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/5f8d5271303dad260692ba73dfa21777d38e124e",
+                "reference": "5f8d5271303dad260692ba73dfa21777d38e124e",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5.9|>=7.0.8"
+                "php": "^7.2.5"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.4-dev"
+                    "dev-master": "5.0-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony CssSelector Component",
             "homepage": "https://symfony.com",
-            "time": "2019-10-01T11:57:37+00:00"
-        },
-        {
-            "name": "symfony/debug",
-            "version": "v3.4.36",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/debug.git",
-                "reference": "f72e33fdb1170b326e72c3157f0cd456351dd086"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/debug/zipball/f72e33fdb1170b326e72c3157f0cd456351dd086",
-                "reference": "f72e33fdb1170b326e72c3157f0cd456351dd086",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^5.5.9|>=7.0.8",
-                "psr/log": "~1.0"
-            },
-            "conflict": {
-                "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
-            },
-            "require-dev": {
-                "symfony/http-kernel": "~2.8|~3.0|~4.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.4-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Debug\\": ""
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
                 },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
                 {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
                 },
                 {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
                 }
             ],
-            "description": "Symfony Debug Component",
-            "homepage": "https://symfony.com",
-            "time": "2019-10-24T15:33:53+00:00"
+            "time": "2020-03-27T16:56:45+00:00"
         },
         {
             "name": "symfony/dependency-injection",
-            "version": "v3.3.18",
+            "version": "v4.4.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dependency-injection.git",
-                "reference": "54243abc4e1a1a15e274e391bd6f7090b44711f1"
+                "reference": "755b18859be26b90f4bf63753432d3387458bf31"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/54243abc4e1a1a15e274e391bd6f7090b44711f1",
-                "reference": "54243abc4e1a1a15e274e391bd6f7090b44711f1",
+                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/755b18859be26b90f4bf63753432d3387458bf31",
+                "reference": "755b18859be26b90f4bf63753432d3387458bf31",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5.9|>=7.0.8",
-                "psr/container": "^1.0"
+                "php": "^7.1.3",
+                "psr/container": "^1.0",
+                "symfony/service-contracts": "^1.1.6|^2"
             },
             "conflict": {
-                "symfony/config": "<3.3.7",
-                "symfony/finder": "<3.3",
-                "symfony/yaml": "<3.3"
+                "symfony/config": "<4.3|>=5.0",
+                "symfony/finder": "<3.4",
+                "symfony/proxy-manager-bridge": "<3.4",
+                "symfony/yaml": "<3.4"
             },
             "provide": {
-                "psr/container-implementation": "1.0"
+                "psr/container-implementation": "1.0",
+                "symfony/service-implementation": "1.0"
             },
             "require-dev": {
-                "symfony/config": "~3.3",
-                "symfony/expression-language": "~2.8|~3.0",
-                "symfony/yaml": "~3.3"
+                "symfony/config": "^4.3",
+                "symfony/expression-language": "^3.4|^4.0|^5.0",
+                "symfony/yaml": "^3.4|^4.0|^5.0"
             },
             "suggest": {
                 "symfony/config": "",
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.4-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony DependencyInjection Component",
             "homepage": "https://symfony.com",
-            "time": "2018-01-29T09:02:23+00:00"
+            "time": "2020-03-30T10:09:30+00:00"
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v4.4.1",
+            "version": "v4.4.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "36bbcab9369fc2f583220890efd43bf262d563fd"
+                "reference": "4d0fb3374324071ecdd94898367a3fa4b5563162"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/36bbcab9369fc2f583220890efd43bf262d563fd",
-                "reference": "36bbcab9369fc2f583220890efd43bf262d563fd",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4d0fb3374324071ecdd94898367a3fa4b5563162",
+                "reference": "4d0fb3374324071ecdd94898367a3fa4b5563162",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony DomCrawler Component",
             "homepage": "https://symfony.com",
-            "time": "2019-10-29T11:38:30+00:00"
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-03-29T19:12:22+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v3.4.36",
+            "version": "v5.0.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "f9031c22ec127d4a2450760f81a8677fe8a10177"
+                "reference": "24f40d95385774ed5c71dbf014edd047e2f2f3dc"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f9031c22ec127d4a2450760f81a8677fe8a10177",
-                "reference": "f9031c22ec127d4a2450760f81a8677fe8a10177",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/24f40d95385774ed5c71dbf014edd047e2f2f3dc",
+                "reference": "24f40d95385774ed5c71dbf014edd047e2f2f3dc",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5.9|>=7.0.8"
+                "php": "^7.2.5",
+                "symfony/event-dispatcher-contracts": "^2"
             },
             "conflict": {
-                "symfony/dependency-injection": "<3.3"
+                "symfony/dependency-injection": "<4.4"
+            },
+            "provide": {
+                "psr/event-dispatcher-implementation": "1.0",
+                "symfony/event-dispatcher-implementation": "2.0"
             },
             "require-dev": {
                 "psr/log": "~1.0",
-                "symfony/config": "~2.8|~3.0|~4.0",
-                "symfony/dependency-injection": "~3.3|~4.0",
-                "symfony/expression-language": "~2.8|~3.0|~4.0",
-                "symfony/stopwatch": "~2.8|~3.0|~4.0"
+                "symfony/config": "^4.4|^5.0",
+                "symfony/dependency-injection": "^4.4|^5.0",
+                "symfony/expression-language": "^4.4|^5.0",
+                "symfony/http-foundation": "^4.4|^5.0",
+                "symfony/service-contracts": "^1.1|^2",
+                "symfony/stopwatch": "^4.4|^5.0"
             },
             "suggest": {
                 "symfony/dependency-injection": "",
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.4-dev"
+                    "dev-master": "5.0-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2019-10-24T15:33:53+00:00"
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-03-27T16:56:45+00:00"
+        },
+        {
+            "name": "symfony/event-dispatcher-contracts",
+            "version": "v2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+                "reference": "af23c2584d4577d54661c434446fb8fbed6025dd"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/af23c2584d4577d54661c434446fb8fbed6025dd",
+                "reference": "af23c2584d4577d54661c434446fb8fbed6025dd",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5",
+                "psr/event-dispatcher": "^1"
+            },
+            "suggest": {
+                "symfony/event-dispatcher-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\EventDispatcher\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to dispatching event",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "time": "2019-11-18T17:27:11+00:00"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v5.0.1",
+            "version": "v5.0.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "1d71f670bc5a07b9ccc97dc44f932177a322d4e6"
+                "reference": "ca3b87dd09fff9b771731637f5379965fbfab420"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/1d71f670bc5a07b9ccc97dc44f932177a322d4e6",
-                "reference": "1d71f670bc5a07b9ccc97dc44f932177a322d4e6",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/ca3b87dd09fff9b771731637f5379965fbfab420",
+                "reference": "ca3b87dd09fff9b771731637f5379965fbfab420",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2019-11-26T23:25:11+00:00"
+            "time": "2020-03-27T16:56:45+00:00"
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.13.1",
+            "version": "v1.15.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
+                "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
-                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14",
+                "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.13-dev"
+                    "dev-master": "1.15-dev"
                 }
             },
             "autoload": {
                 "polyfill",
                 "portable"
             ],
-            "time": "2019-11-27T13:56:44+00:00"
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-02-27T09:26:54+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-idn",
+            "version": "v1.15.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-idn.git",
+                "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
+                "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "symfony/polyfill-mbstring": "^1.3",
+                "symfony/polyfill-php72": "^1.10"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.15-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Idn\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Laurent Bassin",
+                    "email": "laurent@bassin.info"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "idn",
+                "intl",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-03-09T19:04:49+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.13.1",
+            "version": "v1.15.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f"
+                "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f",
-                "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
+                "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.13-dev"
+                    "dev-master": "1.15-dev"
                 }
             },
             "autoload": {
                 "portable",
                 "shim"
             ],
-            "time": "2019-11-27T14:18:11+00:00"
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-03-09T19:04:49+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php72",
+            "version": "v1.15.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php72.git",
+                "reference": "37b0976c78b94856543260ce09b460a7bc852747"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747",
+                "reference": "37b0976c78b94856543260ce09b460a7bc852747",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.15-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php72\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-02-27T09:26:54+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php73",
+            "version": "v1.15.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php73.git",
+                "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7",
+                "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.15-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php73\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-02-27T09:26:54+00:00"
         },
         {
             "name": "symfony/process",
-            "version": "v2.8.52",
+            "version": "v5.0.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8"
+                "reference": "c5ca4a0fc16a0c888067d43fbcfe1f8a53d8e70e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/c3591a09c78639822b0b290d44edb69bf9f05dc8",
-                "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8",
+                "url": "https://api.github.com/repos/symfony/process/zipball/c5ca4a0fc16a0c888067d43fbcfe1f8a53d8e70e",
+                "reference": "c5ca4a0fc16a0c888067d43fbcfe1f8a53d8e70e",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.9"
+                "php": "^7.2.5"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.8-dev"
+                    "dev-master": "5.0-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Process Component",
             "homepage": "https://symfony.com",
-            "time": "2018-11-11T11:18:13+00:00"
+            "time": "2020-03-27T16:56:45+00:00"
+        },
+        {
+            "name": "symfony/service-contracts",
+            "version": "v2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/service-contracts.git",
+                "reference": "144c5e51266b281231e947b51223ba14acf1a749"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749",
+                "reference": "144c5e51266b281231e947b51223ba14acf1a749",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5",
+                "psr/container": "^1.0"
+            },
+            "suggest": {
+                "symfony/service-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Service\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to writing services",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "time": "2019-11-18T17:27:11+00:00"
         },
         {
             "name": "symfony/translation",
-            "version": "v3.3.18",
+            "version": "v4.4.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "90cb5ca3eb84b3053fef876e11e405fd819487fc"
+                "reference": "4e54d336f2eca5facad449d0b0118bb449375b76"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/90cb5ca3eb84b3053fef876e11e405fd819487fc",
-                "reference": "90cb5ca3eb84b3053fef876e11e405fd819487fc",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/4e54d336f2eca5facad449d0b0118bb449375b76",
+                "reference": "4e54d336f2eca5facad449d0b0118bb449375b76",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5.9|>=7.0.8",
-                "symfony/polyfill-mbstring": "~1.0"
+                "php": "^7.1.3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/translation-contracts": "^1.1.6|^2"
             },
             "conflict": {
-                "symfony/config": "<2.8",
-                "symfony/yaml": "<3.3"
+                "symfony/config": "<3.4",
+                "symfony/dependency-injection": "<3.4",
+                "symfony/http-kernel": "<4.4",
+                "symfony/yaml": "<3.4"
+            },
+            "provide": {
+                "symfony/translation-implementation": "1.0"
             },
             "require-dev": {
                 "psr/log": "~1.0",
-                "symfony/config": "~2.8|~3.0",
-                "symfony/intl": "^2.8.18|^3.2.5",
-                "symfony/yaml": "~3.3"
+                "symfony/config": "^3.4|^4.0|^5.0",
+                "symfony/console": "^3.4|^4.0|^5.0",
+                "symfony/dependency-injection": "^3.4|^4.0|^5.0",
+                "symfony/finder": "~2.8|~3.0|~4.0|^5.0",
+                "symfony/http-kernel": "^4.4",
+                "symfony/intl": "^3.4|^4.0|^5.0",
+                "symfony/service-contracts": "^1.1.2|^2",
+                "symfony/yaml": "^3.4|^4.0|^5.0"
             },
             "suggest": {
-                "psr/log": "To use logging capability in translator",
+                "psr/log-implementation": "To use logging capability in translator",
                 "symfony/config": "",
                 "symfony/yaml": ""
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.4-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Translation Component",
             "homepage": "https://symfony.com",
-            "time": "2018-01-18T14:19:00+00:00"
+            "time": "2020-03-27T16:54:36+00:00"
+        },
+        {
+            "name": "symfony/translation-contracts",
+            "version": "v2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/translation-contracts.git",
+                "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/8cc682ac458d75557203b2f2f14b0b92e1c744ed",
+                "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5"
+            },
+            "suggest": {
+                "symfony/translation-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Translation\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to translation",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "time": "2019-11-18T17:27:11+00:00"
         },
         {
             "name": "symfony/yaml",
-            "version": "v3.3.18",
+            "version": "v4.4.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
-                "reference": "af615970e265543a26ee712c958404eb9b7ac93d"
+                "reference": "ef166890d821518106da3560086bfcbeb4fadfec"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/af615970e265543a26ee712c958404eb9b7ac93d",
-                "reference": "af615970e265543a26ee712c958404eb9b7ac93d",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/ef166890d821518106da3560086bfcbeb4fadfec",
+                "reference": "ef166890d821518106da3560086bfcbeb4fadfec",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5.9|>=7.0.8"
+                "php": "^7.1.3",
+                "symfony/polyfill-ctype": "~1.8"
+            },
+            "conflict": {
+                "symfony/console": "<3.4"
             },
             "require-dev": {
-                "symfony/console": "~2.8|~3.0"
+                "symfony/console": "^3.4|^4.0|^5.0"
             },
             "suggest": {
                 "symfony/console": "For validating YAML files using the lint command"
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "4.4-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Yaml Component",
             "homepage": "https://symfony.com",
-            "time": "2018-01-20T15:04:53+00:00"
+            "time": "2020-03-30T11:41:10+00:00"
         },
         {
             "name": "theseer/tokenizer",
         },
         {
             "name": "webmozart/assert",
-            "version": "1.6.0",
+            "version": "1.8.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/webmozart/assert.git",
-                "reference": "573381c0a64f155a0d9a23f4b0c797194805b925"
+                "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925",
-                "reference": "573381c0a64f155a0d9a23f4b0c797194805b925",
+                "url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6",
+                "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6",
                 "shasum": ""
             },
             "require": {
                 "symfony/polyfill-ctype": "^1.8"
             },
             "conflict": {
-                "vimeo/psalm": "<3.6.0"
+                "vimeo/psalm": "<3.9.1"
             },
             "require-dev": {
                 "phpunit/phpunit": "^4.8.36 || ^7.5.13"
                 "check",
                 "validate"
             ],
-            "time": "2019-11-24T13:36:37+00:00"
+            "time": "2020-04-18T12:12:48+00:00"
         }
     ],
     "aliases": [
     "prefer-stable": false,
     "prefer-lowest": false,
     "platform": [],
-    "platform-dev": []
+    "platform-dev": [],
+    "plugin-api-version": "1.1.0"
 }
index 51f7e55..90989bb 100644 (file)
@@ -628,6 +628,13 @@ $CFG->admin = 'admin';
 //
 //      $CFG->debugsessionlock = 5;
 //
+// There are times when a session lock is not required during a request. For a page/service to opt-in whether or not a
+// session lock is required this setting must first be set to 'true'.
+// This is an experimental issue. The session store can not be in the session, please
+// see https://docs.moodle.org/en/Session_handling#Read_only_sessions.
+//
+//      $CFG->enable_read_only_sessions = true;
+//
 // Uninstall plugins from CLI only. This stops admins from uninstalling plugins from the graphical admin
 // user interface, and forces plugins to be uninstalled from the Command Line tool only, found at
 // admin/cli/plugin_uninstall.php.
@@ -735,6 +742,9 @@ $CFG->admin = 'admin';
 // Force developer level debug and add debug info to the output of cron
 // $CFG->showcrondebugging = true;
 //
+// Force result of checks used to determine whether a site is considered "public" or not (such as for site registration).
+// $CFG->site_is_public = false;
+//
 //=========================================================================
 // 8. FORCED SETTINGS
 //=========================================================================
diff --git a/contentbank/amd/build/actions.min.js b/contentbank/amd/build/actions.min.js
new file mode 100644 (file)
index 0000000..2e95d9a
Binary files /dev/null and b/contentbank/amd/build/actions.min.js differ
diff --git a/contentbank/amd/build/actions.min.js.map b/contentbank/amd/build/actions.min.js.map
new file mode 100644 (file)
index 0000000..05f46fb
Binary files /dev/null and b/contentbank/amd/build/actions.min.js.map differ
diff --git a/contentbank/amd/src/actions.js b/contentbank/amd/src/actions.js
new file mode 100644 (file)
index 0000000..caa25c5
--- /dev/null
@@ -0,0 +1,162 @@
+// 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/>.
+
+/**
+ * Module to manage content bank actions, such as delete or rename.
+ *
+ * @module     core_contentbank/actions
+ * @package    core_contentbank
+ * @copyright  2020 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define([
+    'jquery',
+    'core/ajax',
+    'core/notification',
+    'core/str',
+    'core/templates',
+    'core/url',
+    'core/modal_factory',
+    'core/modal_events'],
+function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents) {
+
+    /**
+     * List of action selectors.
+     *
+     * @type {{DELETE_CONTENT: string}}
+     */
+    var ACTIONS = {
+        DELETE_CONTENT: '[data-action="deletecontent"]',
+    };
+
+    /**
+     * Actions class.
+     */
+    var Actions = function() {
+        this.registerEvents();
+    };
+
+    /**
+     * Register event listeners.
+     */
+    Actions.prototype.registerEvents = function() {
+        $(ACTIONS.DELETE_CONTENT).click(function(e) {
+            e.preventDefault();
+
+            var contentname = $(this).data('contentname');
+            var contentid = $(this).data('contentid');
+            var contextid = $(this).data('contextid');
+
+            var strings = [
+                {
+                    key: 'deletecontent',
+                    component: 'core_contentbank'
+                },
+                {
+                    key: 'deletecontentconfirm',
+                    component: 'core_contentbank',
+                    param: {
+                        name: contentname,
+                    }
+                },
+                {
+                    key: 'delete',
+                    component: 'core'
+                },
+            ];
+
+            var deleteButtonText = '';
+            Str.get_strings(strings).then(function(langStrings) {
+                var modalTitle = langStrings[0];
+                var modalContent = langStrings[1];
+                deleteButtonText = langStrings[2];
+
+                return ModalFactory.create({
+                    title: modalTitle,
+                    body: modalContent,
+                    type: ModalFactory.types.SAVE_CANCEL,
+                    large: true
+                });
+            }).done(function(modal) {
+                modal.setSaveButtonText(deleteButtonText);
+                modal.getRoot().on(ModalEvents.save, function() {
+                    // The action is now confirmed, sending an action for it.
+                    return deleteContent(contentid, contextid);
+                });
+
+                // Handle hidden event.
+                modal.getRoot().on(ModalEvents.hidden, function() {
+                    // Destroy when hidden.
+                    modal.destroy();
+                });
+
+                // Show the modal.
+                modal.show();
+
+                return;
+            }).catch(Notification.exception);
+        });
+    };
+
+    /**
+     * Delete content from the content bank.
+     *
+     * @param {int} contentid The content to delete.
+     * @param {int} contextid The contextid where the content belongs.
+     */
+    function deleteContent(contentid, contextid) {
+        var request = {
+            methodname: 'core_contentbank_delete_content',
+            args: {
+                contentids: {contentid}
+            }
+        };
+
+        var requestType = 'success';
+        Ajax.call([request])[0].then(function(data) {
+            if (data.result) {
+                return Str.get_string('contentdeleted', 'core_contentbank');
+            }
+            requestType = 'error';
+            return Str.get_string('contentnotdeleted', 'core_contentbank');
+
+        }).done(function(message) {
+            var params = {
+                contextid: contextid
+            };
+            if (requestType == 'success') {
+                params.statusmsg = message;
+            } else {
+                params.errormsg = message;
+            }
+            // Redirect to the main content bank page and display the message as a notification.
+            window.location.href = Url.relativeUrl('contentbank/index.php', params, false);
+        }).fail(Notification.exception);
+    }
+
+    return /** @alias module:core_contentbank/actions */ {
+        // Public variables and functions.
+
+        /**
+         * Initialise the contentbank actions.
+         *
+         * @method init
+         * @return {Actions}
+         */
+        'init': function() {
+            return new Actions();
+        }
+    };
+});
index b535b2f..a77b7a0 100644 (file)
@@ -44,25 +44,25 @@ abstract class content {
     /**
      * Content bank constructor
      *
-     * @param stdClass $content A contentbanck_content record.
+     * @param stdClass $record A contentbank_content record.
      * @throws coding_exception If content type is not right.
      */
-    public function __construct(stdClass $content) {
+    public function __construct(stdClass $record) {
         // Content type should exist and be linked to plugin classname.
-        $classname = $content->contenttype.'\\content';
+        $classname = $record->contenttype.'\\content';
         if (get_class($this) != $classname) {
-            throw new coding_exception(get_string('contenttypenotfound', 'error', $content->contenttype));
+            throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
         }
-        $typeclass = $content->contenttype.'\\contenttype';
+        $typeclass = $record->contenttype.'\\contenttype';
         if (!class_exists($typeclass)) {
-            throw new coding_exception(get_string('contenttypenotfound', 'error', $content->contenttype));
+            throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
         }
         // A record with the id must exist in 'contenbank_content' table.
         // To improve performance, we are only checking the id is set, but no querying the database.
-        if (!isset($content->id)) {
+        if (!isset($record->id)) {
             throw new coding_exception(get_string('invalidcontentid', 'error'));
         }
-        $this->content = $content;
+        $this->content = $record;
     }
 
     /**
@@ -211,7 +211,7 @@ abstract class content {
      *
      * @return bool     True if content could be accessed. False otherwise.
      */
-    public function can_view(): bool {
+    public function is_view_allowed(): bool {
         // There's no capability at content level to check,
         // but plugins can overwrite this method in case they want to check something related to content properties.
         return true;
index 2e89d42..0a21353 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Content bank manager class
+ * Content bank class
  *
  * @package    core_contentbank
  * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
@@ -25,7 +25,7 @@
 namespace core_contentbank;
 
 /**
- * Content bank manager class
+ * Content bank class
  *
  * @package    core_contentbank
  * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
@@ -44,8 +44,9 @@ class contentbank {
         $enabledtypes = \core\plugininfo\contenttype::get_enabled_plugins();
         $types = [];
         foreach ($enabledtypes as $name) {
-            $classname = "\\contenttype_$name\\contenttype";
-            if (class_exists($classname)) {
+            $contenttypeclassname = "\\contenttype_$name\\contenttype";
+            $contentclassname = "\\contenttype_$name\\content";
+            if (class_exists($contenttypeclassname) && class_exists($contentclassname)) {
                 $types[] = $name;
             }
         }
@@ -65,16 +66,14 @@ class contentbank {
             $supportedextensions = [];
             foreach ($this->get_enabled_content_types() as $type) {
                 $classname = "\\contenttype_$type\\contenttype";
-                if (class_exists($classname)) {
-                    $manager = new $classname;
-                    if ($manager->is_feature_supported($manager::CAN_UPLOAD)) {
-                        $extensions = $manager->get_manageable_extensions();
-                        foreach ($extensions as $extension) {
-                            if (array_key_exists($extension, $supportedextensions)) {
-                                $supportedextensions[$extension][] = $type;
-                            } else {
-                                $supportedextensions[$extension] = [$type];
-                            }
+                $contenttype = new $classname;
+                if ($contenttype->is_feature_supported($contenttype::CAN_UPLOAD)) {
+                    $extensions = $contenttype->get_manageable_extensions();
+                    foreach ($extensions as $extension) {
+                        if (array_key_exists($extension, $supportedextensions)) {
+                            $supportedextensions[$extension][] = $type;
+                        } else {
+                            $supportedextensions[$extension] = [$type];
                         }
                     }
                 }
@@ -100,12 +99,10 @@ class contentbank {
             foreach ($supportedextensions as $extension => $types) {
                 foreach ($types as $type) {
                     $classname = "\\contenttype_$type\\contenttype";
-                    if (class_exists($classname)) {
-                        $manager = new $classname($context);
-                        if ($manager->can_upload()) {
-                            $contextextensions[$extension] = $type;
-                            break;
-                        }
+                    $contenttype = new $classname($context);
+                    if ($contenttype->can_upload()) {
+                        $contextextensions[$extension] = $type;
+                        break;
                     }
                 }
             }
@@ -155,4 +152,54 @@ class contentbank {
         }
         return null;
     }
+
+    /**
+     * Find the contents with %$search% in the contextid defined.
+     * If contextid and search are empty, all contents are returned.
+     * In all the cases, only the contents for the enabled contentbank-type plugins are returned.
+     *
+     * @param  string|null $search Optional string to search (for now it will search only into the name).
+     * @param  int $contextid Optional contextid to search.
+     * @return array The contents for the enabled contentbank-type plugins having $search as name and placed in $contextid.
+     */
+    public function search_contents(?string $search = null, ?int $contextid = 0): array {
+        global $DB;
+
+        $contents = [];
+
+        // Get only contents for enabled content-type plugins.
+        $contenttypes = array_map(function($contenttypename) {
+            return "contenttype_$contenttypename";
+        }, $this->get_enabled_content_types());
+        if (empty($contenttypes)) {
+            // Early return if there are no content-type plugins enabled.
+            return $contents;
+        }
+
+        list($sqlcontenttypes, $params) = $DB->get_in_or_equal($contenttypes, SQL_PARAMS_NAMED);
+        $sql = " contenttype $sqlcontenttypes ";
+
+        // Filter contents on this context (if defined).
+        if (!empty($contextid)) {
+            $params['contextid'] = $contextid;
+            $sql .= ' AND contextid = :contextid ';
+        }
+
+        // Search for contents having this string (if defined).
+        if (!empty($search)) {
+            $sql .= ' AND ' . $DB->sql_like('name', ':name', false, false);
+            $params['name'] = '%' . $DB->sql_like_escape($search) . '%';
+        }
+
+        $records = $DB->get_records_select('contentbank_content', $sql, $params);
+        foreach ($records as $record) {
+            $contentclass = "\\$record->contenttype\\content";
+            $content = new $contentclass($record);
+            if ($content->is_view_allowed()) {
+                $contents[] = $content;
+            }
+        }
+
+        return $contents;
+    }
 }
index f406282..46e92a9 100644 (file)
@@ -57,29 +57,48 @@ abstract class contenttype {
     /**
      * Fills content_bank table with appropiate information.
      *
-     * @param stdClass $content  An optional content record compatible object (default null)
-     * @return content       Object with content bank information.
+     * @param stdClass $record An optional content record compatible object (default null)
+     * @return content  Object with content bank information.
      */
-    public function create_content(\stdClass $content = null): ?content {
+    public function create_content(\stdClass $record = null): ?content {
         global $USER, $DB;
 
-        $record = new \stdClass();
-        $record->contenttype = $this->get_contenttype_name();
-        $record->contextid = $this->context->id;
-        $record->name = $content->name ?? '';
-        $record->usercreated = $content->usercreated ?? $USER->id;
-        $record->timecreated = time();
-        $record->usermodified = $record->usercreated;
-        $record->timemodified = $record->timecreated;
-        $record->configdata = $content->configdata ?? '';
-        $record->id = $DB->insert_record('contentbank_content', $record);
-        if ($record->id) {
-            $classname = '\\'.$record->contenttype.'\\content';
-            return new $classname($record);
+        $entry = new \stdClass();
+        $entry->contenttype = $this->get_contenttype_name();
+        $entry->contextid = $this->context->id;
+        $entry->name = $record->name ?? '';
+        $entry->usercreated = $record->usercreated ?? $USER->id;
+        $entry->timecreated = time();
+        $entry->usermodified = $entry->usercreated;
+        $entry->timemodified = $entry->timecreated;
+        $entry->configdata = $record->configdata ?? '';
+        $entry->id = $DB->insert_record('contentbank_content', $entry);
+        if ($entry->id) {
+            $classname = '\\'.$entry->contenttype.'\\content';
+            return new $classname($entry);
         }
         return null;
     }
 
+    /**
+     * Delete this content from the content_bank.
+     * This method can be overwritten by the plugins if they need to delete specific information.
+     *
+     * @param  content $content The content to delete.
+     * @return boolean true if the content has been deleted; false otherwise.
+     */
+    public function delete_content(content $content): bool {
+        global $DB;
+
+        // Delete the file if it exists.
+        if ($file = $content->get_file()) {
+            $file->delete();
+        }
+
+        // Delete the contentbank DB entry.
+        return $DB->delete_records('contentbank_content', ['id' => $content->get_id()]);
+    }
+
     /**
      * Returns the contenttype name of this content.
      *
@@ -105,8 +124,8 @@ abstract class contenttype {
     /**
      * Returns the URL where the content will be visualized.
      *
-     * @param stdClass $record  Th content to be displayed.
-     * @return string            URL where to visualize the given content.
+     * @param stdClass $record  The content to be displayed.
+     * @return string           URL where to visualize the given content.
      */
     public function get_view_url(\stdClass $record): string {
         return new moodle_url('/contentbank/view.php', ['id' => $record->id]);
@@ -115,8 +134,8 @@ abstract class contenttype {
     /**
      * Returns the HTML content to add to view.php visualizer.
      *
-     * @param stdClass $record  Th content to be displayed.
-     * @return string            HTML code to include in view.php.
+     * @param stdClass $record  The content to be displayed.
+     * @return string           HTML code to include in view.php.
      */
     public function get_view_content(\stdClass $record): string {
         // Main contenttype class can visualize the content, but plugins could overwrite visualization.
@@ -187,6 +206,40 @@ abstract class contenttype {
         return true;
     }
 
+    /**
+     * Check if the user can delete this content.
+     *
+     * @param  content $content The content to be deleted.
+     * @return bool True if content could be uploaded. False otherwise.
+     */
+    final public function can_delete(content $content): bool {
+        global $USER;
+
+        if ($this->context->id != $content->get_content()->contextid) {
+            // The content has to have exactly the same context as this contenttype.
+            return false;
+        }
+
+        $hascapability = has_capability('moodle/contentbank:deleteanycontent', $this->context);
+        if ($content->get_content()->usercreated == $USER->id) {
+            // This content has been created by the current user; check if she can delete her content.
+            $hascapability = $hascapability || has_capability('moodle/contentbank:deleteowncontent', $this->context);
+        }
+
+        return $hascapability && $this->is_delete_allowed($content);
+    }
+
+    /**
+     * Returns if content allows deleting.
+     *
+     * @param  content $content The content to be deleted.
+     * @return bool True if content allows uploading. False otherwise.
+     */
+    protected function is_delete_allowed(content $content): bool {
+        // Plugins can overwrite this function to add any check they need.
+        return true;
+    }
+
     /**
      * Returns the plugin supports the feature.
      *
diff --git a/contentbank/classes/external/delete_content.php b/contentbank/classes/external/delete_content.php
new file mode 100644 (file)
index 0000000..65846aa
--- /dev/null
@@ -0,0 +1,133 @@
+<?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/>.
+
+/**
+ * This is the external method for deleting a content.
+ *
+ * @package    core_contentbank
+ * @since      Moodle 3.9
+ * @copyright  2020 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_contentbank\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/externallib.php');
+
+use external_api;
+use external_function_parameters;
+use external_multiple_structure;
+use external_single_structure;
+use external_value;
+use external_warnings;
+
+/**
+ * This is the external method for deleting a content.
+ *
+ * @copyright  2020 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class delete_content extends external_api {
+    /**
+     * Parameters.
+     *
+     * @return external_function_parameters
+     */
+    public static function execute_parameters(): external_function_parameters {
+        return new external_function_parameters([
+            'contentids' => new external_multiple_structure(
+                new external_value(PARAM_INT, 'The content id to delete', VALUE_REQUIRED)
+            )
+        ]);
+    }
+
+    /**
+     * Delete content from the contentbank.
+     *
+     * @param  array $contentids List of content ids to delete.
+     * @return array True if the content has been deleted; false and the warning, otherwise.
+     */
+    public static function execute(array $contentids): array {
+        global $DB;
+
+        $result = false;
+        $warnings = [];
+
+        $params = self::validate_parameters(self::execute_parameters(), ['contentids' => $contentids]);
+        foreach ($params['contentids'] as $contentid) {
+            try {
+                $record = $DB->get_record('contentbank_content', ['id' => $contentid], '*', MUST_EXIST);
+                $contenttypeclass = "\\$record->contenttype\\contenttype";
+                if (class_exists($contenttypeclass)) {
+                    $context = \context::instance_by_id($record->contextid, MUST_EXIST);
+                    self::validate_context($context);
+                    $contenttype = new $contenttypeclass($context);
+                    $contentclass = "\\$record->contenttype\\content";
+                    $content = new $contentclass($record);
+                    // Check capability.
+                    if ($contenttype->can_delete($content)) {
+                        // This content can be deleted.
+                        if (!$contenttype->delete_content($content)) {
+                            $warnings[] = [
+                                'item' => $contentid,
+                                'warningcode' => 'contentnotdeleted',
+                                'message' => get_string('contentnotdeleted', 'core_contentbank')
+                            ];
+                        }
+                    } else {
+                        // The user has no permission to delete this content.
+                        $warnings[] = [
+                            'item' => $contentid,
+                            'warningcode' => 'nopermissiontodelete',
+                            'message' => get_string('nopermissiontodelete', 'core_contentbank')
+                        ];
+                    }
+                }
+            } catch (\moodle_exception $e) {
+                // The content or the context don't exist.
+                $warnings[] = [
+                    'item' => $contentid,
+                    'warningcode' => 'exception',
+                    'message' => $e->getMessage()
+                ];
+            }
+        }
+
+        if (empty($warnings)) {
+            $result = true;
+        }
+
+        return [
+            'result' => $result,
+            'warnings' => $warnings
+        ];
+    }
+
+    /**
+     * Return.
+     *
+     * @return external_single_structure
+     */
+    public static function execute_returns(): external_single_structure {
+        return new external_single_structure([
+            'result' => new external_value(PARAM_BOOL, 'The processing result'),
+            'warnings' => new external_warnings()
+        ]);
+    }
+}
index 3869f3d..d654ab3 100644 (file)
@@ -26,9 +26,14 @@ namespace core_contentbank\privacy;
 
 use core_privacy\local\metadata\collection;
 use core_privacy\local\request\approved_contextlist;
-use core_privacy\local\request\approved_userlist;
 use core_privacy\local\request\contextlist;
+use core_privacy\local\request\transform;
+use core_privacy\local\request\writer;
 use core_privacy\local\request\userlist;
+use core_privacy\local\request\approved_userlist;
+use context_system;
+use context_coursecat;
+use context_course;
 
 /**
  * Privacy provider implementation for core_contentbank.
@@ -42,88 +47,236 @@ class provider implements
     \core_privacy\local\request\plugin\provider {
 
     /**
-     * Returns metadata.
-     * TODO: MDL-67798.
+     * Returns meta data about this system.
      *
      * @param collection $collection The initialised collection to add items to.
      * @return collection A listing of user data stored through this system.
      */
-    public static function get_metadata(collection $collection) : collection {
-        // We are not implementing a proper privacy provider for now.
-        // A right privacy provider will be implemented in MDL-67798.
-
+    public static function get_metadata(collection $collection): collection {
         $collection->add_database_table('contentbank_content', [
+            'name' => 'privacy:metadata:content:name',
+            'contenttype' => 'privacy:metadata:content:contenttype',
             'usercreated' => 'privacy:metadata:content:usercreated',
             'usermodified' => 'privacy:metadata:content:usermodified',
-        ], 'privacy:metadata:userid');
+            'timecreated' => 'privacy:metadata:content:timecreated',
+            'timemodified' => 'privacy:metadata:content:timemodified',
+        ], 'privacy:metadata:contentbankcontent');
 
         return $collection;
     }
 
     /**
-     * TODO: MDL-67798.
+     * Get the list of contexts that contain user information for the specified user.
      *
-     * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
+     * @param   int $userid The user to search.
+     * @return  contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
      */
-    public static function get_users_in_context(userlist $userlist) {
-        // We are not implementing a proper privacy provider for now.
-        // A right privacy provider will be implemented in MDL-67798.
-    }
+    public static function get_contexts_for_userid(int $userid): contextlist {
+        $sql = "SELECT DISTINCT ctx.id
+                  FROM {context} ctx
+                  JOIN {contentbank_content} cb
+                       ON cb.contextid = ctx.id
+                 WHERE cb.usercreated = :userid
+                       AND (ctx.contextlevel = :contextlevel1
+                           OR ctx.contextlevel = :contextlevel2
+                           OR ctx.contextlevel = :contextlevel3)";
 
-    /**
-     * TODO: MDL-67798.
-     *
-     * @param   approved_userlist       $userlist The approved context and user information to delete information for.
-     */
-    public static function delete_data_for_users(approved_userlist $userlist) {
-        // We are not implementing a proper privacy provider for now.
-        // A right privacy provider will be implemented in MDL-67798.
+        $params = [
+            'userid'        => $userid,
+            'contextlevel1' => CONTEXT_SYSTEM,
+            'contextlevel2' => CONTEXT_COURSECAT,
+            'contextlevel3' => CONTEXT_COURSE,
+        ];
+
+        $contextlist = new contextlist();
+        $contextlist->add_from_sql($sql, $params);
+
+        return $contextlist;
     }
 
     /**
-     * TODO: MDL-67798.
-     * Get the list of contexts that contain user information for the specified user.
+     * Get the list of users within a specific context.
      *
-     * @param   int         $userid     The user to search.
-     * @return  contextlist   $contextlist  The contextlist containing the list of contexts used in this plugin.
+     * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
      */
-    public static function get_contexts_for_userid(int $userid) : contextlist {
-        // We are not implementing a proper privacy provider for now.
-        // A right privacy provider will be implemented in MDL-67798.
+    public static function get_users_in_context(userlist $userlist) {
+        $context = $userlist->get_context();
+
+        $allowedcontextlevels = [
+            CONTEXT_SYSTEM,
+            CONTEXT_COURSECAT,
+            CONTEXT_COURSE,
+        ];
+
+        if (!in_array($context->contextlevel, $allowedcontextlevels)) {
+            return;
+        }
+
+        $sql = "SELECT cb.usercreated as userid
+                  FROM {contentbank_content} cb
+                 WHERE cb.contextid = :contextid";
 
-        return (new contextlist());
+        $params = [
+            'contextid' => $context->id
+        ];
+
+        $userlist->add_from_sql('userid', $sql, $params);
     }
 
     /**
-     * TODO: MDL-67798.
      * Export all user data for the specified user, in the specified contexts.
      *
-     * @param   approved_contextlist    $contextlist    The approved contexts to export information for.
+     * @param approved_contextlist $contextlist The approved contexts to export information for.
      */
     public static function export_user_data(approved_contextlist $contextlist) {
-        // We are not implementing a proper privacy provider for now.
-        // A right privacy provider will be implemented in MDL-67798.
+        global $DB;
+
+        // Remove contexts different from SYSTEM, COURSECAT or COURSE.
+        $contextids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
+            if ($context->contextlevel == CONTEXT_SYSTEM || $context->contextlevel == CONTEXT_COURSECAT
+                || $context->contextlevel == CONTEXT_COURSE) {
+                $carry[] = $context->id;
+            }
+            return $carry;
+        }, []);
+
+        if (empty($contextids)) {
+            return;
+        }
+
+        $userid = $contextlist->get_user()->id;
+
+        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
+        // Retrieve the contentbank_content records created for the user.
+        $sql = "SELECT cb.id,
+                       cb.name,
+                       cb.contenttype,
+                       cb.usercreated,
+                       cb.usermodified,
+                       cb.timecreated,
+                       cb.timemodified,
+                       cb.contextid
+                  FROM {contentbank_content} cb
+                 WHERE cb.usercreated = :userid
+                       AND cb.contextid {$contextsql}
+                 ORDER BY cb.contextid";
+
+        $params = ['userid' => $userid] + $contextparams;
+
+        $contents = $DB->get_recordset_sql($sql, $params);
+        $data = [];
+        $lastcontextid = null;
+        $subcontext = [
+            get_string('name', 'core_contentbank'),
+        ];
+        foreach ($contents as $content) {
+            // The core_contentbank data export is organised in:
+            // {Sytem|Course Category|Course Context Level}/Content/data.json.
+            if ($lastcontextid && $lastcontextid != $content->contextid) {
+                $context = \context::instance_by_id($lastcontextid);
+                writer::with_context($context)->export_data($subcontext, (object)$data);
+                $data = [];
+            }
+            $data[] = (object) [
+                'name' => $content->name,
+                'contenttype' => $content->contenttype,
+                'usercreated' => transform::user($content->usercreated),
+                'usermodified' => transform::user($content->usermodified),
+                'timecreated' => transform::datetime($content->timecreated),
+                'timemodified' => transform::datetime($content->timemodified)
+            ];
+            $lastcontextid = $content->contextid;
+
+            // The core_contentbank files export is organised in:
+            // {Sytem|Course Category|Course Context Level}/Content/_files/public/_itemid/filename.
+            $context = \context::instance_by_id($lastcontextid);
+            writer::with_context($context)->export_area_files($subcontext, 'contentbank', 'public', $content->id);
+        }
+        if (!empty($data)) {
+            $context = \context::instance_by_id($lastcontextid);
+            writer::with_context($context)->export_data($subcontext, (object)$data);
+        }
+        $contents->close();
     }
 
     /**
-     * TODO: MDL-67798.
      * Delete all data for all users in the specified context.
      *
-     * @param   context                 $context   The specific context to delete data for.
+     * @param   context $context The specific context to delete data for.
      */
     public static function delete_data_for_all_users_in_context(\context $context) {
-        // We are not implementing a proper privacy provider for now.
-        // A right privacy provider will be implemented in MDL-67798.
+        global $DB;
+
+        if (!$context instanceof context_system && !$context instanceof context_coursecat
+                && !$context instanceof context_course) {
+            return;
+        }
+
+        static::delete_data($context, []);
+    }
+
+    /**
+     * Delete multiple users within a single context.
+     *
+     * @param approved_userlist $userlist The approved context and user information to delete information for.
+     */
+    public static function delete_data_for_users(approved_userlist $userlist) {
+        $context = $userlist->get_context();
+
+        if (!$context instanceof context_system && !$context instanceof context_coursecat
+                && !$context instanceof context_course) {
+            return;
+        }
+
+        static::delete_data($context, $userlist->get_userids());
     }
 
     /**
-     * TODO: MDL-67798.
      * Delete all user data for the specified user, in the specified contexts.
      *
-     * @param   approved_contextlist    $contextlist    The approved contexts and user information to delete information for.
+     * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
      */
     public static function delete_data_for_user(approved_contextlist $contextlist) {
-        // We are not implementing a proper privacy provider for now.
-        // A right privacy provider will be implemented in MDL-67798.
+        if (empty($contextlist->count())) {
+            return;
+        }
+
+        $userid = $contextlist->get_user()->id;
+        foreach ($contextlist->get_contexts() as $context) {
+            if (!$context instanceof context_system && !$context instanceof context_coursecat
+            && !$context instanceof context_course) {
+                continue;
+            }
+            static::delete_data($context, [$userid]);
+        }
+    }
+
+    /**
+     * Delete data related to a context and users (if defined).
+     *
+     * @param context $context A context.
+     * @param array $userids The user IDs.
+     */
+    protected static function delete_data(\context $context, array $userids) {
+        global $DB;
+
+        $params = ['contextid' => $context->id];
+        $select = 'contextid = :contextid';
+
+        // Delete the Content Bank files.
+        if (!empty($userids)) {
+            list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
+            $params += $inparams;
+            $select .= ' AND usercreated '.$insql;
+        }
+        $fs = get_file_storage();
+        $contents = $DB->get_records_select('contentbank_content',
+            $select, $params);
+        foreach ($contents as $content) {
+            $fs->delete_area_files($content->contextid, 'contentbank', 'public', $content->id);
+        }
+
+        // Delete all the contents.
+        $DB->delete_records_select('contentbank_content', $select, $params);
     }
 }
index 1457476..4670423 100644 (file)
@@ -35,4 +35,5 @@ use html_writer;
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class content extends \core_contentbank\content {
+
 }
index 6c100cb..f28bee8 100644 (file)
@@ -36,6 +36,21 @@ use html_writer;
  */
 class contenttype extends \core_contentbank\contenttype {
 
+    /**
+     * Delete this content from the content_bank and remove all the H5P related information.
+     *
+     * @param  content $content The content to delete.
+     * @return boolean true if the content has been deleted; false otherwise.
+     */
+    public function delete_content(\core_contentbank\content $content): bool {
+        // Delete the H5P content.
+        $factory = new \core_h5p\factory();
+        \core_h5p\api::delete_content_from_pluginfile_url($content->get_file_url(), $factory);
+
+        // Delete the content from the content_bank.
+        return parent::delete_content($content);
+    }
+
     /**
      * Returns the HTML content to add to view.php visualizer.
      *
index 10ea721..179e80f 100644 (file)
@@ -25,5 +25,5 @@
 $string['pluginname'] = 'H5P';
 $string['pluginname_help'] = 'Content bank to upload and share H5P content';
 $string['privacy:metadata'] = 'The H5P content bank plugin does not store any personal data.';
-$string['h5p:access'] = 'Access to H5P content in the content bank';
-$string['h5p:upload'] = 'Upload new H5P content';
+$string['h5p:access'] = 'Access H5P content in the content bank';
+$string['h5p:upload'] = 'Upload new H5P content';
index 8d03118..61f28ea 100644 (file)
@@ -48,7 +48,7 @@ Feature: H5P file upload to content bank for admins
     And I click on "Save changes" "button"
     And I wait until the page is ready
     And I should see "filltheblanks.h5p"
-    And I navigate to "Plugins > Content bank > Manage content bank content types" in site administration
+    And I navigate to "Plugins > Content bank > Manage content types" in site administration
     And I click on "Disable" "icon" in the "H5P" "table_row"
     And I wait until the page is ready
     When I click on "Content bank" "link"
index a2daa82..3b9e3e3 100644 (file)
  */
 class contenttype_h5p_contenttype_plugin_testcase extends advanced_testcase {
 
+    /**
+     * Test the behaviour of delete_content().
+     */
+    public function test_delete_content() {
+        global $CFG, $USER, $DB;
+
+        $this->resetAfterTest();
+        $systemcontext = context_system::instance();
+
+        // Create users.
+        $roleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
+        $manager = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->role_assign($roleid, $manager->id);
+        $this->setUser($manager);
+
+        // Add an H5P file to the content bank.
+        $filepath = $CFG->dirroot . '/h5p/tests/fixtures/filltheblanks.h5p';
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        $contents = $generator->generate_contentbank_data('contenttype_h5p', 2, $USER->id, $systemcontext, true, $filepath);
+        $content1 = array_shift($contents);
+        $content2 = array_shift($contents);
+
+        // Load this H5P file though the player to create the H5P DB entries.
+        $h5pplayer = new \core_h5p\player($content1->get_file_url(), new \stdClass(), true);
+        $h5pplayer->add_assets_to_page();
+        $h5pplayer->output();
+        $h5pplayer = new \core_h5p\player($content2->get_file_url(), new \stdClass(), true);
+        $h5pplayer->add_assets_to_page();
+        $h5pplayer->output();
+
+        // Check the H5P content has been created.
+        $this->assertEquals(2, $DB->count_records('h5p'));
+        $this->assertEquals(2, $DB->count_records('contentbank_content'));
+
+        // Check the H5P content is removed after calling this method.
+        $contenttype = new \contenttype_h5p\contenttype($systemcontext);
+        $contenttype->delete_content($content1);
+        $this->assertEquals(1, $DB->count_records('h5p'));
+        $this->assertEquals(1, $DB->count_records('contentbank_content'));
+    }
+
     /**
      * Tests can_upload behavior.
      *
index 01d1db6..bbf9786 100644 (file)
@@ -27,10 +27,14 @@ require('../config.php');
 require_login();
 
 $contextid    = optional_param('contextid', \context_system::instance()->id, PARAM_INT);
+$search = optional_param('search', '', PARAM_CLEAN);
 $context = context::instance_by_id($contextid, MUST_EXIST);
 
 require_capability('moodle/contentbank:access', $context);
 
+$statusmsg = optional_param('statusmsg', '', PARAM_RAW);
+$errormsg = optional_param('errormsg', '', PARAM_RAW);
+
 $title = get_string('contentbank');
 \core_contentbank\helper::get_page_ready($context, $title);
 if ($PAGE->course) {
@@ -43,27 +47,13 @@ $PAGE->set_heading($title);
 $PAGE->set_pagetype('contenbank');
 
 // Get all contents managed by active plugins to render.
-$foldercontents = array();
-$contents = $DB->get_records('contentbank_content', ['contextid' => $contextid]);
-foreach ($contents as $content) {
-    $plugin = core_plugin_manager::instance()->get_plugin_info($content->contenttype);
-    if (!$plugin || !$plugin->is_enabled()) {
-        continue;
-    }
-    $contentclass = "\\$content->contenttype\\content";
-    if (class_exists($contentclass)) {
-        $contentmanager = new $contentclass($content);
-        if ($contentmanager->can_view()) {
-            $foldercontents[] = $contentmanager;
-        }
-    }
-}
+$cb = new \core_contentbank\contentbank();
+$foldercontents = $cb->search_contents($search, $contextid);
 
 // Get the toolbar ready.
 $toolbar = array ();
 if (has_capability('moodle/contentbank:upload', $context)) {
     // Don' show upload button if there's no plugin to support any file extension.
-    $cb = new \core_contentbank\contentbank();
     $accepted = $cb->get_supported_extensions_as_string($context);
     if (!empty($accepted)) {
         $importurl = new moodle_url('/contentbank/upload.php', ['contextid' => $contextid]);
@@ -74,6 +64,14 @@ if (has_capability('moodle/contentbank:upload', $context)) {
 echo $OUTPUT->header();
 echo $OUTPUT->box_start('generalbox');
 
+// If needed, display notifications.
+if ($errormsg !== '') {
+    echo $OUTPUT->notification($errormsg);
+} else if ($statusmsg !== '') {
+    echo $OUTPUT->notification($statusmsg, 'notifysuccess');
+}
+
+// Render the contentbank contents.
 $folder = new \core_contentbank\output\bankcontent($foldercontents, $toolbar, $context);
 echo $OUTPUT->render($folder);
 
index 5b1a1dd..2212b81 100644 (file)
 
 }}
 {{>core_contentbank/toolbar}}
-<div class="content-bank-container card">
+<div class="content-bank-container pb-3 border">
     <div class="content-bank">
-        <div class="cb-navbar">
+        <div class="cb-navbar bg-light p-2 border-bottom">
             {{#pix}} i/folder {{/pix}}
         </div>
-        <div class="cb-content-wrapper">
+        <div class="cb-content-wrapper d-flex flex-wrap p-2">
         {{#contents}}
-            <div class="cb-content">
-                <div class="cb-iconview">
-                    <div class="cb-file text-center position-relative">
-                        {{#link}}<a href="{{{ link }}}">{{/link}}
-                            <div style="position:relative;">
-                                <div class="cb-thumbnail text-center d-block" style="width: 110px; height: 110px;">
-                                    {{{ icon }}}
-                                </div>
-                            </div>
-                            <div class="cb-contentname-field position-absolute overflow-visible">
-                                <div class="cb-contentname text-truncate" style="width: 112px;">{{{ name }}}</div>
-                            </div>
-                        {{#link}}</a>{{/link}}
+            <div class="cb-file position-relative mb-2">
+                <div class="p-2">
+                    <div class="cb-thumbnail mb-1 text-center">
+                        {{{ icon }}}
                     </div>
+
+                    {{#link}}
+                        <a href="{{{ link }}}" class="stretched-link" title="{{{name}}}">
+                    {{/link}}
+                            <span class="cb-name word-break-all clamp-2 text-center" >
+                                {{{ name }}}
+                            </span>
+                    {{#link}}
+                        </a>
+                    {{/link}}
                 </div>
             </div>
         {{/contents}}
index 7a041a6..9ab8024 100644 (file)
@@ -37,7 +37,7 @@
         <div class="cb-toolbar float-sm-right">
         {{#tools}}
             {{#link}}<a href="{{{ link }}}" title="{{{ name }}}">{{/link}}
-                <div class="cb-tool btn btn-secondary btn-sm">
+                <div class="cb-tool icon-no-margin btn btn-secondary btn-lg">
                     {{#pix}} {{{ icon }}} {{/pix}}
                 </div>
             {{#link}}</a>{{/link}}
diff --git a/contentbank/tests/behat/delete_content.feature b/contentbank/tests/behat/delete_content.feature
new file mode 100644 (file)
index 0000000..b49a79c
--- /dev/null
@@ -0,0 +1,75 @@
+@core @core_contentbank @contentbank_h5p @_file_upload @javascript
+Feature: Delete H5P file from the content bank
+  In order remove H5P content from the content bank
+  As an admin
+  I need to be able to delete any H5P content from the content bank
+
+  Background:
+    Given I log in as "admin"
+    And I follow "Manage private files..."
+    And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
+    And I click on "Save changes" "button"
+    And I am on site homepage
+    And I turn editing mode on
+    And I add the "Navigation" block if not present
+    And I configure the "Navigation" block
+    And I set the following fields to these values:
+      | Page contexts | Display throughout the entire site |
+    And I press "Save changes"
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link" in the "Navigation" "block"
+    And I click on "Upload" "link"
+    And I click on "Choose a file..." "button"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "filltheblanks.h5p" "link"
+    And I click on "Select this file" "button"
+    And I click on "Save changes" "button"
+
+  Scenario: Admins can delete content from the content bank
+    Given I should see "filltheblanks.h5p"
+    And I follow "filltheblanks.h5p"
+    When I open the action menu in "region-main-settings-menu" "region"
+    Then I should see "Delete"
+    And I choose "Delete" in the open action menu
+    And I should see "Are you sure you want to delete the content 'filltheblanks.h5p'"
+    And I click on "Cancel" "button" in the "Delete content" "dialogue"
+    And I should see "filltheblanks.h5p"
+    And I open the action menu in "region-main-settings-menu" "region"
+    And I choose "Delete" in the open action menu
+    And I click on "Delete" "button" in the "Delete content" "dialogue"
+    And I wait until the page is ready
+    And I should see "The content has been deleted."
+    And I should not see "filltheblanks.h5p"
+
+  Scenario: Users without the required capability can only delete their own content
+    Given the following "permission overrides" exist:
+      | capability                            | permission | role    | contextlevel | reference |
+      | moodle/contentbank:deleteanycontent   | Prohibit   | manager | System       |           |
+    And the following "users" exist:
+      | username    | firstname | lastname | email              |
+      | manager     | Max       | Manager  | man@example.com    |
+    And the following "role assigns" exist:
+      | user        | role      | contextlevel  | reference     |
+      | manager     | manager       | System    |               |
+    And I log out
+    And I log in as "manager"
+    And I follow "Manage private files..."
+    And I upload "h5p/tests/fixtures/find-the-words.h5p" file to "Files" filemanager
+    And I click on "Save changes" "button"
+    When I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link" in the "Navigation" "block"
+    And I should see "filltheblanks.h5p"
+    And I follow "filltheblanks.h5p"
+    Then ".header-actions-container" "css_element" should not exist
+    And I click on "Content bank" "link"
+    And I click on "Upload" "link"
+    And I click on "Choose a file..." "button"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "find-the-words.h5p" "link"
+    And I click on "Select this file" "button"
+    And I click on "Save changes" "button"
+    And I should see "filltheblanks.h5p"
+    And I should see "find-the-words.h5p"
+    And I follow "find-the-words.h5p"
+    And I open the action menu in "region-main-settings-menu" "region"
+    And I should see "Delete"
index 199b73a..824f7a5 100644 (file)
@@ -175,4 +175,143 @@ class core_contentbank_testcase extends advanced_testcase {
         $supporter = $cb->get_extension_supporter($extension, $systemcontext);
         $this->assertEquals($expected, $supporter);
     }
+
+    /**
+     * Test the behaviour of search_contents().
+     *
+     * @dataProvider search_contents_provider
+     * @param  string $search String to search.
+     * @param  int $contextid Contextid to search.
+     * @param  int $expectedresult Expected result.
+     * @param  array $contexts List of contexts where to create content.
+     */
+    public function test_search_contents(?string $search, int $contextid, int $expectedresult, array $contexts = []): void {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        // Create users.
+        $managerroleid = $DB->get_field('role', 'id', ['shortname' => 'manager']);
+        $manager = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->role_assign($managerroleid, $manager->id);
+
+        // Add some content to the content bank.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        foreach ($contexts as $context) {
+            $records = $generator->generate_contentbank_data('contenttype_h5p', 3,
+                $manager->id, $context, false);
+        }
+
+        // Search for some content.
+        $cb = new \core_contentbank\contentbank();
+        $contents = $cb->search_contents($search, $contextid);
+
+        $this->assertCount($expectedresult, $contents);
+        if (!empty($contents) && !empty($search)) {
+            foreach ($contents as $content) {
+                $this->assertContains($search, $content->get_name());
+            }
+        }
+    }
+
+    /**
+     * Data provider for test_search_contents().
+     *
+     * @return array
+     */
+    public function search_contents_provider(): array {
+        // Create a category and a course.
+        $systemcontext = \context_system::instance();
+        $coursecat = $this->getDataGenerator()->create_category();
+        $course = $this->getDataGenerator()->create_course();
+        $coursecatcontext = \context_coursecat::instance($coursecat->id);
+        $coursecontext = \context_course::instance($course->id);
+
+        return [
+            'Search all content in all contexts' => [
+                null,
+                0,
+                9,
+                [$systemcontext, $coursecatcontext, $coursecontext]
+            ],
+            'Search in all contexts for existing string in all contents' => [
+                'content',
+                0,
+                9,
+                [$systemcontext, $coursecatcontext, $coursecontext]
+            ],
+            'Search in all contexts for unexisting string in all contents' => [
+                'chocolate',
+                0,
+                0,
+                [$systemcontext, $coursecatcontext, $coursecontext]
+            ],
+            'Search in all contexts for existing string in some contents' => [
+                '1',
+                0,
+                3,
+                [$systemcontext, $coursecatcontext, $coursecontext]
+            ],
+            'Search in all contexts for existing string in some contents (create only 1 context)' => [
+                '1',
+                0,
+                1,
+                [$systemcontext]
+            ],
+            'Search in system context for existing string in all contents' => [
+                'content',
+                $systemcontext->id,
+                3,
+                [$systemcontext, $coursecatcontext, $coursecontext]
+            ],
+            'Search in category context for unexisting string in all contents' => [
+                'chocolate',
+                $coursecatcontext->id,
+                0,
+                [$systemcontext, $coursecatcontext, $coursecontext]
+            ],
+            'Search in course context for existing string in some contents' => [
+                '1',
+                $coursecontext->id,
+                1,
+                [$systemcontext, $coursecatcontext, $coursecontext]
+            ],
+            'Search in system context' => [
+                null,
+                $systemcontext->id,
+                3,
+                [$systemcontext, $coursecatcontext, $coursecontext]
+            ],
+            'Search in course context with existing content' => [
+                null,
+                $coursecontext->id,
+                3,
+                [$systemcontext, $coursecatcontext, $coursecontext]
+            ],
+            'Search in course context without existing content' => [
+                null,
+                $coursecontext->id,
+                0,
+                [$systemcontext, $coursecatcontext]
+            ],
+            'Search in an empty contentbank' => [
+                null,
+                0,
+                0,
+                []
+            ],
+            'Search in a context in an empty contentbank' => [
+                null,
+                $systemcontext->id,
+                0,
+                []
+            ],
+            'Search for a string in an empty contentbank' => [
+                'content',
+                0,
+                0,
+                []
+            ],
+        ];
+    }
 }
index ec231a0..7175b63 100644 (file)
@@ -46,6 +46,24 @@ use contenttype_testable\contenttype as contenttype;
  */
 class core_contenttype_contenttype_testcase extends \advanced_testcase {
 
+    /** @var int Identifier for the manager role. */
+    protected $managerroleid;
+
+    /** @var stdClass Manager user. */
+    protected $manager1;
+
+    /** @var stdClass Manager user. */
+    protected $manager2;
+
+    /** @var stdClass User. */
+    protected $user;
+
+    /** @var array List of contents created (every user has a key with contents created by her). */
+    protected $contents = [];
+
+    /** @var contenttype The contenttype instance. */
+    protected $contenttype;
+
     /**
      * Tests get_contenttype_name result.
      *
@@ -157,4 +175,83 @@ class core_contenttype_contenttype_testcase extends \advanced_testcase {
         $this->assertEquals('contenttype_testable', $content->get_content_type());
         $this->assertInstanceOf('\\contenttype_testable\\content', $content);
     }
+
+
+    /**
+     * Test the behaviour of can_delete().
+     */
+    public function test_can_delete() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->contenttype_setup_scenario_data();
+
+        $managercontent = array_shift($this->contents[$this->manager1->id]);
+        $usercontent = array_shift($this->contents[$this->user->id]);
+
+        // Check the content has been created as expected.
+        $records = $DB->count_records('contentbank_content');
+        $this->assertEquals(4, $records);
+
+        // Check user can only delete records created by her.
+        $this->setUser($this->user);
+        $this->assertFalse($this->contenttype->can_delete($managercontent));
+        $this->assertTrue($this->contenttype->can_delete($usercontent));
+
+        // Check manager can delete records all the records created.
+        $this->setUser($this->manager1);
+        $this->assertTrue($this->contenttype->can_delete($managercontent));
+        $this->assertTrue($this->contenttype->can_delete($usercontent));
+
+        // Unassign capability to manager role and check not can only delete their own records.
+        unassign_capability('moodle/contentbank:deleteanycontent', $this->managerroleid);
+        $this->assertTrue($this->contenttype->can_delete($managercontent));
+        $this->assertFalse($this->contenttype->can_delete($usercontent));
+        $this->setUser($this->manager2);
+        $this->assertFalse($this->contenttype->can_delete($managercontent));
+        $this->assertFalse($this->contenttype->can_delete($usercontent));
+    }
+
+    /**
+     * Test the behaviour of delete_content().
+     */
+    public function test_delete_content() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->contenttype_setup_scenario_data();
+
+        // Check the content has been created as expected.
+        $this->assertEquals(4, $DB->count_records('contentbank_content'));
+
+        // Check the content is deleted as expected.
+        $this->setUser($this->manager1);
+        $content = array_shift($this->contents[$this->manager1->id]);
+        $deleted = $this->contenttype->delete_content($content);
+        $this->assertTrue($deleted);
+        $this->assertEquals(3, $DB->count_records('contentbank_content'));
+    }
+
+    /**
+     * Helper function to setup 3 users (manager1, manager2 and user) and 4 contents (3 created by manager1 and 1 by user).
+     */
+    protected function contenttype_setup_scenario_data(): void {
+        global $DB;
+        $systemcontext = context_system::instance();
+
+        // Create users.
+        $this->manager1 = $this->getDataGenerator()->create_user();
+        $this->manager2 = $this->getDataGenerator()->create_user();
+        $this->managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
+        $this->getDataGenerator()->role_assign($this->managerroleid, $this->manager1->id);
+        $this->getDataGenerator()->role_assign($this->managerroleid, $this->manager2->id);
+        $this->user = $this->getDataGenerator()->create_user();
+
+        // Add some content to the content bank.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        $this->contents[$this->manager1->id] = $generator->generate_contentbank_data(null, 3, $this->manager1->id);
+        $this->contents[$this->user->id] = $generator->generate_contentbank_data(null, 1, $this->user->id);
+
+        $this->contenttype = new \contenttype_testable\contenttype($systemcontext);
+    }
 }
diff --git a/contentbank/tests/external/delete_content_test.php b/contentbank/tests/external/delete_content_test.php
new file mode 100644 (file)
index 0000000..fd35f45
--- /dev/null
@@ -0,0 +1,112 @@
+<?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/>.
+
+/**
+ * External function test for delete_content.
+ *
+ * @package    core_contentbank
+ * @category   external
+ * @since      Moodle 3.9
+ * @copyright  2020 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_contentbank;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+use core_contentbank\external\delete_content;
+use dml_missing_record_exception;
+use external_api;
+use externallib_advanced_testcase;
+
+/**
+ * External function test for delete_content.
+ *
+ * @package    core_contentbank
+ * @copyright  2020 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class delete_content_testcase extends externallib_advanced_testcase {
+
+    /**
+     * Test the behaviour of delete_content().
+     */
+    public function test_delete_content() {
+        global $DB;
+        $this->resetAfterTest();
+        $records = [];
+
+        // Create users.
+        $user = $this->getDataGenerator()->create_user();
+        $roleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
+        $manager = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->role_assign($roleid, $manager->id);
+
+        // Add some content to the content bank.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        $records[$manager->id] = $generator->generate_contentbank_data('contenttype_testable', 4, $manager->id, null, false);
+        $records[$user->id] = $generator->generate_contentbank_data('contenttype_testable', 2, $user->id, null, false);
+
+        // Check the content has been created as expected.
+        $this->assertEquals(6, $DB->count_records('contentbank_content'));
+
+        // Check the content is deleted as expected by the user when the content has been created by herself.
+        $this->setUser($user);
+        $userrecord = array_shift($records[$user->id]);
+        $result = delete_content::execute([$userrecord->id]);
+        $result = external_api::clean_returnvalue(delete_content::execute_returns(), $result);
+        $this->assertTrue($result['result']);
+        $this->assertCount(0, $result['warnings']);
+        $this->assertEquals(5, $DB->count_records('contentbank_content'));
+
+        // Check the content is not deleted if the user hasn't created it and has only permission to delete her own content.
+        $userrecord = array_shift($records[$user->id]);
+        $managerrecord1 = array_shift($records[$manager->id]);
+        $result = delete_content::execute([$managerrecord1->id, $userrecord->id]);
+        $result = external_api::clean_returnvalue(delete_content::execute_returns(), $result);
+        $this->assertFalse($result['result']);
+        $this->assertCount(1, $result['warnings']);
+        $warning = array_shift($result['warnings']);
+        $this->assertEquals('nopermissiontodelete', $warning['warningcode']);
+        $this->assertEquals($managerrecord1->id, $warning['item']);
+        $this->assertEquals(4, $DB->count_records('contentbank_content'));
+
+        // Check the content is deleted as expected by the manager.
+        $this->setUser($manager);
+        $managerrecord2 = array_shift($records[$manager->id]);
+        $result = delete_content::execute([$managerrecord1->id, $managerrecord2->id]);
+        $result = external_api::clean_returnvalue(delete_content::execute_returns(), $result);
+        $this->assertTrue($result['result']);
+        $this->assertCount(0, $result['warnings']);
+        $this->assertEquals(2, $DB->count_records('contentbank_content'));
+
+        // Check an exception warning is returned if an unexisting contentid is deleted.
+        // Check also the other content is deleted (so the process continues after the exception is thrown).
+        $managerrecord3 = array_shift($records[$manager->id]);
+        $result = delete_content::execute([$managerrecord1->id, $managerrecord3->id]);
+        $result = external_api::clean_returnvalue(delete_content::execute_returns(), $result);
+        $this->assertFalse($result['result']);
+        $this->assertCount(1, $result['warnings']);
+        $warning = array_shift($result['warnings']);
+        $this->assertEquals('exception', $warning['warningcode']);
+        $this->assertEquals($managerrecord1->id, $warning['item']);
+        $this->assertEquals(1, $DB->count_records('contentbank_content'));
+    }
+}
diff --git a/contentbank/tests/generator/lib.php b/contentbank/tests/generator/lib.php
new file mode 100644 (file)
index 0000000..8bb9ae0
--- /dev/null
@@ -0,0 +1,102 @@
+<?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/>.
+
+/**
+ * Generator for the core_contentbank subsystem.
+ *
+ * @package    core_contentbank
+ * @category   test
+ * @copyright  2020 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
+
+/**
+ * Generator for the core_contentbank subsystem.
+ *
+ * @package    core_contentbank
+ * @copyright  2020 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_contentbank_generator extends \component_generator_base {
+
+    /**
+     * Populate contentbank database tables with relevant data to simulate the process of adding items to the content bank.
+     *
+     * @param string $contenttype Content bank plugin type to add. If none is defined, contenttype_testable is used.
+     * @param int $itemstocreate Number of items to add to the content bank.
+     * @param int $userid The user identifier creating the content.
+     * @param context $context The context where the content will be created.
+     * @param bool $convert2class Whether the class should return stdClass or plugin instance.
+     * @param string $filepath The filepath of the file associated to the content to create.
+     * @return array An array with all the records added to the content bank.
+     */
+    public function generate_contentbank_data(?string $contenttype, int $itemstocreate = 1, int $userid = 0,
+            ?\context $context = null, bool $convert2class = true, string $filepath = 'contentfile.h5p'): array {
+        global $DB, $USER;
+
+        $records = [];
+
+        $contenttype = $contenttype ?? 'contenttype_testable';
+        $contenttypeclass = "\\$contenttype\\contenttype";
+        if (!class_exists($contenttypeclass)) {
+            // Early return with empty array because the contenttype doesn't exist.
+            return $records;
+        }
+        if (empty($context)) {
+            $context = \context_system::instance();
+        }
+        $type = new $contenttypeclass($context);
+        $fs = get_file_storage();
+        for ($i = 0; $i < $itemstocreate; $i++) {
+            // Create content.
+            $record = new stdClass();
+            $record->name = 'Test content ' . $i;
+            $record->configdata = '';
+            $record->usercreated = $userid ?? $USER->id;
+
+            $content = $type->create_content($record);
+            $record = $content->get_content();
+
+            // Create a dummy file.
+            $filerecord = array(
+                'contextid' => $context->id,
+                'component' => 'contentbank',
+                'filearea' => 'public',
+                'itemid' => $record->id,
+                'filepath' => '/',
+                'filename' => basename($filepath)
+            );
+            if (file_exists($filepath)) {
+                $fs->create_file_from_pathname($filerecord, $filepath);
+            } else {
+                $fs->create_file_from_string($filerecord, 'Dummy content ' . $i);
+            }
+
+            // Prepare the return value.
+            if ($convert2class) {
+                $records[$record->id] = $content;
+            } else {
+                $records[$record->id] = $record;
+            }
+        }
+
+        return $records;
+    }
+}
diff --git a/contentbank/tests/privacy_test.php b/contentbank/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..09c3f0f
--- /dev/null
@@ -0,0 +1,366 @@
+<?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/>.
+
+/**
+ * Base class for unit tests for core_contentbank.
+ *
+ * @package    core_contentbank
+ * @category   test
+ * @copyright  2020 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_contentbank;
+
+defined('MOODLE_INTERNAL') || die();
+
+use stdClass;
+use context_system;
+use context_coursecat;
+use context_course;
+use core_contentbank\privacy\provider;
+use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\writer;
+use core_privacy\tests\provider_testcase;
+use core_privacy\local\request\userlist;
+use core_privacy\local\request\approved_userlist;
+
+/**
+ * Unit tests for contentbank\classes\privacy\provider.php
+ *
+ * @copyright  2020 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_contentbank_privacy_testcase extends provider_testcase {
+
+    /**
+     * Setup to ensure that fixtures are loaded.
+     */
+    public static function setupBeforeClass(): void {
+        global $CFG;
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
+    }
+
+    /**
+     * Test for provider::get_contexts_for_userid().
+     */
+    public function test_get_contexts_for_userid() {
+
+        $this->resetAfterTest();
+        // Setup scenario.
+        $scenario = $this->setup_scenario();
+
+        // Testing againts Manager who has content in the three contexts.
+        $contextlist = provider::get_contexts_for_userid($scenario->manager->id);
+        // There are three contexts in the list.
+        $contextlistids = $contextlist->get_contextids();
+        $this->assertCount(3, $contextlistids);
+        // Check the list against the expected list of contexts.
+        $this->assertContains($scenario->systemcontext->id, $contextlistids);
+        $this->assertContains($scenario->coursecategorycontext->id,
+            $contextlistids);
+        $this->assertContains($scenario->coursecontext->id, $contextlistids);
+
+        // Testing againts Teacher who has content in the one context.
+        $contextlist = provider::get_contexts_for_userid($scenario->teacher->id);
+        // There are only one context in the list.
+        $contextlistids = $contextlist->get_contextids();
+        $this->assertCount(1, $contextlistids);
+        // Check the againts Course Context.
+        $this->assertContains($scenario->coursecontext->id, $contextlistids);
+        // And there is not a System and Course Category Context.
+        $this->assertNotContains($scenario->systemcontext->id, $contextlistids);
+        $this->assertNotContains($scenario->coursecategorycontext->id, $contextlistids);
+    }
+
+    /**
+     * Test for provider::get_users_in_context().
+     */
+    public function test_get_users_in_context() {
+
+        $this->resetAfterTest();
+        // Setup scenario.
+        $scenario = $this->setup_scenario();
+
+        // Get the userlist to Context System, only Manager will be there.
+        $userlist = new userlist($scenario->systemcontext, 'core_contentbank');
+        provider::get_users_in_context($userlist);
+        $this->assertEquals([$scenario->manager->id], $userlist->get_userids());
+        // Teacher will not be there.
+        $this->assertNotEquals([$scenario->teacher->id], $userlist->get_userids());
+
+        // Get the userlist to Context Course, Manager and Teacher will be there.
+        $userlist = new userlist($scenario->coursecontext, 'core_contentbank');
+        provider::get_users_in_context($userlist);
+
+        $expected = [$scenario->manager->id, $scenario->teacher->id];
+        sort($expected);
+        $actual = $userlist->get_userids();
+        sort($actual);
+        $this->assertEquals($expected, $actual);
+    }
+
+    /**
+     * Test for provider::test_export_user_data().
+     */
+    public function test_export_user_data() {
+
+        $this->resetAfterTest();
+        // Setup scenario.
+        $scenario = $this->setup_scenario();
+
+        $subcontexts = [
+            get_string('name', 'core_contentbank')
+        ];
+        // Get the data for the System Context.
+        $writer = writer::with_context($scenario->systemcontext);
+        $this->assertFalse($writer->has_any_data());
+        // Export data for Manager.
+        $this->export_context_data_for_user($scenario->manager->id,
+            $scenario->systemcontext, 'core_contentbank');
+        $data = $writer->get_data($subcontexts);
+        $this->assertCount(3, (array) $data);
+        $this->assertCount(3, $writer->get_files($subcontexts));
+
+        // Get the data for the Course Categoy Context.
+        $writer = writer::with_context($scenario->coursecategorycontext);
+        // Export data for Manager.
+        $this->export_context_data_for_user($scenario->manager->id,
+            $scenario->coursecategorycontext, 'core_contentbank');
+        $data = $writer->get_data($subcontexts);
+        $this->assertCount(2, (array) $data);
+        $this->assertCount(2, $writer->get_files($subcontexts));
+
+        // Get the data for the Course Context.
+        $writer = writer::with_context($scenario->coursecontext);
+        // Export data for Manager.
+        $this->export_context_data_for_user($scenario->manager->id,
+            $scenario->coursecontext, 'core_contentbank');
+        $data = $writer->get_data($subcontexts);
+        $this->assertCount(2, (array) $data);
+        $this->assertCount(2, $writer->get_files($subcontexts));
+
+        // Export data for Teacher.
+        $writer = writer::reset();
+        $writer = writer::with_context($scenario->coursecontext);
+        $this->export_context_data_for_user($scenario->teacher->id,
+            $scenario->coursecontext, 'core_contentbank');
+        $data = $writer->get_data($subcontexts);
+        $this->assertCount(3, (array) $data);
+        $this->assertCount(3, $writer->get_files($subcontexts));
+    }
+
+    /**
+     * Test for provider::delete_data_for_all_users_in_context().
+     */
+    public function test_delete_data_for_all_users_in_context() {
+        global $DB;
+
+        $this->resetAfterTest();
+        // Setup scenario.
+        $scenario = $this->setup_scenario();
+
+        // Before delete data, we have 4 contents.
+        // - 3 in a system context.
+        // - 2 in a course category context.
+        // - 5 in a course context (2 by manager and 3 by teacher).
+
+        // Delete data based on system context.
+        provider::delete_data_for_all_users_in_context($scenario->systemcontext);
+        $count = $DB->count_records('contentbank_content');
+        // 3 content should be deleted.
+        // 7 contents should be remain.
+        $this->assertEquals(7, $count);
+
+        // Delete data based on course category context.
+        provider::delete_data_for_all_users_in_context($scenario->coursecategorycontext);
+        $count = $DB->count_records('contentbank_content');
+        // 2 contents should be deleted.
+        // 5 content should be remain.
+        $this->assertEquals(5, $count);
+
+        // Delete data based on course context.
+        provider::delete_data_for_all_users_in_context($scenario->coursecontext);
+         $count = $DB->count_records('contentbank_content');
+        // 5 content should be deleted.
+        // 0 content should be remain.
+        $this->assertEquals(0, $count);
+    }
+
+    /**
+     * Test for provider::test_delete_data_for_users().
+     */