Merge branch 'MDL-68049' of https://github.com/spvickers/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 30 Mar 2020 11:26:21 +0000 (13:26 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 30 Mar 2020 11:26:21 +0000 (13:26 +0200)
285 files changed:
.eslintignore
.stylelintignore
admin/index.php
admin/settings/h5p.php
admin/settings/subsystems.php
admin/tool/behat/tests/behat/data_generators.feature
admin/tool/dataprivacy/tests/privacy_provider_test.php [new file with mode: 0644]
admin/tool/usertours/classes/manager.php
admin/tool/usertours/db/upgrade.php
admin/tool/usertours/tests/manager_test.php
admin/tool/usertours/version.php
backup/moodle2/tests/moodle2_test.php
backup/util/dbops/restore_controller_dbops.class.php
backup/util/helper/async_helper.class.php
blocks/tests/behat/manage_blocks.feature
calendar/amd/build/calendar_mini.min.js.map
calendar/amd/src/calendar_mini.js
calendar/classes/external/day_exporter.php
calendar/renderer.php
calendar/templates/minicalendar_day_link.mustache
calendar/templates/month_detailed.mustache
calendar/templates/month_mini.mustache
calendar/templates/threemonth_month.mustache
completion/tests/behat/behat_completion.php
course/amd/build/actions.min.js
course/amd/build/actions.min.js.map
course/amd/build/activitychooser.min.js
course/amd/build/activitychooser.min.js.map
course/amd/build/local/activitychooser/dialogue.min.js
course/amd/build/local/activitychooser/dialogue.min.js.map
course/amd/build/local/activitychooser/selectors.min.js
course/amd/build/local/activitychooser/selectors.min.js.map
course/amd/src/actions.js
course/amd/src/activitychooser.js
course/amd/src/local/activitychooser/dialogue.js
course/amd/src/local/activitychooser/selectors.js
course/classes/local/repository/content_item_readonly_repository.php
course/classes/local/service/content_item_service.php
course/format/renderer.php
course/lib.php
course/mod.php
course/modedit.php
course/moodleform_mod.php
course/renderer.php
course/templates/activitychooser.mustache [moved from course/templates/chooser.mustache with 90% similarity]
course/templates/local/activitychooser/favourites.mustache [moved from course/templates/chooser_favourites.mustache with 95% similarity]
course/templates/local/activitychooser/help.mustache [moved from course/templates/chooser_help.mustache with 92% similarity]
course/templates/local/activitychooser/item.mustache [moved from course/templates/chooser_item.mustache with 94% similarity]
course/templates/local/activitychooser/search.mustache [moved from course/templates/chooser_search.mustache with 95% similarity]
course/templates/local/activitychooser/search_results.mustache [moved from course/templates/chooser_search_results.mustache with 84% similarity]
course/tests/behat/add_activities.feature
course/tests/behat/behat_course.php
course/tests/courselib_test.php
course/upgrade.txt
enrol/lti/classes/task/sync_grades.php
enrol/manual/amd/build/quickenrolment.min.js
enrol/manual/amd/build/quickenrolment.min.js.map
enrol/manual/amd/src/quickenrolment.js
favourites/classes/local/repository/favourite_repository.php
favourites/classes/local/service/user_favourite_service.php
favourites/tests/repository_test.php
favourites/tests/user_favourite_service_test.php
files/renderer.php
filter/displayh5p/db/upgrade.php
filter/displayh5p/filter.php
filter/displayh5p/lang/en/filter_displayh5p.php
filter/displayh5p/settings.php
filter/displayh5p/tests/behat/h5p_filter.feature
filter/displayh5p/tests/filter_test.php
filter/displayh5p/version.php
grade/report/history/db/upgrade.php [new file with mode: 0644]
grade/report/history/settings.php
grade/report/history/version.php
h5p/classes/autoloader.php [deleted file]
h5p/classes/core.php
h5p/classes/factory.php
h5p/classes/helper.php
h5p/classes/local/library/autoloader.php [new file with mode: 0644]
h5p/classes/local/library/handler.php [new file with mode: 0644]
h5p/classes/player.php
h5p/h5plib/v124/classes/local/library/handler.php [new file with mode: 0644]
h5p/h5plib/v124/classes/privacy/provider.php [new file with mode: 0644]
h5p/h5plib/v124/joubel/core/LICENSE.txt [moved from lib/h5p/LICENSE.txt with 100% similarity]
h5p/h5plib/v124/joubel/core/README.txt [moved from lib/h5p/README.txt with 100% similarity]
h5p/h5plib/v124/joubel/core/doc/spec_en.html [moved from lib/h5p/doc/spec_en.html with 100% similarity]
h5p/h5plib/v124/joubel/core/embed.php [moved from lib/h5p/embed.php with 100% similarity]
h5p/h5plib/v124/joubel/core/fonts/h5p-core-23.eot [moved from lib/h5p/fonts/h5p-core-23.eot with 100% similarity]
h5p/h5plib/v124/joubel/core/fonts/h5p-core-23.svg [moved from lib/h5p/fonts/h5p-core-23.svg with 100% similarity]
h5p/h5plib/v124/joubel/core/fonts/h5p-core-23.ttf [moved from lib/h5p/fonts/h5p-core-23.ttf with 100% similarity]
h5p/h5plib/v124/joubel/core/fonts/h5p-core-23.woff [moved from lib/h5p/fonts/h5p-core-23.woff with 100% similarity]
h5p/h5plib/v124/joubel/core/h5p-default-storage.class.php [moved from lib/h5p/h5p-default-storage.class.php with 100% similarity]
h5p/h5plib/v124/joubel/core/h5p-development.class.php [moved from lib/h5p/h5p-development.class.php with 100% similarity]
h5p/h5plib/v124/joubel/core/h5p-event-base.class.php [moved from lib/h5p/h5p-event-base.class.php with 100% similarity]
h5p/h5plib/v124/joubel/core/h5p-file-storage.interface.php [moved from lib/h5p/h5p-file-storage.interface.php with 100% similarity]
h5p/h5plib/v124/joubel/core/h5p-metadata.class.php [moved from lib/h5p/h5p-metadata.class.php with 100% similarity]
h5p/h5plib/v124/joubel/core/h5p.classes.php [moved from lib/h5p/h5p.classes.php with 99% similarity]
h5p/h5plib/v124/joubel/core/images/h5p.svg [moved from lib/h5p/images/h5p.svg with 100% similarity]
h5p/h5plib/v124/joubel/core/images/throbber.gif [moved from lib/h5p/images/throbber.gif with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-action-bar.js [moved from lib/h5p/js/h5p-action-bar.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-confirmation-dialog.js [moved from lib/h5p/js/h5p-confirmation-dialog.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-content-type.js [moved from lib/h5p/js/h5p-content-type.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-content-upgrade-process.js [moved from lib/h5p/js/h5p-content-upgrade-process.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-content-upgrade-worker.js [moved from lib/h5p/js/h5p-content-upgrade-worker.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-content-upgrade.js [moved from lib/h5p/js/h5p-content-upgrade.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-data-view.js [moved from lib/h5p/js/h5p-data-view.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-display-options.js [moved from lib/h5p/js/h5p-display-options.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-embed.js [moved from lib/h5p/js/h5p-embed.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-event-dispatcher.js [moved from lib/h5p/js/h5p-event-dispatcher.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-library-details.js [moved from lib/h5p/js/h5p-library-details.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-library-list.js [moved from lib/h5p/js/h5p-library-list.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-resizer.js [moved from lib/h5p/js/h5p-resizer.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-utils.js [moved from lib/h5p/js/h5p-utils.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-version.js [moved from lib/h5p/js/h5p-version.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-x-api-event.js [moved from lib/h5p/js/h5p-x-api-event.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p-x-api.js [moved from lib/h5p/js/h5p-x-api.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/h5p.js [moved from lib/h5p/js/h5p.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/jquery.js [moved from lib/h5p/js/jquery.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/request-queue.js [moved from lib/h5p/js/request-queue.js with 100% similarity]
h5p/h5plib/v124/joubel/core/js/settings/h5p-disable-hub.js [moved from lib/h5p/js/settings/h5p-disable-hub.js with 100% similarity]
h5p/h5plib/v124/joubel/core/readme_moodle.txt [moved from lib/h5p/readme_moodle.txt with 82% similarity]
h5p/h5plib/v124/joubel/core/styles/h5p-admin.css [moved from lib/h5p/styles/h5p-admin.css with 100% similarity]
h5p/h5plib/v124/joubel/core/styles/h5p-confirmation-dialog.css [moved from lib/h5p/styles/h5p-confirmation-dialog.css with 100% similarity]
h5p/h5plib/v124/joubel/core/styles/h5p-core-button.css [moved from lib/h5p/styles/h5p-core-button.css with 100% similarity]
h5p/h5plib/v124/joubel/core/styles/h5p.css [moved from lib/h5p/styles/h5p.css with 100% similarity]
h5p/h5plib/v124/lang/en/h5plib_v124.php [new file with mode: 0644]
h5p/h5plib/v124/thirdpartylibs.xml [new file with mode: 0644]
h5p/h5plib/v124/version.php [new file with mode: 0644]
h5p/lib.php
h5p/templates/h5pembed.mustache
h5p/tests/event_h5p_deleted_test.php
h5p/tests/event_h5p_viewed_test.php
h5p/tests/external_test.php
h5p/tests/fixtures/guess-the-answer.h5p [moved from lib/editor/atto/tests/fixtures/guess-the-answer.h5p with 100% similarity]
h5p/tests/fixtures/ipsums.h5p [moved from filter/displayh5p/tests/fixtures/ipsums.h5p with 100% similarity]
h5p/tests/generator/lib.php
h5p/tests/generator_test.php
h5p/tests/h5p_core_test.php
h5p/tests/h5p_file_storage_test.php
h5p/tests/helper_test.php
h5p/upgrade.txt [new file with mode: 0644]
install/lang/de/install.php
install/lang/el/admin.php
install/lang/el/error.php
install/lang/el/install.php
install/lang/el/moodle.php
install/lang/pt/install.php
lang/en/admin.php
lang/en/h5p.php
lang/en/hub.php
lang/en/moodle.php
lang/en/plugin.php
lang/en/repository.php
lang/en/table.php
lib/adminlib.php
lib/amd/build/modal.min.js
lib/amd/build/modal.min.js.map
lib/amd/build/modal_factory.min.js
lib/amd/build/modal_factory.min.js.map
lib/amd/src/modal.js
lib/amd/src/modal_factory.js
lib/authlib.php
lib/behat/classes/util.php
lib/classes/event/group_member_removed.php
lib/classes/hub/registration.php
lib/classes/plugin_manager.php
lib/classes/plugininfo/h5plib.php [new file with mode: 0644]
lib/components.json
lib/editor/atto/plugins/h5p/lang/en/atto_h5p.php
lib/editor/atto/plugins/h5p/lang/en/deprecated.txt [new file with mode: 0644]
lib/editor/atto/plugins/h5p/lib.php
lib/editor/atto/plugins/h5p/tests/behat/h5p.feature
lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-debug.js
lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-min.js
lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button.js
lib/editor/atto/plugins/h5p/yui/src/button/js/button.js
lib/environmentlib.php
lib/externallib.php
lib/form/filemanager.js
lib/form/templates/element-checkbox.mustache
lib/form/templates/element-date_selector.mustache
lib/form/templates/element-date_time_selector.mustache
lib/form/templates/element-group.mustache
lib/form/templates/element-password-inline.mustache [new file with mode: 0644]
lib/form/templates/element-password.mustache
lib/form/templates/element-template-inline.mustache
lib/form/templates/element-template.mustache
lib/moodlelib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/pagelib.php
lib/questionlib.php
lib/table/classes/dynamic.php [new file with mode: 0644]
lib/table/classes/local/filter/filter.php [new file with mode: 0644]
lib/table/classes/local/filter/filterset.php [new file with mode: 0644]
lib/table/classes/local/filter/integer_filter.php [new file with mode: 0644]
lib/table/classes/local/filter/numeric_comparison_filter.php [new file with mode: 0644]
lib/table/classes/local/filter/string_filter.php [new file with mode: 0644]
lib/table/classes/privacy/provider.php [new file with mode: 0644]
lib/table/tests/coverage.php [new file with mode: 0644]
lib/table/tests/local/filter/filter_test.php [new file with mode: 0644]
lib/table/tests/local/filter/filterset_test.php [new file with mode: 0644]
lib/table/tests/local/filter/integer_filter_test.php [new file with mode: 0644]
lib/table/tests/local/filter/numeric_comparison_filter_test.php [new file with mode: 0644]
lib/table/tests/local/filter/string_filter_test.php [new file with mode: 0644]
lib/templates/action_menu_trigger.mustache
lib/templates/chooser.mustache
lib/templates/navbar.mustache
lib/tests/authlib_test.php
lib/tests/externallib_test.php
lib/tests/fixtures/test_external_function_throwable.php [new file with mode: 0644]
lib/tests/h5p_get_content_types_task_test.php
lib/tests/moodlelib_test.php
lib/tests/questionlib_test.php
lib/thirdpartylibs.xml
lib/upgrade.txt
login/lib.php
login/tests/lib_test.php
message/amd/build/message_drawer_view_conversation.min.js
message/amd/build/message_drawer_view_conversation.min.js.map
message/amd/build/message_drawer_view_overview_section.min.js
message/amd/build/message_drawer_view_overview_section.min.js.map
message/amd/src/message_drawer_view_conversation.js
message/amd/src/message_drawer_view_overview_section.js
message/output/airnotifier/lang/en/message_airnotifier.php
message/output/airnotifier/requestaccesskey.php
message/templates/message_drawer_view_conversation_footer_content.mustache
mod/assign/module.js
mod/assign/styles.css
mod/forum/classes/local/exporters/discussion.php
mod/h5pactivity/backup/moodle2/backup_h5pactivity_activity_task.class.php [new file with mode: 0644]
mod/h5pactivity/backup/moodle2/backup_h5pactivity_stepslib.php [new file with mode: 0644]
mod/h5pactivity/backup/moodle2/restore_h5pactivity_activity_task.class.php [new file with mode: 0644]
mod/h5pactivity/backup/moodle2/restore_h5pactivity_stepslib.php [new file with mode: 0644]
mod/h5pactivity/classes/event/course_module_instance_list_viewed.php [new file with mode: 0644]
mod/h5pactivity/classes/event/course_module_viewed.php [new file with mode: 0644]
mod/h5pactivity/classes/privacy/provider.php [new file with mode: 0644]
mod/h5pactivity/db/access.php [new file with mode: 0644]
mod/h5pactivity/db/install.xml [new file with mode: 0644]
mod/h5pactivity/grade.php [new file with mode: 0644]
mod/h5pactivity/index.php [new file with mode: 0644]
mod/h5pactivity/lang/en/h5pactivity.php [new file with mode: 0644]
mod/h5pactivity/lib.php [new file with mode: 0644]
mod/h5pactivity/mod_form.php [new file with mode: 0644]
mod/h5pactivity/pix/icon.png [new file with mode: 0644]
mod/h5pactivity/pix/icon.svg [new file with mode: 0644]
mod/h5pactivity/tests/behat/add_h5pactivity.feature [new file with mode: 0644]
mod/h5pactivity/tests/events_test.php [new file with mode: 0644]
mod/h5pactivity/tests/generator/lib.php [new file with mode: 0644]
mod/h5pactivity/tests/generator_test.php [new file with mode: 0644]
mod/h5pactivity/version.php [new file with mode: 0644]
mod/h5pactivity/view.php [new file with mode: 0644]
mod/lti/locallib.php
mod/lti/tests/locallib_test.php
mod/url/locallib.php
mod/url/tests/lib_test.php
mod/workshop/tests/behat/file_type_restriction.feature
phpunit.xml.dist
question/behaviour/interactivecountback/tests/walkthrough_test.php
question/classes/bank/edit_menu_column.php
question/engine/tests/helpers.php
question/question.php
question/tests/bank_view_test.php
question/type/match/question.php
question/type/match/questiontype.php
question/type/match/tests/backup_test.php [new file with mode: 0644]
question/type/match/tests/helper.php
question/type/match/tests/question_test.php
question/type/match/tests/questiontype_test.php
question/type/match/tests/walkthrough_test.php
question/type/multichoice/amd/build/clearchoice.min.js
question/type/multichoice/amd/build/clearchoice.min.js.map
question/type/multichoice/amd/src/clearchoice.js
question/type/multichoice/renderer.php
question/type/multichoice/tests/behat/clearanswers.feature [new file with mode: 0644]
question/type/multichoice/tests/walkthrough_test.php
theme/boost/classes/output/core_renderer.php
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/question.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css
user/classes/participants_table.php
user/classes/table/participants_filterset.php [new file with mode: 0644]
user/index.php
version.php

index 19f5a98..c111d11 100644 (file)
@@ -9,6 +9,7 @@ cache/stores/mongodb/MongoDB/
 enrol/lti/ims-blti/
 filter/algebra/AlgParser.pm
 filter/tex/mimetex.*
+h5p/h5plib/v124/joubel/core/
 lib/editor/atto/plugins/html/yui/src/codemirror/
 lib/editor/atto/plugins/html/yui/src/beautify/
 lib/editor/atto/yui/src/rangy/js/*.*
@@ -63,7 +64,6 @@ lib/php-jwt/
 lib/babel-polyfill/
 lib/mdn-polyfills/
 lib/emoji-data/
-lib/h5p/
 media/player/videojs/amd/src/video-lazy.js
 media/player/videojs/amd/src/Youtube-lazy.js
 media/player/videojs/videojs/
index 4d4c82f..a828212 100644 (file)
@@ -10,6 +10,7 @@ cache/stores/mongodb/MongoDB/
 enrol/lti/ims-blti/
 filter/algebra/AlgParser.pm
 filter/tex/mimetex.*
+h5p/h5plib/v124/joubel/core/
 lib/editor/atto/plugins/html/yui/src/codemirror/
 lib/editor/atto/plugins/html/yui/src/beautify/
 lib/editor/atto/yui/src/rangy/js/*.*
@@ -64,7 +65,6 @@ lib/php-jwt/
 lib/babel-polyfill/
 lib/mdn-polyfills/
 lib/emoji-data/
-lib/h5p/
 media/player/videojs/amd/src/video-lazy.js
 media/player/videojs/amd/src/Youtube-lazy.js
 media/player/videojs/videojs/
index c95c73c..c391089 100644 (file)
@@ -226,7 +226,7 @@ if (!core_tables_exist()) {
     }
     if (empty($confirmrelease)) {
         require_once($CFG->libdir.'/environmentlib.php');
-        list($envstatus, $environment_results) = check_moodle_environment(normalize_version($release), ENV_SELECT_RELEASE);
+        list($envstatus, $environmentresults) = check_moodle_environment(normalize_version($release), ENV_SELECT_RELEASE);
         $strcurrentrelease = get_string('currentrelease');
 
         $PAGE->navbar->add($strcurrentrelease);
@@ -235,7 +235,7 @@ if (!core_tables_exist()) {
         $PAGE->set_cacheable(false);
 
         $output = $PAGE->get_renderer('core', 'admin');
-        echo $output->install_environment_page($maturity, $envstatus, $environment_results, $release);
+        echo $output->install_environment_page($maturity, $envstatus, $environmentresults, $release);
         die();
     }
 
@@ -358,9 +358,9 @@ if (!$cache and $version > $CFG->version) {  // upgrade
         echo $output->upgrade_confirm_page($a->newversion, $maturity, $testsite);
         die();
 
-    } else if (empty($confirmrelease)){
+    } else if (empty($confirmrelease)) {
         require_once($CFG->libdir.'/environmentlib.php');
-        list($envstatus, $environment_results) = check_moodle_environment($release, ENV_SELECT_RELEASE);
+        list($envstatus, $environmentresults) = check_moodle_environment($release, ENV_SELECT_RELEASE);
         $strcurrentrelease = get_string('currentrelease');
 
         $PAGE->navbar->add($strcurrentrelease);
@@ -368,7 +368,7 @@ if (!$cache and $version > $CFG->version) {  // upgrade
         $PAGE->set_heading($strcurrentrelease);
         $PAGE->set_cacheable(false);
 
-        echo $output->upgrade_environment_page($release, $envstatus, $environment_results);
+        echo $output->upgrade_environment_page($release, $envstatus, $environmentresults);
         die();
 
     } else if (empty($confirmplugins)) {
@@ -533,7 +533,10 @@ if (!$cache and $branch <> $CFG->branch) {  // Update the branch
 
 if (!$cache and moodle_needs_upgrading()) {
 
-    $PAGE->set_url(new moodle_url($PAGE->url, array('confirmplugincheck' => $confirmplugins)));
+    $PAGE->set_url(new moodle_url($PAGE->url, array(
+        'confirmrelease' => $confirmrelease,
+        'confirmplugincheck' => $confirmplugins,
+    )));
 
     check_upgrade_key($upgradekeyhash);
 
@@ -543,7 +546,21 @@ if (!$cache and moodle_needs_upgrading()) {
         $pluginman = core_plugin_manager::instance();
         $output = $PAGE->get_renderer('core', 'admin');
 
-        if (!$confirmplugins) {
+        if (empty($confirmrelease)) {
+            require_once($CFG->libdir . '/environmentlib.php');
+
+            list($envstatus, $environmentresults) = check_moodle_environment($release, ENV_SELECT_RELEASE);
+            $strcurrentrelease = get_string('currentrelease');
+
+            $PAGE->navbar->add($strcurrentrelease);
+            $PAGE->set_title($strcurrentrelease);
+            $PAGE->set_heading($strcurrentrelease);
+            $PAGE->set_cacheable(false);
+
+            echo $output->upgrade_environment_page($release, $envstatus, $environmentresults);
+            die();
+
+        } else if (!$confirmplugins) {
             $strplugincheck = get_string('plugincheck');
 
             $PAGE->navbar->add($strplugincheck);
@@ -802,7 +819,7 @@ $SESSION->admin_critical_warning = ($insecuredataroot==INSECURE_DATAROOT_ERROR);
 $adminroot = admin_get_root();
 
 // Check if there are any new admin settings which have still yet to be set
-if (any_new_admin_settings($adminroot)){
+if (any_new_admin_settings($adminroot)) {
     redirect('upgradesettings.php');
 }
 
index 881b34c..3b9302d 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-// Settings page.
+// H5P overview.
 $ADMIN->add('h5p', new admin_externalpage('h5poverview', get_string('h5poverview', 'core_h5p'),
     new moodle_url('/h5p/overview.php'), ['moodle/site:config']));
-$ADMIN->add('h5p', new admin_externalpage('h5psettings', get_string('h5pmanage', 'core_h5p'),
+
+// Manage H5P libraries page.
+$ADMIN->add('h5p', new admin_externalpage('h5pmanagelibraries', get_string('h5pmanage', 'core_h5p'),
     new moodle_url('/h5p/libraries.php'), ['moodle/site:config', 'moodle/h5p:updatelibraries']));
+
+// H5P settings.
+$defaulth5plib = \core_h5p\local\library\autoloader::get_default_handler();
+if (!empty($defaulth5plib)) {
+    // As for now this page only has this setting, it will be hidden if there isn't any H5P libraries handler defined.
+    $settings = new admin_settingpage('h5psettings', new lang_string('h5psettings', 'core_h5p'));
+    $ADMIN->add('h5p', $settings);
+
+    $settings->add(new admin_settings_h5plib_handler_select('h5plibraryhandler', new lang_string('h5plibraryhandler', 'core_h5p'),
+        new lang_string('h5plibraryhandler_help', 'core_h5p'), $defaulth5plib));
+}
index 01de61c..14a1331 100644 (file)
@@ -74,4 +74,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
             new lang_string('configallowemojipickerincompatible', 'admin')
         ));
     }
+
+    $optionalsubsystems->add(new admin_setting_configcheckbox('enablemoodlenet', new lang_string('enablemoodlenet', 'admin'),
+        new lang_string('enablemoodlenet_desc', 'admin'), 1, 1, 0));
 }
index 19644be..3617ccc 100644 (file)
@@ -143,15 +143,15 @@ Feature: Set up contextual data for tests
     And I log out
     And I log in as "user2"
     And I am on "Course 1" course homepage
-    And "Turn editing on" "link" should exist in current page administration
+    And "Turn editing on" "button" should exist
     And I log out
     And I log in as "user3"
     And I am on "Course 1" course homepage
-    And "Turn editing on" "link" should exist in current page administration
+    And "Turn editing on" "button" should exist
     And I log out
     And I log in as "user4"
     And I am on "Course 1" course homepage
-    And "Turn editing on" "link" should exist in current page administration
+    And "Turn editing on" "button" should exist
     And I log out
     And I log in as "user5"
     And I should see "You are logged in as"
diff --git a/admin/tool/dataprivacy/tests/privacy_provider_test.php b/admin/tool/dataprivacy/tests/privacy_provider_test.php
new file mode 100644 (file)
index 0000000..e671fa7
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Tests for the plugin privacy provider
+ *
+ * @package    tool_dataprivacy
+ * @copyright  2020 Paul Holden <paulh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_privacy\local\request\userlist;
+use core_privacy\local\request\writer;
+use core_privacy\tests\provider_testcase;
+use tool_dataprivacy\api;
+use tool_dataprivacy\local\helper;
+use tool_dataprivacy\privacy\provider;
+
+/**
+ * Privacy provider tests
+ *
+ * @package    tool_dataprivacy
+ * @copyright  2020 Paul Holden <paulh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_dataprivacy_privacy_provider_testcase extends provider_testcase {
+
+    /**
+     * Test provider get_contexts_for_userid method
+     *
+     * @return void
+     */
+    public function test_get_contexts_for_userid() {
+        $this->resetAfterTest();
+
+        $user = $this->getDataGenerator()->create_user();
+        $context = context_user::instance($user->id);
+
+        // Returned context list should contain a single item.
+        $contextlist = $this->get_contexts_for_userid($user->id, 'tool_dataprivacy');
+        $this->assertCount(1, $contextlist);
+
+        // We should have the user context of our test user.
+        $this->assertSame($context, $contextlist->current());
+    }
+
+    /**
+     * Test provider get_users_in_context method
+     *
+     * @return void
+     */
+    public function test_get_users_in_context() {
+        $this->resetAfterTest();
+
+        $user = $this->getDataGenerator()->create_user();
+        $context = context_user::instance($user->id);
+
+        $userlist = new userlist($context, 'tool_dataprivacy');
+        provider::get_users_in_context($userlist);
+
+        $this->assertEquals([$user->id], $userlist->get_userids());
+    }
+
+    /**
+     * Test provider get_users_in_context method for a non-user context
+     *
+     * @return void
+     */
+    public function test_get_users_in_context_non_user_context() {
+        $context = context_system::instance();
+
+        $userlist = new userlist($context, 'tool_dataprivacy');
+        provider::get_users_in_context($userlist);
+
+        $this->assertEmpty($userlist);
+    }
+
+    /**
+     * Test provider export_user_data method
+     *
+     * @return void
+     */
+    public function test_export_user_data() {
+        $this->resetAfterTest();
+
+        $user = $this->getDataGenerator()->create_user();
+        $context = context_user::instance($user->id);
+
+        $this->setUser($user);
+
+        // Create an export request, approve it.
+        $requestexport = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT,
+            'Please export my stuff');
+        api::update_request_status($requestexport->get('id'), api::DATAREQUEST_STATUS_APPROVED);
+
+        // Create a deletion request, reject it.
+        $requestdelete = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
+        api::update_request_status($requestdelete->get('id'), api::DATAREQUEST_STATUS_REJECTED, 0, 'Nope');
+
+        $this->export_context_data_for_user($user->id, $context, 'tool_dataprivacy');
+
+        /** @var \core_privacy\tests\request\content_writer $writer */
+        $writer = writer::with_context($context);
+        $this->assertTrue($writer->has_any_data());
+
+        /** @var stdClass[] $data */
+        $data = (array) $writer->get_data([
+            get_string('privacyandpolicies', 'admin'),
+            get_string('datarequests', 'tool_dataprivacy'),
+        ]);
+
+        $this->assertCount(2, $data);
+
+        $strs = get_strings(['requesttypeexportshort', 'requesttypedeleteshort',
+            'statusapproved', 'statusrejected', 'creationmanual'], 'tool_dataprivacy');
+
+        // First item is the approved export request.
+        $this->assertEquals($strs->requesttypeexportshort, $data[0]->type);
+        $this->assertEquals($strs->statusapproved, $data[0]->status);
+        $this->assertEquals($strs->creationmanual, $data[0]->creationmethod);
+        $this->assertEquals($requestexport->get('comments'), $data[0]->comments);
+        $this->assertEmpty($data[0]->dpocomment);
+        $this->assertNotEmpty($data[0]->timecreated);
+
+        // Next is the rejected deletion request.
+        $this->assertEquals($strs->requesttypedeleteshort, $data[1]->type);
+        $this->assertEquals($strs->statusrejected, $data[1]->status);
+        $this->assertEquals($strs->creationmanual, $data[1]->creationmethod);
+        $this->assertEmpty($data[1]->comments);
+        $this->assertContains('Nope', $data[1]->dpocomment);
+        $this->assertNotEmpty($data[1]->timecreated);
+    }
+
+    /**
+     * Test class export_user_preferences method
+     *
+     * @return void
+     */
+    public function test_export_user_preferences() {
+        $this->resetAfterTest();
+
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+
+        // Set filters preference.
+        $filters = [
+            helper::FILTER_TYPE . ':' . api::DATAREQUEST_TYPE_EXPORT,
+            helper::FILTER_STATUS . ':' . api::DATAREQUEST_STATUS_PENDING,
+        ];
+        set_user_preference(helper::PREF_REQUEST_FILTERS, json_encode($filters), $user);
+
+        // Set paging preference.
+        set_user_preference(helper::PREF_REQUEST_PERPAGE, 6, $user);
+
+        provider::export_user_preferences($user->id);
+
+        /** @var \core_privacy\tests\request\content_writer $writer */
+        $writer = writer::with_context(context_system::instance());
+        $this->assertTrue($writer->has_any_data());
+
+        /** @var stdClass[] $preferences */
+        $preferences = (array) $writer->get_user_preferences('tool_dataprivacy');
+        $this->assertCount(2, $preferences);
+
+        $this->assertEquals((object) [
+            'value' => '1:1, 2:0',
+            'description' => 'Type: Export, Status: Pending',
+        ], $preferences[helper::PREF_REQUEST_FILTERS]);
+
+        $this->assertEquals(6, $preferences[helper::PREF_REQUEST_PERPAGE]->value);
+    }
+}
\ No newline at end of file
index 40d2595..8eef2d8 100644 (file)
@@ -762,6 +762,13 @@ class manager {
      * @param   int     $direction
      */
     protected static function _move_tour(tour $tour, $direction) {
+        // We can't move the first tour higher, nor the last tour any lower.
+        if (($tour->is_first_tour() && $direction == helper::MOVE_UP) ||
+                ($tour->is_last_tour() && $direction == helper::MOVE_DOWN)) {
+
+            return;
+        }
+
         $currentsortorder   = $tour->get_sortorder();
         $targetsortorder    = $currentsortorder + $direction;
 
@@ -890,6 +897,9 @@ class manager {
         }
         $existingtourrecords->close();
 
+        // Ensure we correct the sortorder in any existing tours, prior to adding latest shipped tours.
+        helper::reset_tour_sortorder();
+
         foreach (array_reverse($shippedtours) as $filename => $version) {
             $filepath = $CFG->dirroot . "/{$CFG->admin}/tool/usertours/tours/" . $filename;
             $tourjson = file_get_contents($filepath);
index a6855ea..db3ccd8 100644 (file)
@@ -61,5 +61,12 @@ function xmldb_tool_usertours_upgrade($oldversion) {
     // Automatically generated Moodle v3.8.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2020031900) {
+        // Updating shipped tours will fix broken sortorder records in existing tours.
+        manager::update_shipped_tours();
+
+        upgrade_plugin_savepoint(true, 2020031900, 'tool', 'usertours');
+    }
+
     return true;
 }
index bffecb3..913c3e1 100644 (file)
@@ -122,6 +122,83 @@ class tool_usertours_manager_testcase extends advanced_testcase {
         $rcm->invokeArgs($manager, $arguments);
     }
 
+    /**
+     * Data provider for test_move_tour
+     *
+     * @return array
+     */
+    public function move_tour_provider() {
+        $alltours = [
+            ['name' => 'Tour 1'],
+            ['name' => 'Tour 2'],
+            ['name' => 'Tour 3'],
+        ];
+
+        return [
+            'Move up' => [
+                $alltours,
+                'Tour 2',
+                \tool_usertours\helper::MOVE_UP,
+                0,
+            ],
+            'Move down' => [
+                $alltours,
+                'Tour 2',
+                \tool_usertours\helper::MOVE_DOWN,
+                2,
+            ],
+            'Move up (first)' => [
+                $alltours,
+                'Tour 1',
+                \tool_usertours\helper::MOVE_UP,
+                0,
+            ],
+            'Move down (last)' => [
+                $alltours,
+                'Tour 3',
+                \tool_usertours\helper::MOVE_DOWN,
+                2,
+            ],
+        ];
+    }
+
+    /**
+     * Test moving tours (changing sortorder)
+     *
+     * @dataProvider move_tour_provider
+     *
+     * @param array $alltours
+     * @param string $movetourname
+     * @param int $direction
+     * @param int $expectedsortorder
+     * @return void
+     */
+    public function test_move_tour($alltours, $movetourname, $direction, $expectedsortorder) {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        // Clear out existing tours so ours are the only ones, otherwise we can't predict the sortorder.
+        $DB->delete_records('tool_usertours_tours');
+
+        foreach ($alltours as $tourconfig) {
+            $this->helper_create_tour((object) $tourconfig);
+        }
+
+        // Load our tour to move.
+        $record = $DB->get_record('tool_usertours_tours', ['name' => $movetourname]);
+        $tour = \tool_usertours\tour::load_from_record($record);
+
+        // Call protected method via reflection.
+        $class = new ReflectionClass(\tool_usertours\manager::class);
+        $method = $class->getMethod('_move_tour');
+        $method->setAccessible(true);
+        $method->invokeArgs(null, [$tour, $direction]);
+
+        // Assert expected sortorder.
+        $this->assertEquals($expectedsortorder, $tour->get_sortorder());
+    }
+
     /**
      * Data Provider for get_matching_tours tests.
      *
index c56c1ff..aa140f9 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2019120400;            // The current module version (Date: YYYYMMDDXX).
+$plugin->version   = 2020031900;            // The current module version (Date: YYYYMMDDXX).
 $plugin->requires  = 2019111200;            // Requires this Moodle version.
 $plugin->component = 'tool_usertours';      // Full name of the plugin (used for diagnostics).
index c4f9e2c..6649934 100644 (file)
@@ -955,6 +955,52 @@ class core_backup_moodle2_testcase extends advanced_testcase {
         $this->assertEquals('', $requests[2]->searcharea);
     }
 
+    /**
+     * Test restoring courses based on the backup plan. Primarily used with
+     * the import functionality
+     */
+    public function test_restore_course_using_plan_defaults() {
+        global $DB, $CFG, $USER;
+
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+        $CFG->enableglobalsearch = true;
+
+        // Set admin config setting so that activities are not restored by default.
+        set_config('restore_general_activities', 0, 'restore');
+
+        // Create a course.
+        $generator = $this->getDataGenerator();
+        $course = $generator->create_course();
+        $course2 = $generator->create_course();
+        $course3 = $generator->create_course();
+
+        // Add a forum.
+        $forum = $generator->create_module('forum', ['course' => $course->id]);
+
+        // Backup course...
+        $CFG->backup_file_logger_level = backup::LOG_NONE;
+        $bc = new backup_controller(backup::TYPE_1COURSE, $course->id,
+            backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT,
+            $USER->id);
+        $backupid = $bc->get_backupid();
+        $bc->execute_plan();
+        $bc->destroy();
+
+        // Restore it on top of course2 (should duplicate the forum).
+        $rc = new restore_controller($backupid, $course2->id,
+            backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id,
+            backup::TARGET_EXISTING_ADDING, null, backup::RELEASESESSION_NO);
+        $this->assertTrue($rc->execute_precheck());
+        $rc->execute_plan();
+        $rc->destroy();
+
+        // Get the forums now on the old course.
+        $modinfo = get_fast_modinfo($course2->id);
+        $forums = $modinfo->get_instances_of('forum');
+        $this->assertCount(0, $forums);
+    }
+
     /**
      * The Question category hierarchical structure was changed in Moodle 3.5.
      * From 3.5, all question categories in each context are a child of a single top level question category for that context.
index 1894eeb..6649777 100644 (file)
@@ -282,7 +282,14 @@ abstract class restore_controller_dbops extends restore_dbops {
             if ($plan->setting_exists($settingname)) {
                 $setting = $plan->get_setting($settingname);
                 $value = self::get_setting_default($config, $setting);
-                $locked = (get_config('restore', $config . '_locked') == true);
+                $locked = (get_config('restore',$config . '_locked') == true);
+
+                // Use the original value when this is an import and the setting is unlocked.
+                if ($controller->get_mode() == backup::MODE_IMPORT && $controller->get_interactive()) {
+                    if (!$uselocks || !$locked) {
+                        $value = $setting->get_value();
+                    }
+                }
 
                 // We can only update the setting if it isn't already locked by config or permission.
                 if ($setting->get_status() != base_setting::LOCKED_BY_CONFIG
index b36e515..309c478 100644 (file)
@@ -305,10 +305,17 @@ class async_helper  {
         $tabledata = array();
 
         // Get relevant backup ids based on context instance id.
-        $select = 'itemid = ? AND execution = ? AND status < ? AND status > ?';
-        $params = array($instanceid, backup::EXECUTION_DELAYED, backup::STATUS_FINISHED_ERR, backup::STATUS_NEED_PRECHECK);
-        $backups = $DB->get_records_select('backup_controllers', $select, $params, 'timecreated DESC', 'id, backupid, timecreated');
+        $select = 'itemid = :itemid AND execution = :execution AND status < :status1 AND status > :status2 ' .
+            'AND operation = :operation';
+        $params = [
+            'itemid' => $instanceid,
+            'execution' => backup::EXECUTION_DELAYED,
+            'status1' => backup::STATUS_FINISHED_ERR,
+            'status2' => backup::STATUS_NEED_PRECHECK,
+            'operation' => 'backup',
+        ];
 
+        $backups = $DB->get_records_select('backup_controllers', $select, $params, 'timecreated DESC', 'id, backupid, timecreated');
         foreach ($backups as $backup) {
             $bc = \backup_controller::load_controller($backup->backupid);  // Get the backup controller.
             $filename = $bc->get_plan()->get_setting('filename')->get_value();
index 19b0fc6..afc49fd 100644 (file)
@@ -45,7 +45,7 @@ Feature: Block appearances
     And I set the following fields to these values:
       | Display on page types | Any course page |
     And I press "Save changes"
-    And I follow "Turn editing off"
+    And I press "Turn editing off"
     And I follow "Test survey name"
     And I should not see "Comments"
 
@@ -55,6 +55,6 @@ Feature: Block appearances
     And I set the following fields to these values:
       | Visible | No |
     And I press "Save changes"
-    And I follow "Turn editing off"
+    And I press "Turn editing off"
     And I follow "Test book name"
     Then I should not see "Comments"
index 3137c7d..8fd4293 100644 (file)
Binary files a/calendar/amd/build/calendar_mini.min.js.map and b/calendar/amd/build/calendar_mini.min.js.map differ
index 30f0385..e419a25 100644 (file)
@@ -20,7 +20,7 @@
  * components by listening for and responding to different events
  * triggered within the calendar UI.
  *
- * @module     core_calendar/calendar
+ * @module     core_calendar/calendar_mini
  * @package    core_calendar
  * @copyright  2017 Andrew Nicols <andrew@nicols.co.uk>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
index 7e888b8..377d2cd 100644 (file)
@@ -126,6 +126,10 @@ class day_exporter extends exporter {
                 'type' => PARAM_URL,
                 'optional' => true,
             ],
+            'viewdaylinktitle' => [
+                'type' => PARAM_RAW,
+                'optional' => true,
+            ],
             'events' => [
                 'type' => calendar_event_exporter::read_properties_definition(),
                 'multiple' => true,
@@ -183,6 +187,10 @@ class day_exporter extends exporter {
             'viewdaylink' => $this->url->out(false),
         ];
 
+        if ($viewdaylinktitle = $this->get_view_link_title()) {
+            $return['viewdaylinktitle'] = $viewdaylinktitle;
+        }
+
 
         $cache = $this->related['cache'];
         $eventexporters = array_map(function($event) use ($cache, $output) {
@@ -267,4 +275,22 @@ class day_exporter extends exporter {
             'time' => $this->calendar->time,
         ]);
     }
+
+    /**
+     * Get the title for view link.
+     *
+     * @return string
+     */
+    protected function get_view_link_title() {
+        $title = null;
+
+        $userdate = userdate($this->data[0], get_string('strftimedayshort'));
+        if ($this->data['istoday']) {
+            $title = get_string('todayplustitle', 'calendar', $userdate);
+        } else if (count($this->related['events'])) {
+            $title = get_string('eventsfor', 'calendar', $userdate);
+        }
+
+        return $title;
+    }
 }
index a078c02..0679979 100644 (file)
@@ -292,13 +292,15 @@ class core_calendar_renderer extends plugin_renderer_base {
         $courseurl = new moodle_url($returnurl);
         $courseurl->remove_params('course');
 
-        if ($label === null) {
+        $labelattributes = [];
+        if (empty($label)) {
             $label = get_string('listofcourses');
+            $labelattributes['class'] = 'sr-only';
         }
 
-        $select = html_writer::label($label, 'course', false, ['class' => 'mr-1']);
+        $select = html_writer::label($label, 'course', false, $labelattributes);
         $select .= html_writer::select($courseoptions, 'course', $selected, false,
-                ['class' => 'cal_courses_flt mr-auto']);
+                ['class' => 'cal_courses_flt ml-1 mr-auto', 'id' => 'course']);
 
         return $select;
     }
index 5438bd5..8772356 100644 (file)
@@ -34,7 +34,7 @@
     }} data-toggle="popover"{{!
     }} data-html="true"{{!
     }} data-region="mini-day-link"{{!
-    }} data-trigger="hover"{{!
+    }} data-trigger="hover focus"{{!
     }} data-placement="top"{{!
     }} data-year="{{year}}"{{!
     }} data-month="{{date.mon}}"{{!
@@ -42,6 +42,7 @@
     }} data-categoryid="{{categoryid}}"{{!
     }} data-title="{{$title}}{{title}}{{/title}}"{{!
     }} data-alternate="{{$nocontent}}{{/nocontent}}"{{!
+    }} aria-label="{{viewdaylinktitle}}"{{!
 }}>{{$day}}{{day}}{{/day}}</a>
 <div class="hidden">
     {{$content}}{{/content}}
index d5dbfa4..e1273ec 100644 (file)
@@ -46,7 +46,7 @@
         <thead>
             <tr>
                 {{# daynames }}
-                <th class="header text-xs-center">
+                <th class="header text-xs-center" aria-label="{{fullname}}">
                     {{shortname}}
                 </th>
                 {{/ daynames }}
@@ -72,7 +72,7 @@
                         data-new-event-timestamp="{{neweventtimestamp}}">
                         <div class="d-none d-md-block hidden-phone text-xs-center">
                             {{#hasevents}}
-                                <a data-action="view-day-link" href="#" class="day" title="{{viewdaylinktitle}}"
+                                <a data-action="view-day-link" href="#" class="day" aria-label="{{viewdaylinktitle}}"
                                     data-year="{{date.year}}" data-month="{{date.mon}}" data-day="{{mday}}"
                                     data-courseid="{{courseid}}" data-categoryid="{{categoryid}}"
                                     data-timestamp="{{timestamp}}">{{mday}}</a>
                         </div>
                         <div class="d-md-none hidden-desktop hidden-tablet">
                             {{#hasevents}}
-                                <a data-action="view-day-link" href="#" class="day" title="{{viewdaylinktitle}}"
+                                <a data-action="view-day-link" href="#" class="day" aria-label="{{viewdaylinktitle}}"
                                     data-year="{{date.year}}" data-month="{{date.mon}}" data-day="{{mday}}"
                                     data-courseid="{{courseid}}" data-categoryid="{{categoryid}}"
                                     data-timestamp="{{timestamp}}">{{mday}}</a>
                             {{/hasevents}}
                             {{^hasevents}}
-                                <div data-region="day-content">
                                     {{mday}}
-                                </div>
                             {{/hasevents}}
                         </div>
                     </td>
index d5cef72..ff10a74 100644 (file)
@@ -78,8 +78,8 @@
         <thead>
           <tr>
                 {{# daynames }}
-                <th class="header text-xs-center" scope="col">
-                    <abbr title="{{fullname}}">{{shortname}}</abbr>
+                <th class="header text-xs-center" scope="col" aria-label="{{fullname}}">
+                    {{shortname}}
                 </th>
                 {{/ daynames }}
             </tr>
index 8d17536..7dddb2f 100644 (file)
@@ -15,7 +15,7 @@
     along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 }}
 {{!
-    @template calendar/calendar_threemonth
+    @template calendar/threemonth_month
 
     Calendar view to show three months as a block.
 
index 3956959..893b79f 100644 (file)
@@ -170,7 +170,7 @@ class behat_completion extends behat_base {
         }
         $iconxpath = "//li[contains(concat(' ', @class, ' '), ' modtype_" . strtolower($activitytype) . " ')]";
         $iconxpath .= "[descendant::*[contains(text(), '" . $activityname . "')]]";
-        $iconxpath .= "/descendant::span[@class='actions']/descendant::img[contains(@src, 'i/completion-')]";
+        $iconxpath .= "/descendant::div[@class='actions']/descendant::img[contains(@src, 'i/completion-')]";
 
         $this->execute("behat_general::the_attribute_of_should_contain",
             array("src", $iconxpath, "xpath_element", $imgname)
@@ -193,7 +193,7 @@ class behat_completion extends behat_base {
         }
         $iconxpath = "//li[contains(concat(' ', @class, ' '), ' modtype_" . strtolower($activitytype) . " ')]";
         $iconxpath .= "[descendant::*[contains(text(), '" . $activityname . "')]]";
-        $iconxpath .= "/descendant::span[@class='actions']/descendant::img[contains(@src, 'i/completion-')]";
+        $iconxpath .= "/descendant::div[@class='actions']/descendant::img[contains(@src, 'i/completion-')]";
 
         $this->execute("behat_general::the_attribute_of_should_contain",
             array("src", $iconxpath, "xpath_element", $imgname)
index fa3aa00..884ee1c 100644 (file)
Binary files a/course/amd/build/actions.min.js and b/course/amd/build/actions.min.js differ
index d15fd1a..3cbade0 100644 (file)
Binary files a/course/amd/build/actions.min.js.map and b/course/amd/build/actions.min.js.map differ
index 114a575..56f6374 100644 (file)
Binary files a/course/amd/build/activitychooser.min.js and b/course/amd/build/activitychooser.min.js differ
index 5dc3ca7..9201790 100644 (file)
Binary files a/course/amd/build/activitychooser.min.js.map and b/course/amd/build/activitychooser.min.js.map differ
index 042132f..65c611f 100644 (file)
Binary files a/course/amd/build/local/activitychooser/dialogue.min.js and b/course/amd/build/local/activitychooser/dialogue.min.js differ
index 0c18fec..8ea4836 100644 (file)
Binary files a/course/amd/build/local/activitychooser/dialogue.min.js.map and b/course/amd/build/local/activitychooser/dialogue.min.js.map differ
index 0aee2dc..37df273 100644 (file)
Binary files a/course/amd/build/local/activitychooser/selectors.min.js and b/course/amd/build/local/activitychooser/selectors.min.js differ
index da656df..75707c6 100644 (file)
Binary files a/course/amd/build/local/activitychooser/selectors.min.js.map and b/course/amd/build/local/activitychooser/selectors.min.js.map differ
index c1446ef..a2eef2c 100644 (file)
@@ -567,7 +567,7 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
                 str.get_string('numberweeks').done(function(strNumberSections) {
                     var trigger = $(SELECTOR.ADDSECTIONS),
                         modalTitle = trigger.attr('data-add-sections'),
-                        newSections = trigger.attr('new-sections');
+                        newSections = trigger.attr('data-new-sections');
                     var modalBody = $('<div><label for="add_section_numsections"></label> ' +
                         '<input id="add_section_numsections" type="number" min="1" max="' + newSections + '" value="1"></div>');
                     modalBody.find('label').html(strNumberSections);
index 6565e72..cb58e9f 100644 (file)
@@ -109,10 +109,10 @@ const sectionIdMapper = (webServiceData, id) => {
 };
 
 /**
- * Build a modal for each section ID and store it into a map for quick access
+ * Build a modal on demand to save page load times
  *
  * @method modalBuilder
- * @param {Map} data our map of section ID's & modules to generate modals for
+ * @param {Array} data our array of modules with section ID's applied in the URL field
  * @return {Object} Our modal that we are going to show the user
  */
 const modalBuilder = data => buildModal(templateDataBuilder(data));
@@ -149,7 +149,7 @@ const templateDataBuilder = (data) => {
 };
 
 /**
- * Given an object we want to prebuild a modal ready to store into a map
+ * Given an object we want to build a modal ready to show
  *
  * @method buildModal
  * @param {Object} data The template data which contains arrays of modules
@@ -159,7 +159,7 @@ const buildModal = data => {
     return ModalFactory.create({
         type: ModalFactory.types.DEFAULT,
         title: getString('addresourceoractivity'),
-        body: Templates.render('core_course/chooser', data),
+        body: Templates.render('core_course/activitychooser', data),
         large: true,
         templateContext: {
             classes: 'modchooser'
@@ -230,7 +230,8 @@ const partiallyAppliedFavouriteManager = (moduleData, sectionId) => {
 
                 const builtFaves = sectionIdMapper(newFaves, sectionId);
 
-                const {html, js} = await Templates.renderForPromise('core_course/chooser_favourites', {favourites: builtFaves});
+                const {html, js} = await Templates.renderForPromise('core_course/local/activitychooser/favourites',
+                    {favourites: builtFaves});
 
                 await Templates.replaceNodeContents(favouriteArea, html, js);
 
index 9429ee6..9f803c3 100644 (file)
@@ -53,13 +53,13 @@ const showModuleHelp = (carousel, moduleData) => {
     });
 
     // Build up the html & js ready to place into the help section.
-    const contentPromise = Templates.renderForPromise('core_course/chooser_help', moduleData);
+    const contentPromise = Templates.renderForPromise('core_course/local/activitychooser/help', moduleData);
 
     // Wait for the content to be ready, and for the transition to be complet.
     Promise.all([contentPromise, spinnerPromise, transitionPromise])
         .then(([{html, js}]) => Templates.replaceNodeContents(help, html, js))
         .then(() => {
-            help.querySelector(selectors.regions.chooserSummary.description).focus();
+            help.querySelector(selectors.regions.chooserSummary.header).focus();
             return help;
         })
         .catch(Notification.exception);
@@ -402,7 +402,7 @@ const renderSearchResults = async(searchResultsContainer, searchResultsData) =>
         'searchresults': searchResultsData
     };
     // Build up the html & js ready to place into the help section.
-    const {html, js} = await Templates.renderForPromise('core_course/chooser_search_results', templateData);
+    const {html, js} = await Templates.renderForPromise('core_course/local/activitychooser/search_results', templateData);
     await Templates.replaceNodeContents(searchResultsContainer, html, js);
 };
 
index dc653bf..a072a1e 100644 (file)
@@ -45,7 +45,7 @@ export default {
         chooserSummary: {
             container: getDataSelector('region', 'chooser-option-summary-container'),
             content: getDataSelector('region', 'chooser-option-summary-content-container'),
-            description: getDataSelector('region', 'summary-description'),
+            header: getDataSelector('region', 'summary-header'),
             actions: getDataSelector('region', 'chooser-option-summary-actions-container'),
         },
         carousel: getDataSelector('region', 'carousel'),
index b92e4fe..8152c6d 100644 (file)
@@ -53,7 +53,9 @@ class content_item_readonly_repository implements content_item_readonly_reposito
             if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs.
                 $link = get_string('modulename_link', $modname);
                 $linktext = get_string('morehelp');
-                $help .= \html_writer::tag('div', $OUTPUT->doc_link($link, $linktext, true), ['class' => 'helpdoclink']);
+                $arialabel = get_string('morehelpaboutmodule', '', get_string('modulename', $modname));
+                $doclink = $OUTPUT->doc_link($link, $linktext, true, ['aria-label' => $arialabel]);
+                $help .= \html_writer::tag('div', $doclink, ['class' => 'helpdoclink']);
             }
         }
         return $help;
index 3c2a605..76aadb9 100644 (file)
@@ -141,10 +141,17 @@ class content_item_service {
 
         $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
         $favourites = [];
-        foreach ($itemtypes as $itemtype) {
-            $favs = $ufservice->find_favourites_by_type(self::COMPONENT, $itemtype);
-            $favobj = (object) ['itemtype' => $itemtype, 'ids' => array_column($favs, 'itemid')];
-            $favourites[] = $favobj;
+        $favs = $ufservice->find_all_favourites(self::COMPONENT, $itemtypes);
+        $favsreduced = array_reduce($favs, function($carry, $item) {
+            $carry[$item->itemtype][$item->itemid] = 0;
+            return $carry;
+        }, []);
+
+        foreach ($itemtypes as $type) {
+            $favourites[] = (object) [
+                'itemtype' => $type,
+                'ids' => isset($favsreduced[$type]) ? array_keys($favsreduced[$type]) : []
+            ];
         }
         return $favourites;
     }
index 8adef40..50a5fe1 100644 (file)
@@ -978,7 +978,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             $icon = $this->output->pix_icon('t/add', '');
             $newsections = $maxsections - $lastsection;
             echo html_writer::link($url, $icon . $straddsections,
-                array('class' => 'add-sections', 'data-add-sections' => $straddsections, 'new-sections' => $newsections));
+                array('class' => 'add-sections', 'data-add-sections' => $straddsections, 'data-new-sections' => $newsections));
             echo html_writer::end_tag('div');
         }
     }
index 18bcfe9..7268824 100644 (file)
@@ -2612,31 +2612,68 @@ function update_course($data, $editoroptions = NULL) {
 }
 
 /**
- * Average number of participants
- * @return integer
+ * Calculate the average number of enrolled participants per course.
+ *
+ * This is intended for statistics purposes during the site registration. Only visible courses are taken into account.
+ * Front page enrolments are excluded.
+ *
+ * @param bool $onlyactive Consider only active enrolments in enabled plugins and obey the enrolment time restrictions.
+ * @param int $lastloginsince If specified, count only users who logged in after this timestamp.
+ * @return float
  */
-function average_number_of_participants() {
-    global $DB, $SITE;
+function average_number_of_participants(bool $onlyactive = false, int $lastloginsince = null): float {
+    global $DB;
 
-    //count total of enrolments for visible course (except front page)
-    $sql = 'SELECT COUNT(*) FROM (
-        SELECT DISTINCT ue.userid, e.courseid
-        FROM {user_enrolments} ue, {enrol} e, {course} c
-        WHERE ue.enrolid = e.id
-            AND e.courseid <> :siteid
-            AND c.id = e.courseid
-            AND c.visible = 1) total';
-    $params = array('siteid' => $SITE->id);
-    $enrolmenttotal = $DB->count_records_sql($sql, $params);
+    $params = [
+        'siteid' => SITEID,
+    ];
 
+    $sql = "SELECT DISTINCT ue.userid, e.courseid
+              FROM {user_enrolments} ue
+              JOIN {enrol} e ON e.id = ue.enrolid
+              JOIN {course} c ON c.id = e.courseid ";
 
-    //count total of visible courses (minus front page)
-    $coursetotal = $DB->count_records('course', array('visible' => 1));
-    $coursetotal = $coursetotal - 1 ;
+    if ($onlyactive || $lastloginsince) {
+        $sql .= "JOIN {user} u ON u.id = ue.userid ";
+    }
+
+    $sql .= "WHERE e.courseid <> :siteid
+               AND c.visible = 1 ";
+
+    if ($onlyactive) {
+        $sql .= "AND ue.status = :active
+                 AND e.status = :enabled
+                 AND ue.timestart < :now1
+                 AND (ue.timeend = 0 OR ue.timeend > :now2) ";
+
+        // Same as in the enrollib - the rounding should help caching in the database.
+        $now = round(time(), -2);
+
+        $params += [
+            'active' => ENROL_USER_ACTIVE,
+            'enabled' => ENROL_INSTANCE_ENABLED,
+            'now1' => $now,
+            'now2' => $now,
+        ];
+    }
+
+    if ($lastloginsince) {
+        $sql .= "AND u.lastlogin > :lastlogin ";
+        $params['lastlogin'] = $lastloginsince;
+    }
+
+    $sql = "SELECT COUNT(*)
+              FROM ($sql) total";
+
+    $enrolmenttotal = $DB->count_records_sql($sql, $params);
+
+    // Get the number of visible courses (exclude the front page).
+    $coursetotal = $DB->count_records('course', ['visible' => 1]);
+    $coursetotal = $coursetotal - 1;
 
-    //average of enrolment
     if (empty($coursetotal)) {
         $participantaverage = 0;
+
     } else {
         $participantaverage = $enrolmenttotal / $coursetotal;
     }
index 54f6f9c..67719a7 100644 (file)
@@ -27,7 +27,7 @@ require("../config.php");
 require_once("lib.php");
 
 $sectionreturn = optional_param('sr', null, PARAM_INT);
-$add           = optional_param('add', '', PARAM_ALPHA);
+$add           = optional_param('add', '', PARAM_ALPHANUM);
 $type          = optional_param('type', '', PARAM_ALPHA);
 $indent        = optional_param('indent', 0, PARAM_INT);
 $update        = optional_param('update', 0, PARAM_INT);
index f048bcd..0388708 100644 (file)
@@ -31,7 +31,7 @@ require_once($CFG->libdir.'/completionlib.php');
 require_once($CFG->libdir.'/plagiarismlib.php');
 require_once($CFG->dirroot . '/course/modlib.php');
 
-$add    = optional_param('add', '', PARAM_ALPHA);     // module name
+$add    = optional_param('add', '', PARAM_ALPHANUM);     // Module name.
 $update = optional_param('update', 0, PARAM_INT);
 $return = optional_param('return', 0, PARAM_BOOL);    //return to course/view.php if false or mod/modname/view.php if true
 $type   = optional_param('type', '', PARAM_ALPHANUM); //TODO: hopefully will be removed in 2.0
index 34c25f0..2bbdac0 100644 (file)
@@ -942,7 +942,7 @@ abstract class moodleform_mod extends moodleform {
         $mform->setType('instance', PARAM_INT);
 
         $mform->addElement('hidden', 'add', 0);
-        $mform->setType('add', PARAM_ALPHA);
+        $mform->setType('add', PARAM_ALPHANUM);
 
         $mform->addElement('hidden', 'update', 0);
         $mform->setType('update', PARAM_INT);
index 53cfd08..784d6ee 100644 (file)
@@ -268,107 +268,129 @@ class core_course_renderer extends plugin_renderer_base {
      * @return string
      */
     function course_section_add_cm_control($course, $section, $sectionreturn = null, $displayoptions = array()) {
-        global $CFG, $PAGE, $USER;
-
-        $vertical = !empty($displayoptions['inblock']);
-
-        // Check to see if user can add menus.
-        if (!has_capability('moodle/course:manageactivities', context_course::instance($course->id))
+        global $CFG, $USER;
+
+        // The returned control HTML can be one of the following:
+        // - Only the non-ajax control (select menus of activities and resources) with a noscript fallback for non js clients.
+        // - Only the ajax control (the link which when clicked produces the activity chooser modal). No noscript fallback.
+        // - [Behat only]: The non-ajax control and optionally the ajax control (depending on site settings). If included, the link
+        // takes priority and the non-ajax control is wrapped in a <noscript>.
+        // Behat requires the third case because some features run with JS, some do not. We must include the noscript fallback.
+        $behatsite = defined('BEHAT_SITE_RUNNING');
+        $nonajaxcontrol = '';
+        $ajaxcontrol = '';
+        $courseajaxenabled = course_ajax_enabled($course);
+        $userchooserenabled = get_user_preferences('usemodchooser', $CFG->modchooserdefault);
+
+        // Decide what combination of controls to output:
+        // During behat runs, both controls can be used in conjunction to provide non-js fallback.
+        // During normal use only one control or the other will be output. No non-js fallback is needed.
+        $rendernonajaxcontrol = $behatsite || !$courseajaxenabled || !$userchooserenabled || $course->id != $this->page->course->id;
+        $renderajaxcontrol = $courseajaxenabled && $userchooserenabled && $course->id == $this->page->course->id;
+
+        // The non-ajax control, which includes an entirely non-js (<noscript>) fallback too.
+        if ($rendernonajaxcontrol) {
+            $vertical = !empty($displayoptions['inblock']);
+
+            // Check to see if user can add menus.
+            if (!has_capability('moodle/course:manageactivities', context_course::instance($course->id))
                 || !$this->page->user_is_editing()) {
-            return '';
-        }
+                return '';
+            }
 
-        // Retrieve all modules with associated metadata
-        $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
-        $urlparams = ['section' => $section];
-        if (!is_null($sectionreturn)) {
-            $urlparams['sr'] = $sectionreturn;
-        }
-        $modules = $contentitemservice->get_content_items_for_user_in_course($USER, $course, $urlparams);
+            // Retrieve all modules with associated metadata.
+            $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
+            $urlparams = ['section' => $section];
+            if (!is_null($sectionreturn)) {
+                $urlparams['sr'] = $sectionreturn;
+            }
+            $modules = $contentitemservice->get_content_items_for_user_in_course($USER, $course, $urlparams);
 
-        // Return if there are no content items to add.
-        if (empty($modules)) {
-            return '';
-        }
+            // Return if there are no content items to add.
+            if (empty($modules)) {
+                return '';
+            }
 
-        // We'll sort resources and activities into two lists
-        $activities = array(MOD_CLASS_ACTIVITY => array(), MOD_CLASS_RESOURCE => array());
+            // We'll sort resources and activities into two lists.
+            $activities = array(MOD_CLASS_ACTIVITY => array(), MOD_CLASS_RESOURCE => array());
 
-        foreach ($modules as $module) {
-            $activityclass = MOD_CLASS_ACTIVITY;
-            if ($module->archetype == MOD_ARCHETYPE_RESOURCE) {
-                $activityclass = MOD_CLASS_RESOURCE;
-            } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) {
-                // System modules cannot be added by user, do not add to dropdown.
-                continue;
+            foreach ($modules as $module) {
+                $activityclass = MOD_CLASS_ACTIVITY;
+                if ($module->archetype == MOD_ARCHETYPE_RESOURCE) {
+                    $activityclass = MOD_CLASS_RESOURCE;
+                } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) {
+                    // System modules cannot be added by user, do not add to dropdown.
+                    continue;
+                }
+                $link = $module->link;
+                $activities[$activityclass][$link] = $module->title;
             }
-            $link = $module->link;
-            $activities[$activityclass][$link] = $module->title;
-        }
 
-        $straddactivity = get_string('addactivity');
-        $straddresource = get_string('addresource');
-        $sectionname = get_section_name($course, $section);
-        $strresourcelabel = get_string('addresourcetosection', null, $sectionname);
-        $stractivitylabel = get_string('addactivitytosection', null, $sectionname);
+            $straddactivity = get_string('addactivity');
+            $straddresource = get_string('addresource');
+            $sectionname = get_section_name($course, $section);
+            $strresourcelabel = get_string('addresourcetosection', null, $sectionname);
+            $stractivitylabel = get_string('addactivitytosection', null, $sectionname);
 
-        $output = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' . $section));
+            $nonajaxcontrol = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-'
+                . $section));
 
-        if (!$vertical) {
-            $output .= html_writer::start_tag('div', array('class' => 'horizontal'));
-        }
+            if (!$vertical) {
+                $nonajaxcontrol .= html_writer::start_tag('div', array('class' => 'horizontal'));
+            }
 
-        if (!empty($activities[MOD_CLASS_RESOURCE])) {
-            $select = new url_select($activities[MOD_CLASS_RESOURCE], '', array(''=>$straddresource), "ressection$section");
-            $select->set_help_icon('resources');
-            $select->set_label($strresourcelabel, array('class' => 'accesshide'));
-            $output .= $this->output->render($select);
-        }
+            if (!empty($activities[MOD_CLASS_RESOURCE])) {
+                $select = new url_select($activities[MOD_CLASS_RESOURCE], '', array('' => $straddresource), "ressection$section");
+                $select->set_help_icon('resources');
+                $select->set_label($strresourcelabel, array('class' => 'accesshide'));
+                $nonajaxcontrol .= $this->output->render($select);
+            }
 
-        if (!empty($activities[MOD_CLASS_ACTIVITY])) {
-            $select = new url_select($activities[MOD_CLASS_ACTIVITY], '', array(''=>$straddactivity), "section$section");
-            $select->set_help_icon('activities');
-            $select->set_label($stractivitylabel, array('class' => 'accesshide'));
-            $output .= $this->output->render($select);
-        }
+            if (!empty($activities[MOD_CLASS_ACTIVITY])) {
+                $select = new url_select($activities[MOD_CLASS_ACTIVITY], '', array('' => $straddactivity), "section$section");
+                $select->set_help_icon('activities');
+                $select->set_label($stractivitylabel, array('class' => 'accesshide'));
+                $nonajaxcontrol .= $this->output->render($select);
+            }
 
-        if (!$vertical) {
-            $output .= html_writer::end_tag('div');
-        }
+            if (!$vertical) {
+                $nonajaxcontrol .= html_writer::end_tag('div');
+            }
 
-        $output .= html_writer::end_tag('div');
+            $nonajaxcontrol .= html_writer::end_tag('div');
+        }
 
-        if (course_ajax_enabled($course) && $course->id == $this->page->course->id) {
-            // modchooser can be added only for the current course set on the page!
+        // The ajax control - the 'Add an activity or resource' link.
+        if ($renderajaxcontrol) {
+            // The module chooser link.
             $straddeither = get_string('addresourceoractivity');
-            // The module chooser link
-            $modchooser = html_writer::start_tag('div', array('class' => 'mdl-right'));
-            $modchooser.= html_writer::start_tag('div', array('class' => 'section-modchooser'));
+            $ajaxcontrol = html_writer::start_tag('div', array('class' => 'mdl-right'));
+            $ajaxcontrol .= html_writer::start_tag('div', array('class' => 'section-modchooser'));
             $icon = $this->output->pix_icon('t/add', '');
             $span = html_writer::tag('span', $straddeither, array('class' => 'section-modchooser-text'));
-            $modchooser .= html_writer::tag('button', $icon . $span, array(
+            $ajaxcontrol .= html_writer::tag('button', $icon . $span, array(
                     'class' => 'section-modchooser-link btn btn-link',
                     'data-action' => 'open-chooser',
                     'data-sectionid' => $section,
                 )
             );
-            $modchooser.= html_writer::end_tag('div');
-            $modchooser.= html_writer::end_tag('div');
-
-            // Wrap the normal output in a noscript div
-            $usemodchooser = get_user_preferences('usemodchooser', $CFG->modchooserdefault);
-            if ($usemodchooser) {
-                $output = html_writer::tag('div', $output, array('class' => 'hiddenifjs addresourcedropdown'));
-                $modchooser = html_writer::tag('div', $modchooser, array('class' => 'visibleifjs addresourcemodchooser'));
-            } else {
-                // If the module chooser is disabled, we need to ensure that the dropdowns are shown even if javascript is disabled
-                $output = html_writer::tag('div', $output, array('class' => 'show addresourcedropdown'));
-                $modchooser = html_writer::tag('div', $modchooser, array('class' => 'hide addresourcemodchooser'));
-            }
-            $output = $this->course_activitychooser($course->id) . $modchooser . $output;
+            $ajaxcontrol .= html_writer::end_tag('div');
+            $ajaxcontrol .= html_writer::end_tag('div');
+
+            // Load the JS for the modal.
+            $this->course_activitychooser($course->id);
         }
 
-        return $output;
+        // Behat only: If both controls are being included in the HTML,
+        // show the link by default and only fall back to the selects if js is disabled.
+        if ($behatsite && $renderajaxcontrol) {
+            $nonajaxcontrol = html_writer::tag('div', $nonajaxcontrol, array('class' => 'hiddenifjs addresourcedropdown'));
+            $ajaxcontrol = html_writer::tag('div', $ajaxcontrol, array('class' => 'visibleifjs addresourcemodchooser'));
+        }
+
+        // If behat is running, we should have the non-ajax control + the ajax control.
+        // Otherwise, we'll have one or the other.
+        return $ajaxcontrol . $nonajaxcontrol;
     }
 
     /**
@@ -927,7 +949,7 @@ class core_course_renderer extends plugin_renderer_base {
         $modicons .= $this->course_section_cm_completion($course, $completioninfo, $mod, $displayoptions);
 
         if (!empty($modicons)) {
-            $output .= html_writer::span($modicons, 'actions');
+            $output .= html_writer::div($modicons, 'actions');
         }
 
         // Show availability info (if module is not available).
similarity index 90%
rename from course/templates/chooser.mustache
rename to course/templates/activitychooser.mustache
index 3c00b54..7bd4b3d 100644 (file)
 
 <div data-region="carousel" class="carousel slide">
     <div class="carousel-inner" aria-live="polite">
-        <div class="carousel-item p-4 active" data-region="modules">
+        <div class="carousel-item px-4 py-3 active" data-region="modules">
             <div class="modchoosercontainer" aria-label="{{#str}} activitymodules, core {{/str}}">
                 <div class="searchcontainer mb-3">
-                    {{>core_course/chooser_search}}
+                    {{>core_course/local/activitychooser/search}}
                 </div>
                 <div data-region="chooser-container">
-                    <ul class="nav nav-tabs mb-2" id="activities-{{uniqid}}" role="tablist">
+                    <ul class="nav nav-tabs mb-3" id="activities-{{uniqid}}" role="tablist">
                         <li class="nav-item">
                             <a class="nav-link {{#favouritesFirst}}active{{/favouritesFirst}} {{^favourites}}d-none{{/favourites}}"
                                id="starred-tab-{{uniqid}}"
                     </ul>
                     <div class="tab-content" id="tabbed-activities-{{uniqid}}">
                         <div class="tab-pane {{#favouritesFirst}}active{{/favouritesFirst}}" id="starred-{{uniqid}}" data-region="favourites" role="tabpanel" aria-labelledby="starred-tab-{{uniqid}}">
-                            <div class="optionscontainer d-flex flex-wrap mw-100 p-3 position-relative" role="menubar" data-region="chooser-options-container" data-render="favourites-area">
-                                {{>core_course/chooser_favourites}}
+                            <div class="optionscontainer d-flex flex-wrap p-1 mw-100 position-relative" role="menubar" data-region="chooser-options-container" data-render="favourites-area">
+                                {{>core_course/local/activitychooser/favourites}}
                             </div>
                         </div>
                         <div class="tab-pane {{#recommendedFirst}}active{{/recommendedFirst}}" id="recommended-{{uniqid}}" data-region="recommended" role="tabpanel" aria-labelledby="recommended-tab-{{uniqid}}">
-                            <div class="optionscontainer d-flex flex-wrap mw-100 p-3 position-relative" role="menubar" data-region="chooser-options-container">
+                            <div class="optionscontainer d-flex flex-wrap p-1 mw-100 position-relative" role="menubar" data-region="chooser-options-container">
                                 {{#recommended}}
-                                    {{>core_course/chooser_item}}
+                                    {{>core_course/local/activitychooser/item}}
                                 {{/recommended}}
                             </div>
                         </div>
                         <div class="tab-pane {{#fallback}}active{{/fallback}}" id="all-{{uniqid}}" data-region="default" role="tabpanel" aria-labelledby="all-tab-{{uniqid}}">
-                            <div class="optionscontainer d-flex flex-wrap mw-100 p-3 position-relative" role="menubar" data-region="chooser-options-container">
+                            <div class="optionscontainer d-flex flex-wrap p-1 mw-100 position-relative" role="menubar" data-region="chooser-options-container">
                                 {{#default}}
-                                    {{>core_course/chooser_item}}
+                                    {{>core_course/local/activitychooser/item}}
                                 {{/default}}
                             </div>
                         </div>
@@ -32,5 +32,5 @@
     }
 }}
 {{#favourites}}
-    {{>core_course/chooser_item}}
+    {{>core_course/local/activitychooser/item}}
 {{/favourites}}
 <div class="optionsummary" tabindex="-1" data-region="chooser-option-summary-container" aria-labelledby="optionsummary_label" aria-describedby="optionsumary_desc">
     <div class="content text-left mb-5 px-5 py-4" data-region="chooser-option-summary-content-container">
         <div class="heading mb-4">
-            <h5 id="optionsummary_label">
+            <h5 id="optionsummary_label-{{uniqid}}" data-region="summary-header" tabindex="0">
                 {{{icon}}}
                 {{title}}
             </h5>
         </div>
-        <div id="optionsumary_desc" class="description" data-region="summary-description" tabindex="0">
+        <div id="optionsumary_desc-{{uniqid}}" class="description" tabindex="0">
             {{{help}}}
         </div>
     </div>
         "icon": "<img class='icon' src='http://urltooptionicon'>"
     }
 }}
-<div role="menuitem" tabindex="-1" aria-label="{{title}}" class="option d-block text-center py-3 px-2" data-region="chooser-option-container" data-internal="{{name}}" data-modname="{{componentname}}_{{link}}">
+<div role="menuitem" tabindex="-1" aria-label="{{title}}" class="option d-block text-center py-1 mb-1" data-region="chooser-option-container" data-internal="{{name}}" data-modname="{{componentname}}_{{link}}">
     <div class="optioninfo w-100" data-region="chooser-option-info-container">
         <a class="d-block" href="{{link}}" title="{{#str}} addnew, moodle, {{title}} {{/str}}" tabindex="-1" data-action="add-chooser-option">
             <span class="optionicon d-block">
                 {{{icon}}}
             </span>
-            <span class="optionname d-block">{{title}}</span>
+            <p class="optionname d-block mt-2 mb-0"> {{#shortentext}}20, {{title}}{{/shortentext}}</p>
         </a>
         <div class="optionactions btn-group" role="group" data-region="chooser-option-actions-container">
             {{^legacyitem}}
@@ -29,7 +29,7 @@
     <input type="text"
            data-action="search"
            id="searchinput"
-           class="form-control form-control-lg searchinput pl-3 pr-0 py-2"
+           class="form-control form-control-lg searchinput px-3 py-2"
            placeholder="{{#str}} search, core {{/str}}"
            name="search"
            autocomplete="off"
@@ -32,9 +32,9 @@
         }
     }
 }}
-<p class="mt-4 px-3">{{#str}} resultsfound, core, {{searchresultsnumber}} {{/str}}</p>
-<div class="searchresultitemscontainer d-flex flex-wrap mw-100 position-relative mt-4" role="menubar" data-region="search-result-items-container">
+<p class="mt-4 px-3 pb-1">{{#str}} resultsfound, core, {{searchresultsnumber}} {{/str}}</p>
+<div class="searchresultitemscontainer d-flex flex-wrap mw-100 position-relative p-1 mt-4" role="menubar" data-region="search-result-items-container">
     {{#searchresults}}
-        {{>core_course/chooser_item}}
+        {{>core_course/local/activitychooser/item}}
     {{/searchresults}}
 </div>
index 345413d..61466e5 100644 (file)
@@ -50,25 +50,3 @@ Feature: Add activities to courses
     And I add a "Database" to section "3" and I fill the form with:
       | Name | Test name |
     Then I should see "Required"
-
-  Scenario: Add an activity to a course with Javascript disabled
-    Then I should see "Add a resource to section 'Topic 1'"
-    And I should see "Add an activity to section 'Topic 1'"
-    And I should see "Add a resource to section 'Topic 2'"
-    And I should see "Add an activity to section 'Topic 2'"
-    And I should see "Add a resource to section 'Topic 3'"
-    And I should see "Add an activity to section 'Topic 3'"
-    And I add a "Label" to section "2"
-    And I should see "Adding a new Label to Topic 2"
-    And I set the following fields to these values:
-      | Label text | I'm a label |
-    And I press "Save and return to course"
-    And I add a "Database" to section "3"
-    And I should see "Adding a new Database to Topic 3"
-    And I set the following fields to these values:
-      | Name | Test database name |
-      | Description | Test database description |
-    And I press "Save and return to course"
-    And I should not see "Adding a new"
-    And I should see "Test database name"
-    And I should see "I'm a label"
index ed41a46..6847675 100644 (file)
@@ -250,7 +250,7 @@ class behat_course extends behat_base {
             // Clicks the selected activity if it exists.
             $activityxpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' modchooser ')]" .
                     "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' optioninfo ')]" .
-                    "/descendant::span[contains(concat(' ', normalize-space(@class), ' '), ' optionname ')]" .
+                    "/descendant::p[contains(concat(' ', normalize-space(@class), ' '), ' optionname ')]" .
                     "[normalize-space(.)=$activityliteral]" .
                     "/parent::a";
 
@@ -1339,8 +1339,8 @@ class behat_course extends behat_base {
     protected function is_course_editor() {
 
         // We don't need to behat_base::spin() here as all is already loaded.
-        if (!$this->getSession()->getPage()->findLink(get_string('turneditingoff')) &&
-                !$this->getSession()->getPage()->findLink(get_string('turneditingon'))) {
+        if (!$this->getSession()->getPage()->findButton(get_string('turneditingoff')) &&
+                !$this->getSession()->getPage()->findButton(get_string('turneditingon'))) {
             return false;
         }
 
index 2fcc9be..fef3933 100644 (file)
@@ -6945,4 +6945,78 @@ class core_course_courselib_testcase extends advanced_testcase {
         // Manager has permissions.
         $this->assertTrue(course_allowed_module($course, 'assign', $manager));
     }
+
+    /**
+     * Test the {@link average_number_of_participants()} function.
+     */
+    public function test_average_number_of_participants() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $generator = $this->getDataGenerator();
+        $now = time();
+
+        // If there are no courses, expect zero number of participants per course.
+        $this->assertEquals(0, average_number_of_participants());
+
+        $c1 = $generator->create_course();
+        $c2 = $generator->create_course();
+
+        // If there are no users, expect zero number of participants per course.
+        $this->assertEquals(0, average_number_of_participants());
+
+        $t1 = $generator->create_user(['lastlogin' => $now]);
+        $s1 = $generator->create_user(['lastlogin' => $now]);
+        $s2 = $generator->create_user(['lastlogin' => $now - WEEKSECS]);
+        $s3 = $generator->create_user(['lastlogin' => $now - WEEKSECS]);
+        $s4 = $generator->create_user(['lastlogin' => $now - YEARSECS]);
+
+        // We have courses, we have users, but no enrolments yet.
+        $this->assertEquals(0, average_number_of_participants());
+
+        // Front page enrolments are ignored.
+        $generator->enrol_user($t1->id, SITEID, 'teacher');
+        $this->assertEquals(0, average_number_of_participants());
+
+        // The teacher enrolled into one of the two courses.
+        $generator->enrol_user($t1->id, $c1->id, 'editingteacher');
+        $this->assertEquals(0.5, average_number_of_participants());
+
+        // The teacher enrolled into both courses.
+        $generator->enrol_user($t1->id, $c2->id, 'editingteacher');
+        $this->assertEquals(1, average_number_of_participants());
+
+        // Student 1 enrolled in the Course 1 only.
+        $generator->enrol_user($s1->id, $c1->id, 'student');
+        $this->assertEquals(1.5, average_number_of_participants());
+
+        // Student 2 enrolled in both courses, but the enrolment in the Course 2 not active yet (enrolment starts in the future).
+        $generator->enrol_user($s2->id, $c1->id, 'student');
+        $generator->enrol_user($s2->id, $c2->id, 'student', 'manual', $now + WEEKSECS);
+        $this->assertEquals(2.5, average_number_of_participants());
+        $this->assertEquals(2, average_number_of_participants(true));
+
+        // Student 3 enrolled in the Course 1, but the enrolment already expired.
+        $generator->enrol_user($s3->id, $c1->id, 'student', 'manual', 0, $now - YEARSECS);
+        $this->assertEquals(3, average_number_of_participants());
+        $this->assertEquals(2, average_number_of_participants(true));
+
+        // Student 4 enrolled in both courses, but the enrolment has been suspended.
+        $generator->enrol_user($s4->id, $c1->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED);
+        $generator->enrol_user($s4->id, $c2->id, 'student', 'manual', $now - DAYSECS, $now + YEARSECS, ENROL_USER_SUSPENDED);
+        $this->assertEquals(4, average_number_of_participants());
+        $this->assertEquals(2, average_number_of_participants(true));
+
+        // Consider only t1 and s1 who logged in recently.
+        $this->assertEquals(1.5, average_number_of_participants(false, $now - DAYSECS));
+
+        // Consider only t1, s1, s2 and s3 who logged in in recent weeks.
+        $this->assertEquals(3, average_number_of_participants(false, $now - 4 * WEEKSECS));
+
+        // Hidden courses are excluded from stats.
+        $DB->set_field('course', 'visible', 0, ['id' => $c1->id]);
+        $this->assertEquals(3, average_number_of_participants());
+        $this->assertEquals(1, average_number_of_participants(true));
+    }
+
 }
index f88a58b..195a6eb 100644 (file)
@@ -4,6 +4,7 @@ information provided here is intended especially for developers.
 === 3.9 ===
 
 * The function get_module_metadata is now deprecated. Please use \core_course\local\service\content_item_service instead.
+* Activity module names are now PARAM_ALPHANUM instead of PARAM_ALPHA so integers can be used in activity module names
 
 === 3.8 ===
 
index f20802e..e8fa2b9 100644 (file)
@@ -100,7 +100,7 @@ class sync_grades extends \core\task\scheduled_task {
                         }
 
                         // Need a valid context to continue.
-                        if (!$context = \context::instance_by_id($tool->contextid)) {
+                        if (!$context = \context::instance_by_id($tool->contextid, IGNORE_MISSING)) {
                             mtrace("Failed - Invalid contextid '$tool->contextid' for the tool '$tool->id'.");
                             continue;
                         }
index 1ae2fd7..a08ba79 100644 (file)
Binary files a/enrol/manual/amd/build/quickenrolment.min.js and b/enrol/manual/amd/build/quickenrolment.min.js differ
index b408f4b..d8e65c3 100644 (file)
Binary files a/enrol/manual/amd/build/quickenrolment.min.js.map and b/enrol/manual/amd/build/quickenrolment.min.js.map differ
index 980581a..74b9e21 100644 (file)
@@ -28,8 +28,9 @@ define(['core/templates',
          'core/modal_factory',
          'core/modal_events',
          'core/fragment',
+         'core/pending',
        ],
-       function(Template, $, Str, Config, Notification, ModalFactory, ModalEvents, Fragment) {
+       function(Template, $, Str, Config, Notification, ModalFactory, ModalEvents, Fragment, Pending) {
 
     /** @type {Object} The list of selectors for the quick enrolment modal. */
     var SELECTORS = {
@@ -91,6 +92,7 @@ define(['core/templates',
             });
 
             modal.getRoot().on(ModalEvents.shown, function() {
+                var pendingPromise = new Pending('enrol_manual/quickenrolment:initModal:shown');
                 var bodyPromise = this.getBody();
                 bodyPromise.then(function(html) {
                     var stringIndex = $(html).find(SELECTORS.COHORTSELECT).length ? 0 : 1;
@@ -98,7 +100,8 @@ define(['core/templates',
 
                     return;
                 })
-                .fail(Notification.exception);
+                .then(pendingPromise.resolve)
+                .catch(Notification.exception);
 
                 modal.setBody(bodyPromise);
             }.bind(this));
index d0236b7..c9403be 100644 (file)
@@ -184,7 +184,7 @@ class favourite_repository implements favourite_repository_interface {
     /**
      * Return all items matching the supplied criteria (a [key => value,..] list).
      *
-     * @param array $criteria the list of key/value criteria pairs.
+     * @param array $criteria the list of key/value(s) criteria pairs.
      * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point.
      * @param int $limitnum optional pagination control for returning a subset comprising this many records.
      * @return array the list of favourites matching the criteria.
@@ -192,7 +192,22 @@ class favourite_repository implements favourite_repository_interface {
      */
     public function find_by(array $criteria, int $limitfrom = 0, int $limitnum = 0) : array {
         global $DB;
-        $records = $DB->get_records($this->favouritetable, $criteria, '', '*', $limitfrom, $limitnum);
+        $conditions = [];
+        $params = [];
+        foreach ($criteria as $field => $value) {
+            if (is_array($value) && count($value)) {
+                list($insql, $inparams) = $DB->get_in_or_equal($value, SQL_PARAMS_NAMED);
+                $conditions[] = "$field $insql";
+                $params = array_merge($params, $inparams);
+            } else {
+                $conditions[] = "$field = :$field";
+                $params = array_merge($params, [$field => $value]);
+            }
+        }
+
+        $records = $DB->get_records_select($this->favouritetable, implode(' AND ', $conditions), $params,
+            '', '*', $limitfrom, $limitnum);
+
         return $this->get_list_of_favourites_from_records($records);
     }
 
index e569523..0246373 100644 (file)
@@ -110,6 +110,38 @@ class user_favourite_service {
         );
     }
 
+    /**
+     * Find a list of favourites, by multiple types within a component.
+     *
+     * E.g. "Find all favourites in the activity chooser" might result in:
+     * $favcourses = find_all_favourites('core_course', ['contentitem_mod_assign','contentitem_mod_assignment');
+     *
+     * @param string $component the frankenstyle component name.
+     * @param array $itemtypes optional the type of the favourited item.
+     * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point.
+     * @param int $limitnum optional pagination control for returning a subset comprising this many records.
+     * @return array the list of favourites found.
+     * @throws \moodle_exception if the component name is invalid, or if the repository encounters any errors.
+     */
+    public function find_all_favourites(string $component, array $itemtypes = [], int $limitfrom = 0, int $limitnum = 0) : array {
+        if (!in_array($component, \core_component::get_component_names())) {
+            throw new \moodle_exception("Invalid component name '$component'");
+        }
+        $params = [
+            'userid' => $this->userid,
+            'component' => $component,
+        ];
+        if ($itemtypes) {
+            $params['itemtype'] = $itemtypes;
+        }
+
+        return $this->repo->find_by(
+            $params,
+            $limitfrom,
+            $limitnum
+        );
+    }
+
     /**
      * Returns the SQL required to include favourite information for a given component/itemtype combination.
      *
index 7a4fa27..b95451a 100644 (file)
@@ -302,6 +302,16 @@ class favourite_repository_testcase extends advanced_testcase {
         );
         $favouritesrepo->add($favourite);
 
+        // Add another favourite.
+        $favourite = new favourite(
+            'core_course',
+            'course_item',
+            $course1context->instanceid,
+            $course1context->id,
+            $user1context->instanceid
+        );
+        $favouritesrepo->add($favourite);
+
         // From the repo, get the list of favourites for the 'core_course/course' area.
         $userfavourites = $favouritesrepo->find_by(['component' => 'core_course', 'itemtype' => 'course']);
         $this->assertIsArray($userfavourites);
@@ -311,6 +321,16 @@ class favourite_repository_testcase extends advanced_testcase {
         $userfavourites = $favouritesrepo->find_by(['component' => 'core_cannibalism', 'itemtype' => 'course']);
         $this->assertIsArray($userfavourites);
         $this->assertCount(0, $userfavourites);
+
+        // From the repo, get the list of favourites for the 'core_course/course' area when passed as an array.
+        $userfavourites = $favouritesrepo->find_by(['component' => 'core_course', 'itemtype' => ['course']]);
+        $this->assertIsArray($userfavourites);
+        $this->assertCount(1, $userfavourites);
+
+        // From the repo, get the list of favourites for the 'core_course' area given multiple item_types.
+        $userfavourites = $favouritesrepo->find_by(['component' => 'core_course', 'itemtype' => ['course', 'course_item']]);
+        $this->assertIsArray($userfavourites);
+        $this->assertCount(2, $userfavourites);
     }
 
     /**
index bc172af..be372f1 100644 (file)
@@ -85,11 +85,29 @@ class user_favourite_service_testcase extends advanced_testcase {
         $mockrepo->expects($this->any())
             ->method('find_by')
             ->will($this->returnCallback(function(array $criteria, int $limitfrom = 0, int $limitnum = 0) use (&$mockstore) {
+                // Check for single value key pair vs multiple.
+                $multipleconditions = [];
+                foreach ($criteria as $key => $value) {
+                    if (is_array($value)) {
+                        $multipleconditions[$key] = $value;
+                        unset($criteria[$key]);
+                    }
+                }
+
                 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria.
                 foreach ($mockstore as $index => $mockrow) {
                     $mockrowarr = (array)$mockrow;
                     if (array_diff_assoc($criteria, $mockrowarr) == []) {
-                        $returns[$index] = $mockrow;
+                        $found = true;
+                        foreach ($multipleconditions as $key => $value) {
+                            if (!in_array($mockrowarr[$key], $value)) {
+                                $found = false;
+                                break;
+                            }
+                        }
+                        if ($found) {
+                            $returns[$index] = $mockrow;
+                        }
                     }
                 }
                 // Return a subset of the records, according to the paging options, if set.
@@ -235,6 +253,42 @@ class user_favourite_service_testcase extends advanced_testcase {
         $this->assertEquals($fav2->id, $favourites[$fav2->id]->id);
     }
 
+    /**
+     * Test fetching favourites for single user, by area.
+     */
+    public function test_find_all_favourites() {
+        list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
+
+        // Get a user_favourite_service for the user.
+        $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB.
+        $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo);
+
+        // Favourite 2 courses, in separate areas.
+        $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context);
+        $fav2 = $service->create_favourite('core_course', 'anothertype', $course2context->instanceid, $course2context);
+        $fav3 = $service->create_favourite('core_course', 'yetanothertype', $course2context->instanceid, $course2context);
+
+        // Verify we can get favourites by area.
+        $favourites = $service->find_all_favourites('core_course', ['course']);
+        $this->assertIsArray($favourites);
+        $this->assertCount(1, $favourites); // We only get favourites for the 'core_course/course' area.
+        $this->assertEquals($fav1->id, $favourites[$fav1->id]->id);
+
+        $favourites = $service->find_all_favourites('core_course', ['course', 'anothertype']);
+        $this->assertIsArray($favourites);
+        // We only get favourites for the 'core_course/course' and 'core_course/anothertype area.
+        $this->assertCount(2, $favourites);
+        $this->assertEquals($fav1->id, $favourites[$fav1->id]->id);
+        $this->assertEquals($fav2->id, $favourites[$fav2->id]->id);
+
+        $favourites = $service->find_all_favourites('core_course');
+        $this->assertIsArray($favourites);
+        $this->assertCount(3, $favourites); // We only get favourites for the 'core_cours' area.
+        $this->assertEquals($fav2->id, $favourites[$fav2->id]->id);
+        $this->assertEquals($fav1->id, $favourites[$fav1->id]->id);
+        $this->assertEquals($fav3->id, $favourites[$fav3->id]->id);
+    }
+
     /**
      * Make sure the find_favourites_by_type() method only returns favourites for the scoped user.
      */
index 8b6a15e..62e15b6 100644 (file)
@@ -114,8 +114,10 @@ class core_files_renderer extends plugin_renderer_base {
                 array('unknownoriginal', 'repository'), array('confirmdeletefolder', 'repository'),
                 array('confirmdeletefilewithhref', 'repository'), array('confirmrenamefolder', 'repository'),
                 array('confirmrenamefile', 'repository'), array('newfolder', 'repository'), array('edit', 'moodle'),
-                ['nofilesselected', 'repository'], ['confirmdeleteselectedfile', 'repository'],
-                ['selectall', 'moodle'], ['deselectall', 'moodle'], ['selectallornone', 'form'],
+                array('originalextensionchange', 'repository'), array('originalextensionremove', 'repository'),
+                array('aliaseschange', 'repository'), ['nofilesselected', 'repository'],
+                ['confirmdeleteselectedfile', 'repository'], ['selectall', 'moodle'], ['deselectall', 'moodle'],
+                ['selectallornone', 'form'],
             )
         );
         if ($this->page->requires->should_create_one_time_item_now('core_file_managertemplate')) {
index 97c4a3b..734f1c1 100644 (file)
@@ -45,5 +45,17 @@ function xmldb_filter_displayh5p_upgrade($oldversion) {
     // Automatically generated Moodle v3.8.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2020031700) {
+        // References to h5p.org has to be removed as default value for the allowedsources in the filter because H5P is going
+        // to close it down completely so that only the author can see the test content.
+        $h5porgurl = 'https://h5p.org/h5p/embed/[id]';
+        $config = get_config('filter_displayh5p', 'allowedsources');
+        if (strpos($config, $h5porgurl) !== false) {
+            set_config('allowedsources', str_replace($h5porgurl, '', $config), 'filter_displayh5p');
+        }
+
+        upgrade_plugin_savepoint(true, 2020031700, 'filter', 'displayh5p');
+    }
+
     return true;
 }
index 9e2bae7..8d85c1c 100644 (file)
@@ -23,6 +23,8 @@
 
 defined('MOODLE_INTERNAL') || die;
 
+use core_h5p\local\library\autoloader;
+
 /**
  * Display H5P filter
  *
@@ -74,6 +76,7 @@ class filter_displayh5p extends moodle_text_filter {
         $specialchars = ['?', '&'];
         $escapedspecialchars = ['\?', '&amp;'];
         $h5pcontents = array();
+        $h5plinks = array();
 
         // Check all allowed sources.
         foreach ($allowedsources as $source) {
@@ -82,7 +85,8 @@ class filter_displayh5p extends moodle_text_filter {
 
             if (($source == $localsource)) {
                 $params['tagbegin'] = '<iframe src="'.$CFG->wwwroot.'/h5p/embed.php?url=';
-                $ultimatepattern = '#'.$source.'#';
+                $escapechars = $source;
+                $ultimatepattern = $source;
             } else {
                 if (!stripos($source, 'embed')) {
                     $params['urlmodifier'] = '/embed';
@@ -90,7 +94,7 @@ class filter_displayh5p extends moodle_text_filter {
                 // Convert special chars.
                 $sourceid = str_replace('[id]', '[0-9]+', $source);
                 $escapechars = str_replace($specialchars, $escapedspecialchars, $sourceid);
-                $ultimatepattern = '#(' . $escapechars . ')#';
+                $ultimatepattern = '(' . $escapechars . ')';
             }
 
             // Improve performance creating filterobjects only when needed.
@@ -101,15 +105,39 @@ class filter_displayh5p extends moodle_text_filter {
             $h5pcontenturl = new filterobject($source, null, null, false,
                 false, null, [$this, 'filterobject_prepare_replacement_callback'], $params);
 
-            $h5pcontenturl->workregexp = $ultimatepattern;
+            $h5pcontenturl->workregexp = '#'.$ultimatepattern.'#';
             $h5pcontents[] = $h5pcontenturl;
+
+            // Regex to find h5p extensions in an <a> tag.
+            $linkregexp = '~<a [^>]*href=["\']('.$escapechars.'[^"\']*)["\'][^>]*>([^<]*)</a>~is';
+
+            $h5plinkurl = new filterobject($linkregexp, null, null, false,
+                false, null, [$this, 'filterobject_prepare_replacement_callback'], $params);
+            $h5plinkurl->workregexp = $linkregexp;
+            $h5plinks[] = $h5plinkurl;
         }
 
-        if (empty($h5pcontents)) {
+        if (empty($h5pcontents) && empty($h5links)) {
             // No matches to deal with.
             return $text;
         }
 
+        // Apply filter inside <a> tag href attribute.
+        // We can not use filter_phrase function because it removes all tags and can not be applied in tag attributes.
+        foreach ($h5plinks as $h5plink) {
+            $text = preg_replace_callback($h5plink->workregexp,
+                function ($matches) use ($h5plink) {
+                    if ($matches[1] == $matches[2]) {
+                        filter_prepare_phrase_for_replacement($h5plink);
+
+                        return str_replace('$1', $matches[1], $h5plink->workreplacementphrase);
+                    } else {
+                        return $matches[0];
+                    }
+                }, $text);
+
+        }
+
         $result = filter_phrases($text, $h5pcontents, null, null, false, true);
 
         // Encoding H5P file URLs.
@@ -151,7 +179,7 @@ class filter_displayh5p extends moodle_text_filter {
 
         // We want to request the resizing script only once.
         if (self::$loadresizerjs) {
-            $resizerurl = new moodle_url('/lib/h5p/js/h5p-resizer.js');
+            $resizerurl = autoloader::get_h5p_core_library_url('js/h5p-resizer.js');
             $tagend .= '<script src="' . $resizerurl->out() . '"></script>';
             self::$loadresizerjs = false;
         }
index dfb1f12..d8fc431 100644 (file)
@@ -27,6 +27,10 @@ defined('MOODLE_INTERNAL') || die;
 $string['allowedsourceslist'] = 'Allowed sources';
 $string['allowedsourceslistdesc'] = 'A list of URLs from which users can embed H5P content. If none are specified, all URLs will remain as links and not be displayed as embedded H5P content.
 
-\'[id]\' is a placeholder for the H5P content ID in the external source.';
+\'[id]\' is a placeholder for the H5P content ID in the external source.
+For example:
+
+- H5P.com: https://[xxxxxx].h5p.com/content/[id]
+- Wordpress: http://myserver/wp-admin/admin-ajax.php?action=h5p_embed&id=[id]';
 $string['filtername'] = 'Display H5P';
 $string['privacy:metadata'] = 'The display H5P filter does not store any personal data.';
index 3bed622..329155a 100644 (file)
@@ -30,5 +30,5 @@ if ($ADMIN->fulltree) {
             get_string('allowedsourceslist',
             'filter_displayh5p'),
             get_string('allowedsourceslistdesc', 'filter_displayh5p'),
-            "https://h5p.org/h5p/embed/[id]"));
+            ''));
 }
index ff62186..df8f220 100644 (file)
@@ -35,13 +35,25 @@ Feature: Render H5P content using filters
     Then I should see "Lorum ipsum"
 
   @javascript
-  Scenario: Add an external H5P content URL in a link. Shouldn't be rendered.
+  Scenario: Add an external H5P content URL in a link with the URL. Should be rendered.
     Given I log in as "teacher1"
     And I am on "Course 1" course homepage
     And I follow "PageName1"
     And I navigate to "Edit settings" in current page administration
 #   This content won't be displayed, so this scenario shouldn't be labeled as external.
-    And I set the field "Page content" to "<a href='https://moodle.h5p.com/content/1290772960722742119/embed'>Go to https://moodle.h5p.com/content/1290772960722742119/embed</a>"
+    And I set the field "Page content" to "<a href='https://moodle.h5p.com/content/1290772960722742119/embed'>https://moodle.h5p.com/content/1290772960722742119/embed</a>"
+    When I click on "Save and display" "button"
+    And I wait until the page is ready
+    And I switch to "h5p-iframe" class iframe
+    Then I should see "Lorum ipsum"
+
+  Scenario: Add an external H5P content URL in a link with text. Shouldn't be rendered.
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I follow "PageName1"
+    And I navigate to "Edit settings" in current page administration
+#   This content won't be displayed, so this scenario shouldn't be labeled as external.
+    And I set the field "Page content" to "<a href='https://moodle.h5p.com/content/1290772960722742119/embed'>Here you are the content</a>"
     When I click on "Save and display" "button"
     And I wait until the page is ready
     Then ".h5p-iframe" "css_element" should not exist
@@ -53,7 +65,7 @@ Feature: Render H5P content using filters
     And I add a "File" to section "1"
     And I set the following fields to these values:
       | Name                      | ipsumFile     |
-    And I upload "filter/displayh5p/tests/fixtures/ipsums.h5p" file to "Select files" filemanager
+    And I upload "h5p/tests/fixtures/ipsums.h5p" file to "Select files" filemanager
     And I press "Save and return to course"
     And I follow "PageName1"
     And I navigate to "Edit settings" in current page administration
@@ -90,7 +102,7 @@ Feature: Render H5P content using filters
     And I add a "File" to section "1"
     And I set the following fields to these values:
       | Name                      | ipsumFile     |
-    And I upload "filter/displayh5p/tests/fixtures/ipsums.h5p" file to "Select files" filemanager
+    And I upload "h5p/tests/fixtures/ipsums.h5p" file to "Select files" filemanager
     And I press "Save and return to course"
     And I follow "PageName1"
     And I navigate to "Edit settings" in current page administration
@@ -115,7 +127,7 @@ Feature: Render H5P content using filters
     And I add a "File" to section "1"
     And I set the following fields to these values:
       | Name                      | ipsumFileTeacher     |
-    And I upload "filter/displayh5p/tests/fixtures/ipsums.h5p" file to "Select files" filemanager
+    And I upload "h5p/tests/fixtures/ipsums.h5p" file to "Select files" filemanager
     And I press "Save and return to course"
     And I follow "PageName1"
     And I navigate to "Edit settings" in current page administration
@@ -139,7 +151,7 @@ Feature: Render H5P content using filters
     And I add a "File" to section "1"
     And I set the following fields to these values:
       | Name                      | ipsumFile     |
-    And I upload "filter/displayh5p/tests/fixtures/ipsums.h5p" file to "Select files" filemanager
+    And I upload "h5p/tests/fixtures/ipsums.h5p" file to "Select files" filemanager
     And I press "Save and return to course"
     And I follow "PageName2"
     And I navigate to "Edit settings" in current page administration
index 8ace093..467118c 100644 (file)
@@ -42,7 +42,7 @@ class filter_displayh5p_testcase extends advanced_testcase {
         $this->resetAfterTest(true);
 
         set_config('allowedsources',
-            "https://h5p.org/h5p/embed/[id]\nhttps://moodle.h5p.com/content/[id]/embed\nhttps://moodle.h5p.com/content/[id]
+            "https://moodle.h5p.com/content/[id]/embed\nhttps://moodle.h5p.com/content/[id]
                 \nhttps://generic.wordpress.soton.ac.uk/altc/wp-admin/admin-ajax.php?action=h5p_embed&id=[id]",
             'filter_displayh5p');
     }
@@ -74,16 +74,20 @@ class filter_displayh5p_testcase extends advanced_testcase {
         return [
             ["http:://example.com", "#http:://example.com#"],
             ["http://google.es/h5p/embed/3425234", "#http://google.es/h5p/embed/3425234#"],
-            ["https://h5p.org/h5p/embed/547225", "#<iframe src=\"https://h5p.org/h5p/embed/547225\"[^>]+?>#"],
             ["https://moodle.h5p.com/content/1290729733828858779/embed", "#<iframe src=\"https://moodle.h5p.com/content/1290729733828858779/embed\"[^>]+?>#"],
             ["https://moodle.h5p.com/content/1290729733828858779", "#<iframe src=\"https://moodle.h5p.com/content/1290729733828858779/embed\"[^>]+?>#"],
-            ["<a href=\"https://h5p.org/h5p/embed/547225\">link</a>",  "#^((?!iframe).)*$#"],
-            ["this is a text with an h5p url https://h5p.org/h5p/embed/547225 inside",
-                    "#this is a text with an h5p url <iframe src=\"https://h5p.org/h5p/embed/547225\"(.|\n)*> inside#"],
+            ["<a href=\"https://moodle.h5p.com/content/1290848995208939539/embed\">https://moodle.h5p.com/content/1290848995208939539/embed</a>",
+                "#<iframe src=\"https://moodle.h5p.com/content/1290848995208939539/embed\"[^>]+?>#"],
+            ["<a href=\"https://moodle.org\">https://moodle.h5p.com/content/1290848995208939539/embed</a>",
+                "#^((?!iframe).)*$#"],
+            ["<a href=\"https://moodle.h5p.com/content/1290848995208939539/embed\">link</a>",  "#^((?!iframe).)*$#"],
+            ["this is a text with an h5p url https://moodle.h5p.com/content/1290848995208939539/embed inside",
+                    "#this is a text with an h5p url <iframe src=\"https://moodle.h5p.com/content/1290848995208939539/embed\"(.|\n)*> inside#"],
             ["https://generic.wordpress.soton.ac.uk/altc/wp-admin/admin-ajax.php?action=h5p_embed&amp;id=13",
                     "#<iframe src=\"https://generic.wordpress.soton.ac.uk/altc/wp-admin/admin-ajax.php\?action=h5p_embed\&amp\;id=13\"[^>]+?>#"],
-            ["https://h5p.org/h5p/embed/547225 another content in the same page https://moodle.h5p.com/content/1290729733828858779/embed",
-                    "#<iframe src=\"https://h5p.org/h5p/embed/547225\"[^>]+?>((?!<iframe).)*<iframe src=\"https://moodle.h5p.com/content/1290729733828858779/embed\"[^>]+?>#"],
+            ["https://moodle.h5p.com/content/1290848995208939539/embed another content in the same page https://moodle.h5p.com/content/1290729733828858779/embed",
+                    "#<iframe src=\"https://moodle.h5p.com/content/1290848995208939539/embed\"[^>]+?>((?!<iframe).)*".
+                    "<iframe src=\"https://moodle.h5p.com/content/1290729733828858779/embed\"[^>]+?>#"],
             [$CFG->wwwroot."/pluginfile.php/5/user/private/interactive-video.h5p?export=1&embed=1",
                     "#<iframe src=\"{$CFG->wwwroot}/h5p/embed.php\?url=".rawurlencode("{$CFG->wwwroot}/pluginfile.php/5/user/private/interactive-video.h5p").
                     "&export=1&embed=1\"[^>]*?></iframe>#"],
index 88a3038..e936737 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die;
 
-$plugin->version  = 2019111800;
+$plugin->version  = 2020031700;
 $plugin->requires = 2019111200;
 $plugin->component = 'filter_displayh5p';
diff --git a/grade/report/history/db/upgrade.php b/grade/report/history/db/upgrade.php
new file mode 100644 (file)
index 0000000..d2c6135
--- /dev/null
@@ -0,0 +1,47 @@
+<?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/>.
+
+/**
+ * Grade overview report upgrade steps.
+ *
+ * @package    gradereport_history
+ * @copyright  2020 Michael Hawkins <michaelh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Function to upgrade grade history report.
+ *
+ * @param int $oldversion the version we are upgrading from
+ * @return bool result
+ */
+function xmldb_gradereport_history_upgrade($oldversion) {
+
+    if ($oldversion < 2019111801) {
+        $perpageconfig = get_config('moodle', 'grade_report_historyperpage');
+
+        // For existing installations with a non-integer 'per page' config, update the value to the default.
+        if (!empty($perpageconfig) && filter_var($perpageconfig, FILTER_VALIDATE_INT) === false) {
+            set_config('grade_report_historyperpage', 50);
+        }
+
+        upgrade_plugin_savepoint(true, 2019111801, 'gradereport', 'history');
+    }
+
+    return true;
+}
index c841c80..3653f07 100644 (file)
@@ -30,7 +30,8 @@ if ($ADMIN->fulltree) {
     $settings->add(new admin_setting_configtext('grade_report_historyperpage',
         new lang_string('historyperpage', 'gradereport_history'),
         new lang_string('historyperpage_help', 'gradereport_history'),
-        50
+        50,
+        PARAM_INT
     ));
 
 }
index b19225b..885b84a 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2019111800;
+$plugin->version   = 2019111801;
 $plugin->requires  = 2019111200;
 $plugin->component = 'gradereport_history';
diff --git a/h5p/classes/autoloader.php b/h5p/classes/autoloader.php
deleted file mode 100644 (file)
index 3e8daea..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?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/>.
-
-/**
- * H5P Autoloader.
- *
- * @package    core_h5p
- * @copyright  2019 Mihail Geshoski <mihail@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-namespace core_h5p;
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * H5P Autoloader.
- *
- * @package    core_h5p
- * @copyright  2019 Mihail Geshoski <mihail@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class autoloader {
-    public static function register(): void {
-        spl_autoload_register([self::class, 'autoload']);
-    }
-
-    public static function autoload($classname): void {
-        global $CFG;
-
-        $classes = [
-            'H5PCore' => '/lib/h5p/h5p.classes.php',
-            'H5PHubEndpoints' => '/lib/h5p/h5p.classes.php',
-            'H5PFrameworkInterface' => '/lib/h5p/h5p.classes.php',
-            'H5PContentValidator' => 'lib/h5p/h5p.classes.php',
-            'H5PValidator' => '/lib/h5p/h5p.classes.php',
-            'H5PStorage' => '/lib/h5p/h5p.classes.php',
-            'H5PDevelopment' => '/lib/h5p/h5p-development.class.php',
-            'H5PFileStorage' => '/lib/h5p/h5p-file-storage.interface.php',
-            'H5PMetadata' => '/lib/h5p/h5p-metadata.class.php',
-        ];
-
-        if (isset($classes[$classname])) {
-            require_once("{$CFG->dirroot}{$classes[$classname]}");
-        }
-    }
-}
index 1458372..3c15d85 100644 (file)
@@ -32,6 +32,7 @@ use H5PCore;
 use H5PFrameworkInterface;
 use stdClass;
 use moodle_url;
+use core_h5p\local\library\autoloader;
 
 /**
  * H5P core class, containing functions and storage shared by the other H5P classes.
@@ -138,20 +139,24 @@ class core extends \H5PCore {
     }
 
     /**
-     * Get core JavaScript files.
+     * Get the list of JS scripts to include on the page.
      *
      * @return array The array containg urls of the core JavaScript files
      */
     public static function get_scripts(): array {
-        global $CFG;
-        $cachebuster = '?ver='.$CFG->jsrev;
-        $liburl = $CFG->wwwroot . '/lib/h5p/';
-        $urls = [];
+        global $PAGE;
 
+        $factory = new factory();
+        $jsrev = $PAGE->requires->get_jsrev();
+        $urls = [];
         foreach (self::$scripts as $script) {
-            $urls[] = new moodle_url($liburl . $script . $cachebuster);
+            $urls[] = autoloader::get_h5p_core_library_url($script, [
+                'ver' => $jsrev,
+            ]);
         }
-        $urls[] = new moodle_url("/h5p/js/h5p_overrides.js");
+        $urls[] = new moodle_url("/h5p/js/h5p_overrides.js", [
+            'ver' => $jsrev,
+        ]);
 
         return $urls;
     }
index 52c8cc0..f115179 100644 (file)
@@ -27,11 +27,12 @@ namespace core_h5p;
 
 defined('MOODLE_INTERNAL') || die();
 
-use \core_h5p\framework as framework;
-use \core_h5p\core as core;
-use \H5PStorage as storage;
-use \H5PValidator as validator;
-use \H5PContentValidator as content_validator;
+use core_h5p\local\library\autoloader;
+use core_h5p\framework;
+use core_h5p\core;
+use H5PStorage as storage;
+use H5PValidator as validator;
+use H5PContentValidator as content_validator;
 
 /**
  * H5P factory class.
@@ -43,6 +44,9 @@ use \H5PContentValidator as content_validator;
  */
 class factory {
 
+    /** @var \core_h5p\local\library\autoloader The autoloader */
+    protected $autoloader;
+
     /** @var \core_h5p\core The Moodle H5PCore implementation */
     protected $core;
 
@@ -63,9 +67,19 @@ class factory {
      */
     public function __construct() {
         // Loading classes we need from H5P third party library.
+        $this->autoloader = new autoloader();
         autoloader::register();
     }
 
+    /**
+     * Returns an instance of the \core_h5p\local\library\autoloader class.
+     *
+     * @return \core_h5p\local\library\autoloader
+     */
+    public function get_autoloader(): autoloader {
+        return $this->autoloader;
+    }
+
     /**
      * Returns an instance of the \core_h5p\framework class.
      *
index d413745..1aaadb3 100644 (file)
@@ -107,6 +107,26 @@ class helper {
         return $core->getStorableDisplayOptions($disableoptions, 0);
     }
 
+    /**
+     * Convert the int representation of display options into stdClass
+     *
+     * @param core $core The \core_h5p\core object
+     * @param int $displayint integer value representing display options
+     *
+     * @return int The representation of display options as int
+     */
+    public static function decode_display_options(core $core, int $displayint = null): \stdClass {
+        $config = new \stdClass();
+        if ($displayint === null) {
+            $displayint = self::get_display_options($core, $config);
+        }
+        $displayarray = $core->getDisplayOptionsForEdit($displayint);
+        $config->export = $displayarray[core::DISPLAY_OPTION_DOWNLOAD] ?? 0;
+        $config->embed = $displayarray[core::DISPLAY_OPTION_EMBED] ?? 0;
+        $config->copyright = $displayarray[core::DISPLAY_OPTION_COPYRIGHT] ?? 0;
+        return $config;
+    }
+
     /**
      * Checks if the author of the .h5p file is "trustable". If the file hasn't been uploaded by a user with the
      * required capability, the content won't be deployed.
diff --git a/h5p/classes/local/library/autoloader.php b/h5p/classes/local/library/autoloader.php
new file mode 100644 (file)
index 0000000..76ef5c9
--- /dev/null
@@ -0,0 +1,124 @@
+<?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/>.
+
+/**
+ * H5P autoloader management class.
+ *
+ * @package    core_h5p
+ * @copyright  2019 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_h5p\local\library;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * H5P autoloader management class.
+ *
+ * @package    core_h5p
+ * @copyright  2019 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class autoloader {
+
+    /**
+     * Returns the list of plugins that can work as H5P library handlers (have class PLUGINNAME\local\library\handler)
+     * @return array with the format: pluginname => class
+     */
+    public static function get_all_handlers(): array {
+        $handlers = [];
+        foreach (\core_component::get_plugin_types() as $ptype => $unused) {
+            $plugins = \core_component::get_plugin_list_with_class($ptype, 'local\library\handler') +
+                \core_component::get_plugin_list_with_class($ptype, 'local_library_handler');
+            // Allow plugins to have the class either with namespace or without (useful for unittest).
+            foreach ($plugins as $pname => $class) {
+                $handlers[$pname] = $class;
+            }
+        }
+
+        return $handlers;
+    }
+
+    /**
+     * Returns the default H5P library handler.
+     * @return string|null H5P library handler class
+     */
+    public static function get_default_handler(): ?string {
+        $default = null;
+        $handlers = self::get_all_handlers();
+        if (!empty($handlers)) {
+            // The default handler will be the first in the list.
+            $keys = array_keys($handlers);
+            $default = array_shift($keys);
+        }
+
+        return $default;
+    }
+
+    /**
+     * Returns the current H5P library handler class.
+     *
+     * @return string H5P library handler class
+     * @throws \moodle_exception
+     */
+    public static function get_handler_classname(): string {
+        global $CFG;
+
+        $handlers = self::get_all_handlers();
+        if (!empty($CFG->h5plibraryhandler)) {
+            if (isset($handlers[$CFG->h5plibraryhandler])) {
+                return $handlers[$CFG->h5plibraryhandler];
+            }
+        }
+
+        // If no handler has been defined or it doesn't exist, return the default one.
+        $defaulthandler = self::get_default_handler();
+        if (empty($defaulthandler)) {
+            // If there is no default handler, throw an exception.
+            throw new \moodle_exception('noh5plibhandlerdefined', 'core_h5p');
+        }
+
+        return $defaulthandler;
+    }
+
+    /**
+     * Get the current version of the H5P core library.
+     *
+     * @return string
+     */
+    public static function get_h5p_version(): string {
+        return component_class_callback(self::get_handler_classname(), 'get_h5p_version', []);
+    }
+
+    /**
+     * Get a URL for the current H5P Core Library.
+     *
+     * @param string $filepath The path within the h5p root
+     * @param array $params these params override current params or add new
+     * @return null|moodle_url
+     */
+    public static function get_h5p_core_library_url(?string $filepath = null, ?array $params = null): ?\moodle_url {
+        return component_class_callback(self::get_handler_classname(), 'get_h5p_core_library_url', [$filepath, $params]);
+    }
+
+    /**
+     * Register the H5P autoloader.
+     */
+    public static function register(): void {
+        component_class_callback(self::get_handler_classname(), 'register', []);
+    }
+}
diff --git a/h5p/classes/local/library/handler.php b/h5p/classes/local/library/handler.php
new file mode 100644 (file)
index 0000000..9bb4864
--- /dev/null
@@ -0,0 +1,118 @@
+<?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 library handlers.
+ *
+ * @package    core_h5p
+ * @copyright  2019 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_h5p\local\library;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Base class for library handlers.
+ *
+ * If a new H5P libraries handler plugin has to be created, it has to define class
+ * PLUGINNAME\local\library\handler that extends \core_h5p\local\library\handler.
+ *
+ * @package    core_h5p
+ * @copyright  2019 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class handler {
+
+    /**
+     * Get the current version of the H5P core library.
+     *
+     * @return string
+     */
+    abstract public static function get_h5p_version(): string;
+
+    /**
+     * Get the base path for the H5P Libraries.
+     *
+     * @return null|string
+     */
+    public static function get_h5p_library_base(): ?string {
+        $h5pversion = static::get_h5p_version();
+        return "/h5p/h5plib/v{$h5pversion}/joubel";
+    }
+
+    /**
+     * Get the base path for the current H5P Core Library.
+     *
+     * @param string $filepath The path within the H5P root
+     * @return null|string
+     */
+    public static function get_h5p_core_library_base(?string $filepath = null): ?string {
+        return static::get_h5p_library_base() . "/core/{$filepath}";
+    }
+
+    /**
+     * Register the H5P autoloader.
+     */
+    public static function register(): void {
+        spl_autoload_register([static::class, 'autoload']);
+    }
+
+    /**
+     * SPL Autoloading function for H5P.
+     *
+     * @param string $classname The name of the class to load
+     */
+    public static function autoload($classname): void {
+        global $CFG;
+
+        $classes = static::get_class_list();
+
+        if (isset($classes[$classname])) {
+            require_once($CFG->dirroot . static::get_h5p_core_library_base($classes[$classname]));
+        }
+    }
+
+    /**
+     * Get a URL for the current H5P Core Library.
+     *
+     * @param string $filepath The path within the h5p root
+     * @param array $params these params override current params or add new
+     * @return null|moodle_url
+     */
+    public static function get_h5p_core_library_url(?string $filepath = null, ?array $params = null): ?\moodle_url {
+        return new \moodle_url(static::get_h5p_core_library_base($filepath), $params);
+    }
+
+    /**
+     * Return the list of classes with their location within the joubel directory.
+     *
+     * @return array
+     */
+    protected static function get_class_list(): array {
+        return [
+            'H5PCore' => 'h5p.classes.php',
+            'H5PFrameworkInterface' => 'h5p.classes.php',
+            'H5PContentValidator' => 'h5p.classes.php',
+            'H5PValidator' => 'h5p.classes.php',
+            'H5PStorage' => 'h5p.classes.php',
+            'H5PDevelopment' => 'h5p-development.class.php',
+            'H5PFileStorage' => 'h5p-file-storage.interface.php',
+            'H5PMetadata' => 'h5p-metadata.class.php',
+        ];
+    }
+}
index f9d32ab..9a86b44 100644 (file)
@@ -26,6 +26,8 @@ namespace core_h5p;
 
 defined('MOODLE_INTERNAL') || die();
 
+use core_h5p\local\library\autoloader;
+
 /**
  * H5P player class, for displaying any local H5P content.
  *
@@ -121,6 +123,38 @@ class player {
         }
     }
 
+    /**
+     * Get the encoded URL for embeding this H5P content.
+     *
+     * @param string $url Local URL of the H5P file to display.
+     * @param stdClass $config Configuration for H5P buttons.
+     * @param bool $preventredirect Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions
+     *
+     * @return string The embedable code to display a H5P file.
+     */
+    public static function display(string $url, \stdClass $config, bool $preventredirect = true): string {
+        global $OUTPUT;
+        $params = [
+                'url' => $url,
+                'preventredirect' => $preventredirect,
+            ];
+
+        $optparams = ['frame', 'export', 'embed', 'copyright'];
+        foreach ($optparams as $optparam) {
+            if (!empty($config->$optparam)) {
+                $params[$optparam] = $config->$optparam;
+            }
+        }
+        $fileurl = new \moodle_url('/h5p/embed.php', $params);
+
+        $template = new \stdClass();
+        $template->embedurl = $fileurl->out(false);
+
+        $result = $OUTPUT->render_from_template('core_h5p/h5pembed', $template);
+        $result .= self::get_resize_code();
+        return $result;
+    }
+
     /**
      * Get the error messages stored in our H5P framework.
      *
@@ -165,7 +199,7 @@ class player {
             'exportUrl'       => ($exporturl instanceof \moodle_url) ? $exporturl->out(false) : '',
             'embedCode'       => $this->get_embed_code($this->url->out(),
                 $displayoptions[ core::DISPLAY_OPTION_EMBED ]),
-            'resizeCode'      => $this->get_resize_code(),
+            'resizeCode'      => self::get_resize_code(),
             'title'           => $this->content['slug'],
             'displayOptions'  => $displayoptions,
             'url'             => self::get_embed_url($this->url->out())->out(),
@@ -598,13 +632,15 @@ class player {
         $cachebuster = $this->get_cache_buster();
 
         // Use relative URL to support both http and https.
-        $liburl = $CFG->wwwroot . '/lib/h5p/';
+        $liburl = autoloader::get_h5p_core_library_url()->out();
         $relpath = '/' . preg_replace('/^[^:]+:\/\/[^\/]+\//', '', $liburl);
 
         // Add core stylesheets.
         foreach (core::$styles as $style) {
             $settings['core']['styles'][] = $relpath . $style . $cachebuster;
-            $this->cssrequires[] = new \moodle_url($liburl . $style . $cachebuster);
+            $this->cssrequires[] = autoloader::get_h5p_core_library_url($style, [
+                'ver' => $cachebuster,
+            ]);
         }
         // Add core JavaScript.
         foreach (core::get_scripts() as $script) {
@@ -687,7 +723,7 @@ class player {
             'crossorigin' => null,
             'libraryConfig' => $this->core->h5pF->getLibraryConfig(),
             'pluginCacheBuster' => $this->get_cache_buster(),
-            'libraryUrl' => $basepath . 'lib/h5p/js',
+            'libraryUrl' => autoloader::get_h5p_core_library_url('js'),
             'moodleLibraryPaths' => $this->core->get_dependency_roots($this->h5pid),
         );
 
@@ -711,11 +747,11 @@ class player {
      *
      * @return string The HTML code with the resize script.
      */
-    private function get_resize_code(): string {
+    private static function get_resize_code(): string {
         global $OUTPUT;
 
         $template = new \stdClass();
-        $template->resizeurl = new \moodle_url('/lib/h5p/js/h5p-resizer.js');
+        $template->resizeurl = autoloader::get_h5p_core_library_url('js/h5p-resizer.js');
 
         return $OUTPUT->render_from_template('core_h5p/h5presize', $template);
     }
diff --git a/h5p/h5plib/v124/classes/local/library/handler.php b/h5p/h5plib/v124/classes/local/library/handler.php
new file mode 100644 (file)
index 0000000..f9a7851
--- /dev/null
@@ -0,0 +1,46 @@
+<?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/>.
+
+/**
+ * Handler for the version 1.24 of the H5P library.
+ *
+ * @package    h5plib_v124
+ * @copyright  2019 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace h5plib_v124\local\library;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Handler for the version 1.24 of the H5P library.
+ *
+ * @package    h5plib_v124
+ * @copyright  2019 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class handler extends \core_h5p\local\library\handler {
+
+    /**
+     * Get the current version of the H5P core library.
+     *
+     * @return string
+     */
+    public static function get_h5p_version(): string {
+        return '124';
+    }
+}
diff --git a/h5p/h5plib/v124/classes/privacy/provider.php b/h5p/h5plib/v124/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..b6c8242
--- /dev/null
@@ -0,0 +1,45 @@
+<?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/>.
+
+/**
+ * Privacy provider implementation for the version 1.24 of the H5P library.
+ *
+ * @package    h5plib_v124
+ * @copyright  2020 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace h5plib_v124\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy provider implementation for the version 1.24 of the H5P library.
+ *
+ * @copyright  2020 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
similarity index 99%
rename from lib/h5p/h5p.classes.php
rename to h5p/h5plib/v124/joubel/core/h5p.classes.php
index 7654b2d..7b78d06 100644 (file)
@@ -3215,21 +3215,23 @@ class H5PCore {
    * @return string
    */
   private static function hashToken($action, $time_factor) {
-    if (!isset($_SESSION['h5p_token'])) {
+    global $SESSION;
+
+    if (!isset($SESSION->h5p_token)) {
       // Create an unique key which is used to create action tokens for this session.
       if (function_exists('random_bytes')) {
-        $_SESSION['h5p_token'] = base64_encode(random_bytes(15));
+        $SESSION->h5p_token = base64_encode(random_bytes(15));
       }
       else if (function_exists('openssl_random_pseudo_bytes')) {
-        $_SESSION['h5p_token'] = base64_encode(openssl_random_pseudo_bytes(15));
+        $SESSION->h5p_token = base64_encode(openssl_random_pseudo_bytes(15));
       }
       else {
-        $_SESSION['h5p_token'] = uniqid('', TRUE);
+        $SESSION->h5p_token = uniqid('', TRUE);
       }
     }
 
     // Create hash and return
-    return substr(hash('md5', $action . $time_factor . $_SESSION['h5p_token']), -16, 13);
+    return substr(hash('md5', $action . $time_factor . $SESSION->h5p_token), -16, 13);
   }
 
   /**
similarity index 82%
rename from lib/h5p/readme_moodle.txt
rename to h5p/h5plib/v124/joubel/core/readme_moodle.txt
index 846d9da..85463e4 100644 (file)
@@ -16,8 +16,7 @@ Added:
 
 Downloaded version: 1.24 release
 
-
-=== 3.8 ===
+Changes:
 1. In order to allow the dependency path to be overridden by child H5PCore classes, a couple of minor changes have been added to the
 h5p.classes.php file:
     - Into the getDependenciesFiles method, the line 2435:
@@ -44,6 +43,12 @@ and 1 ocurrence in h5p-metadata.class.php.
 
 3. Another PR has been sent to H5P library (https://github.com/h5p/h5p-php-library/pull/69) to fix some php74 minor problems. The same fix is being applied locally by MDL-67077. Once we import a new version, if it includes de fix, this won't be needed to reapply and can be removed.
 
+4. Replace the $_SESSION references to $SESSION. That implies that the information is saved to backends, so only the Moodle one should be used by core (core should be free from $_SESSION and always use $SESSION).
+h5p.classes.php file:
+  - Into hashToken method:
+    Declare the global $SESSION.
+    Change all the $_SESSION by $SESSION.
+A script for testing this part can be found in MDL-68068
 
 The point 2 from above won't be needed once the mbstring extension becomes mandatory in Moodle. A request has been
-sent to MDL-65809.
+sent to MDL-65809.
\ No newline at end of file
diff --git a/h5p/h5plib/v124/lang/en/h5plib_v124.php b/h5p/h5plib/v124/lang/en/h5plib_v124.php
new file mode 100644 (file)
index 0000000..ca5ea1a
--- /dev/null
@@ -0,0 +1,29 @@
+<?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/>.
+
+/**
+ * Strings for component 'h5p_v124'
+ *
+ * @package    h5plib_v124
+ * @copyright  2019 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['pluginname'] = 'H5P framework v1.24';
+$string['pluginname_help'] = 'H5P framework. Version 1.24';
+$string['privacy:metadata'] = 'H5P framework v1.24 do not store any personal data.';
diff --git a/h5p/h5plib/v124/thirdpartylibs.xml b/h5p/h5plib/v124/thirdpartylibs.xml
new file mode 100644 (file)
index 0000000..000c977
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<libraries>
+  <library>
+    <location>joubel/core</location>
+    <name>h5p-php-library</name>
+    <license>GPL-3.0</license>
+    <version>1.24</version>
+  </library>
+</libraries>
diff --git a/h5p/h5plib/v124/version.php b/h5p/h5plib/v124/version.php
new file mode 100644 (file)
index 0000000..1e450cc
--- /dev/null
@@ -0,0 +1,29 @@
+<?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/>.
+
+/**
+ * Version information.
+ *
+ * @package   h5plib_v124
+ * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version   = 2019121300;       // The current module version (Date: YYYYMMDDXX)
+$plugin->requires  = 2019051100;       // Requires this Moodle version
+$plugin->component = 'h5plib_v124';    // Full name of the plugin (used for diagnostics).
index deccba4..b640925 100644 (file)
@@ -23,6 +23,8 @@
  */
 defined('MOODLE_INTERNAL') || die();
 
+use core_h5p\local\library\autoloader;
+
 /**
  * Serve the files from the core_h5p file areas.
  *
@@ -44,7 +46,7 @@ function core_h5p_pluginfile($course, $cm, $context, string $filearea, array $ar
     global $DB;
 
     // Require classes from H5P third party library
-    \core_h5p\autoloader::register();
+    autoloader::register();
 
     $filesettingsset = false;
 
index 9f26126..a080840 100644 (file)
@@ -28,5 +28,7 @@
     }
 
 }}
-
-<iframe src="{{embedurl}}" width=":w" height=":h" allowfullscreen="allowfullscreen"></iframe>
\ No newline at end of file
+<iframe src="{{embedurl}}" name="h5player" width=":w" height=":h"
+   allowfullscreen="allowfullscreen" class="h5p-player w-100 border-0"
+   style="min-height: 230px;">
+</iframe>
index 5159b7e..c2c65f5 100644 (file)
@@ -26,7 +26,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-use core_h5p\autoloader;
+use core_h5p\local\library\autoloader;
 
 /**
  * Tests for h5p deleted event.
index 451ba4e..75a7671 100644 (file)
@@ -26,7 +26,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-use core_h5p\autoloader;
+use core_h5p\local\library\autoloader;
 
 /**
  * Tests for h5p viewed event.
index b9ae3ca..737c7cc 100644 (file)
@@ -33,7 +33,7 @@ require_once($CFG->dirroot . '/webservice/tests/helpers.php');
 
 use core_h5p\external;
 use core_h5p\file_storage;
-use core_h5p\autoloader;
+use core_h5p\local\library\autoloader;
 
 /**
  * Core h5p external functions tests
index 5f4654e..5d7ee9f 100644 (file)
@@ -23,7 +23,7 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-use core_h5p\autoloader;
+use core_h5p\local\library\autoloader;
 use core_h5p\core;
 
 defined('MOODLE_INTERNAL') || die();
index 118484c..60fd74f 100644 (file)
@@ -25,7 +25,7 @@
 
 namespace core_h5p;
 
-use core_h5p\autoloader;
+use core_h5p\local\library\autoloader;
 
 defined('MOODLE_INTERNAL') || die();
 
index 648b64e..a814c44 100644 (file)
@@ -25,6 +25,8 @@
 
 namespace core_h5p;
 
+use core_h5p\local\library\autoloader;
+
 defined('MOODLE_INTERNAL') || die();
 
 /**
index d6199e4..fa83984 100644 (file)
@@ -26,7 +26,7 @@
 namespace core_h5p\local\tests;
 
 use core_h5p\file_storage;
-use core_h5p\autoloader;
+use core_h5p\local\library\autoloader;
 use core_h5p\helper;
 use file_archive;
 use zip_archive;
@@ -617,4 +617,4 @@ class h5p_file_storage_testcase extends \advanced_testcase {
             ],
         ];
     }
-}
\ No newline at end of file
+}
index 6158c53..c216a2c 100644 (file)
@@ -41,14 +41,14 @@ class helper_testcase extends \advanced_testcase {
     /**
      * Test the behaviour of get_display_options().
      *
-     * @dataProvider get_display_options_provider
+     * @dataProvider display_options_provider
      * @param  bool   $frame     Whether the frame should be displayed or not
      * @param  bool   $export    Whether the export action button should be displayed or not
      * @param  bool   $embed     Whether the embed action button should be displayed or not
      * @param  bool   $copyright Whether the copyright action button should be displayed or not
      * @param  int    $expected The expectation with the displayoptions value
      */
-    public function test_get_display_options(bool $frame, bool $export, bool $embed, bool $copyright, int $expected): void {
+    public function test_display_options(bool $frame, bool $export, bool $embed, bool $copyright, int $expected): void {
         $this->setRunTestInSeparateProcess(true);
         $this->resetAfterTest();
 
@@ -60,9 +60,16 @@ class helper_testcase extends \advanced_testcase {
             'embed' => $embed,
             'copyright' => $copyright,
         ];
-        $displayoptions = helper::get_display_options($core, $config);
 
+        // Test getting display options.
+        $displayoptions = helper::get_display_options($core, $config);
         $this->assertEquals($expected, $displayoptions);
+
+        // Test decoding display options.
+        $decoded = helper::decode_display_options($core, $expected);
+        $this->assertEquals($decoded->export, $config->export);
+        $this->assertEquals($decoded->embed, $config->embed);
+        $this->assertEquals($decoded->copyright, $config->copyright);
     }
 
     /**
@@ -70,7 +77,7 @@ class helper_testcase extends \advanced_testcase {
      *
      * @return array
      */
-    public function get_display_options_provider(): array {
+    public function display_options_provider(): array {
         return [
             'All display options disabled' => [
                 false,
diff --git a/h5p/upgrade.txt b/h5p/upgrade.txt
new file mode 100644 (file)
index 0000000..e5060d4
--- /dev/null
@@ -0,0 +1,8 @@
+This files describes API changes in core libraries and APIs,
+information provided here is intended especially for developers.
+
+=== 3.9 ===
+* A new plugintype has been created, h5plib, for having installed more
+than one H5P library version.
+* H5P third-party libraries have been moved from /lib/h5p to h5p/h5plib/v124,
+as an h5plib plugintype.
index 8e0b686..e7dc1b5 100644 (file)
@@ -45,7 +45,7 @@ $string['datarootpermission'] = 'Zugriffsrechte zum Datenverzeichnis';
 $string['dbprefix'] = 'Tabellen-Prefix';
 $string['dirroot'] = 'Moodle-Verzeichnis';
 $string['environmenthead'] = 'Installationsvoraussetzungen werden geprüft ...';
-$string['environmentsub2'] = 'Jede Version hat Mindestvoraussetzungen für der PHP-Version und für verbindliche PHP-Extensions. Vor einer Installation oder einer Aktualisierung wird eine vollständige Prüfung durchgeführt. Bitte fragen Sie den Administrator des Servers, wenn Sie mit der Installation einer neuen Version oder mit der Aktivierung von PHP-Extensions nicht weiterkommen.';
+$string['environmentsub2'] = 'Jede Version hat Mindestvoraussetzungen für der PHP-Version und für verbindliche PHP-Extensions. Vor einer Installation oder einer Aktualisierung wird eine vollständige Prüfung durchgeführt. Bitte fragen Sie die Administrator/innen des Servers, wenn Sie mit der Installation einer neuen Version oder mit der Aktivierung von PHP-Extensions nicht weiterkommen.';
 $string['errorsinenvironment'] = 'Fehler bei der Prüfung der Systemvoraussetzungen!';
 $string['installation'] = 'Installation';
 $string['langdownloaderror'] = 'Das Sprachpaket \'{$a}\' konnte nicht heruntergeladen werden. Die Installation wird in englischer Sprache fortgesetzt.';
@@ -63,7 +63,7 @@ $string['pathserrcreatedataroot'] = 'Das Datenverzeichnis ({$a->dataroot}) kann
 $string['pathshead'] = 'Pfade bestätigen';
 $string['pathsrodataroot'] = 'Das Verzeichnis dataroot ist schreibgeschützt.';
 $string['pathsroparentdataroot'] = 'Das Verzeichnis ({$a->parent}) ist schreibgeschützt. Deswegen kann das Datenverzeichnis ({$a->dataroot})  vom Installer nicht angelegt werden.';
-$string['pathssubadmindir'] = 'Einige Webserver benutzen /admin als speziellen Link, um auf Einstellungsseiten oder Ähnliches zu verweisen. Unglücklicherweise kollidiert dies mit dem standardmäßigen Verzeichnis für die Moodle-Administration. Sie können dieses Problem beheben, indem Sie das Verzeichnis admin in Ihrer Moodle-Installation umbenennen und den neuen Namen hier eingeben (z.B. <em>moodleadmin</em>). Mit dieser Änderung werden alle Admin-Links korrigiert.';
+$string['pathssubadmindir'] = 'Einige Webserver benutzen /admin als speziellen Link, um auf Einstellungsseiten oder Ähnliches zu verweisen. Unglücklicherweise kollidiert dies mit dem Verzeichnis für die Moodle-Administration. Sie können dieses Problem beheben, indem Sie das Verzeichnis admin in Ihrer Moodle-Installation umbenennen und den neuen Namen hier eingeben (z.B. <em>moodleadmin</em>). Mit dieser Änderung werden alle Admin-Links korrigiert.';
 $string['pathssubdataroot'] = '<p>Sie benötigen einen Platz, wo Moodle hochgeladene Dateien abspeichern kann. </p><p>Dieses Verzeichnis muss Lese- und Schreibrechte für das Nutzerkonto besitzen, mit dem Ihr Webservers läuft (üblicherweise \'nobody\', \'apache\' oder \'www-data).</p><p>  Außerdem sollte das Verzeichnis nicht direkt aus dem Internet erreichbar sein. </p><p>Das Intallationsskript wird versuchen, ein solches Verzeichnis zu erstellen, falls es nicht existiert.</p>';
 $string['pathssubdirroot'] = '<p>Vollständiger Pfad der Moodle-Installation.</p>';
 $string['pathssubwwwroot'] = '<p>Webadresse, die ein Nutzer im Browser eingibt, um auf Moodle zuzugreifen.</p><p> Es ist nicht möglich, über unterschiedliche Adressen auf Moodle zuzugreifen. Sollte Ihre Website mehrere öffentliche Adressen verwenden, so müssen Sie eine Adresse festlegen und für die übrigen Adressen dauerhafte Weiterleitungen dorthin einrichten.</p>
index dccc3eb..d6e32e9 100644 (file)
@@ -35,11 +35,11 @@ $string['cliansweryes'] = 'ν';
 $string['cliincorrectvalueerror'] = 'Σφάλμα, λανθασμένη τιμή «{$a->value}» για το «{$a->option}»';
 $string['cliincorrectvalueretry'] = 'Λανθασμένη τιμή. Παρακαλούμε προσπαθήστε ξανά.';
 $string['clitypevalue'] = 'πληκτρολογήστε μια τιμή';
-$string['clitypevaluedefault'] = 'πληκτρολογήστε μια τιμή· πατήστε Enter για να χρησιμοποιήσετε τηνπροεπιλεγμένη τιμή ({$a})';
+$string['clitypevaluedefault'] = 'πληκτρολογήστε μια τιμή· πατήστε Enter για να χρησιμοποιήσετε την προεπιλεγμένη τιμή ({$a})';
 $string['cliunknowoption'] = 'Μη αναγνωρίσιμες επιλογές:
  {$a}<br />
 Παρακαλούμε χρησιμοποιήστε την επιλογή --help (βοήθεια)';
 $string['cliyesnoprompt'] = 'πατήστε y (σημαίνει yes=ναι) ή πατήστε n (σημαίνει no=όχι)';
-$string['environmentrequireinstall'] = 'απαιτείται να εγκατασταθεί/ ενεργοποιηθεί';
+$string['environmentrequireinstall'] = 'απαιτείται να εγκατασταθεί και να ενεργοποιηθεί';
 $string['environmentrequireversion'] = 'απαιτείται η έκδοση {$a->needed} ενώ εσείς έχετε την {$a->current}';
 $string['upgradekeyset'] = 'Κλειδί αναβάθμισης (αφήστε κενό για να μην το ορίσετε)';
index 2966927..77160aa 100644 (file)
@@ -30,7 +30,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['cannotcreatedboninstall'] = '<p>Αδυναμία δημιουργίας βάσης δεδομένων.</p> <p>Η βάση δεδομένων που προσδιορίστηκε δεν υπάρχει και ο χρήστης που δόθηκε δεν έχει δικαίωμα να δημιουργήσει την βάση δεδομένων.</p> <p>Ο διαχειριστής του ιστοτόπου πρέπει να επαληθεύσει τις ρυθμίσεις της βάσης δεδομένων.</p>';
+$string['cannotcreatedboninstall'] = '<p>Αδυναμία δημιουργίας βάσης δεδομένων.</p><p>Η βάση δεδομένων που προσδιορίστηκε δεν υπάρχει και ο χρήστης που δόθηκε δεν έχει δικαίωμα να δημιουργήσει την βάση δεδομένων.</p><p>Ο διαχειριστής του ιστοτόπου πρέπει να επαληθεύσει τις ρυθμίσεις της βάσης δεδομένων.</p>';
 $string['cannotcreatelangdir'] = 'Δε δημιουργήθηκε φάκελος γλώσσας.';
 $string['cannotcreatetempdir'] = 'Αδυναμία δημιουργίας προσωρινού φακέλου';
 $string['cannotdownloadcomponents'] = 'Δεν μπορεί να γίνει λήψη των στοιχείων λογισμικού';
@@ -44,7 +44,7 @@ $string['dmlexceptiononinstall'] = '<p>Παρουσιάστηκε κάποιο 
 $string['downloadedfilecheckfailed'] = 'Αποτυχία ελέγχου αρχείου που έγινε λήψη';
 $string['invalidmd5'] = 'Η μεταβλητή ελέγχου ήταν λανθασμένη - δοκιμάστε ξανά';
 $string['missingrequiredfield'] = 'Κάποιο απαιτούμενο πεδίο λείπει';
-$string['remotedownloaderror'] = '<p>Η λήψη του στοιχείου λογισμικού στον εξυπηρετητή σας απέτυχε. Παρακαλούμε επαληθεύστε τις ρυθμίσεις του διακομιστή μεσολάβησης (proxy)· η επέκταση PHP cURL συνιστάται θερμά.</p><br /><p>Πρέπει να κατεβάσετε το αρχείο <a href="{$a->url}">{$a->url}</a> χειροκίνητα, να το αντιγράψετε στο «{$a->dest}» στον εξυπηρετητή σας και να το αποσυμπιέσετε εκεί.</p>';
+$string['remotedownloaderror'] = '<p>Η λήψη του στοιχείου λογισμικού στον εξυπηρετητή σας απέτυχε. Παρακαλούμε επαληθεύστε τις ρυθμίσεις του διακομιστή μεσολάβησης· η επέκταση PHP cURL συνιστάται ιδιαίτερα.</p><br /><p>Πρέπει να κατεβάσετε το αρχείο <a href="{$a->url}">{$a->url}</a> χειροκίνητα, να το αντιγράψετε στο «{$a->dest}» στον εξυπηρετητή σας και να το αποσυμπιέσετε εκεί.</p>';
 $string['wrongdestpath'] = 'Λανθασμένο μονοπάτι προορισμού.';
 $string['wrongsourcebase'] = 'Λανθασμένη βάση πηγής URL.';
-$string['wrongzipfilename'] = 'Λανθασμένo όνομα αρχείου ZIP.';
+$string['wrongzipfilename'] = 'Λανθασμένο όνομα αρχείου ZIP.';
index aa518e4..b810035 100644 (file)
@@ -54,7 +54,7 @@ $string['memorylimithelp'] = '<p>Το όριο μνήμης της PHP στον
 
 <p>Αυτό μπορεί να προκαλέσει προβλήματα μνήμης στο Moodle στη συνέχεια, ειδικά αν έχετε πολλά ενεργοποιημένα αρθρώματα και/ή πολλούς χρήστες.</p>
 
-<p>ΠÏ\81οÏ\84είνεται η ρύθμιση της PHP με μεγαλύτερο όριο, αν αυτό είναι δυνατό, π.χ. 40M. Υπάρχουν πολλοί τρόποι να το κάνετε αυτό, τους οποίους μπορείτε να δοκιμάσετε:</p>
+<p>ΣÏ\85νιÏ\83Ï\84άται η ρύθμιση της PHP με μεγαλύτερο όριο, αν αυτό είναι δυνατό, π.χ. 40M. Υπάρχουν πολλοί τρόποι να το κάνετε αυτό, τους οποίους μπορείτε να δοκιμάσετε:</p>
 <ol>
 <li>Αν έχετε τη δυνατότητα, κάνετε επαναμεταγλώττιση της PHP με την παράμετρο <i>--enable-memory-limit</i>. Αυτό θα επιτρέψει στο Moodle να ορίσει μόνο του το όριο μνήμης.</li>
 <li>Αν έχετε πρόσβαση στο αρχείο php.ini, μπορείτε να αλλάξετε τη ρύθμιση <b>memory_limit</b> σε 40M. Αν δεν έχετε πρόσβαση ζητήστε από το διαχειριστή να το κάνει για εσάς.</li>
@@ -62,13 +62,13 @@ $string['memorylimithelp'] = '<p>Το όριο μνήμης της PHP στον
 <blockquote>php_value memory_limit 40M</div></blockquote>
 <p>Ωστόσο, σε κάποιους εξυπηρετητές αυτό θα εμποδίσει τη λειτουργία <b>όλων</b> των σελίδων PHP (θα βλέπετε σφάλματα όταν ανοίγετε τις σελίδες), οπότε θα πρέπει να διαγράψετε το αρχείο .htaccess.</p></li>
 </ol>';
-$string['paths'] = 'Î\94ιαδÏ\81ομέÏ\82';
-$string['pathserrcreatedataroot'] = 'Ο Φάκελος δεδομένων ({$a->dataroot}) δεν μπορεί να δημιουργθεί από το πρόγραμμα εγκατάστασης.';
-$string['pathshead'] = 'Î\95Ï\80ιβεβαίÏ\89Ï\83η Î\94ιαδÏ\81ομών';
+$string['paths'] = 'Î\9cονοÏ\80άÏ\84ια';
+$string['pathserrcreatedataroot'] = 'Ο φάκελος δεδομένων ({$a->dataroot}) δεν μπορεί να δημιουργηθεί από το πρόγραμμα εγκατάστασης.';
+$string['pathshead'] = 'Î\95Ï\80ιβεβαίÏ\89Ï\83η Î¼Î¿Î½Î¿Ï\80αÏ\84ιών';
 $string['pathsrodataroot'] = 'Ο Φάκελος Δεδομένων δεν είναι εγγράψιμος.';
-$string['pathsroparentdataroot'] = 'Ο γονικός φάκελος ({$a->parent}) δεν είναι εγγράψιμος. Ο φάκελος δεδομένων ({$a->dataroot}) δεν μπορεί να δημιουργθεί από το πρόγραμμα εγκατάστασης.';
+$string['pathsroparentdataroot'] = 'Î\9f Î³Î¿Î½Î¹ÎºÏ\8cÏ\82 Ï\86άκελοÏ\82 ({$a->parent}) Î´ÎµÎ½ ÎµÎ¯Î½Î±Î¹ ÎµÎ³Î³Ï\81άÏ\88ιμοÏ\82. Î\9f Ï\86άκελοÏ\82 Î´ÎµÎ´Î¿Î¼Î­Î½Ï\89ν ({$a->dataroot}) Î´ÎµÎ½ Î¼Ï\80οÏ\81εί Î½Î± Î´Î·Î¼Î¹Î¿Ï\85Ï\81γηθεί Î±Ï\80Ï\8c Ï\84ο Ï\80Ï\81Ï\8cγÏ\81αμμα ÎµÎ³ÎºÎ±Ï\84άÏ\83Ï\84αÏ\83ηÏ\82.';
 $string['pathssubadmindir'] = 'Κάποιοι λίγοι κεντρικοί υπολογιστές ιστού χρησιμοποιούν το /admin ως ειδική διεύθυνση URL για την πρόσβαση σε κάποιο πίνακα ελέγχου ή κάτι τέτοιο. Δυστυχώς αυτό έρχεται σε αντίθεση με την τυπική τοποθεσία των σελίδων διαχείρισης (admin) του Moodle. Αυτό μπορεί να διορθωθεί με την μετονομασία του admin φακέλου στην εγκατάστασή σας, και βάζοντας αυτό το καινούργιο όνομα εδώ. Για παράδειγμα: <em>moodleadmin</em>. Αυτό θα διορθώσει όλους τους συνδέσμους με το admin στην διεύθυνσή τους σε όλη την εγκατάσταση του Moodle σας.';
-$string['pathssubdataroot'] = '<p>Ένας φάκελος όπου το Moodle θα αποθηκεύει όλα τα ανεβασμένα από τους χρήστες αρχεία.</p> <p>Αυτος ο φάκελος θα πρέπει να είναι αναγνώσιμος ΚΑΙ ΕΓΓΡΑΨΙΜΟΣ από τον χρήστη του εξυπηρετητή ιστού (συνήθως «nobody» ή «apache»).</p> <p>Δεν πρέπει να είναι προσβάσιμος κατευθείαν από τον ιστό.</p> <p>Αν ο φάκελος δεν υπάρχει, η διαδικασία εγκατάστασης θα προσπαθήσει να τον δημιουργήσει.</p>';
+$string['pathssubdataroot'] = '<p>Ένας φάκελος όπου το Moodle θα αποθηκεύει όλα τα ανεβασμένα από τους χρήστες αρχεία.</p><p>Αυτος ο φάκελος θα πρέπει να είναι αναγνώσιμος ΚΑΙ ΕΓΓΡΑΨΙΜΟΣ από τον χρήστη του εξυπηρετητή ιστού (συνήθως «nobody» ή «apache»).</p><p>Δεν πρέπει να είναι προσβάσιμος κατευθείαν από τον ιστό.</p><p>Αν ο φάκελος δεν υπάρχει, η διαδικασία εγκατάστασης θα προσπαθήσει να τον δημιουργήσει.</p>';
 $string['pathssubdirroot'] = '<p>Η πλήρης διαδρομή του φακέλου που περιέχει τα αρχεία κώδικα του Moodle.</p>';
 $string['pathssubwwwroot'] = '<p>Η πλήρης διεύθυνση από την οποία θα γίνεται η πρόσβαση στο Moodle, δηλαδή η διεύθυνση που οι χρήστες θα εισάγουν στην γραμμή διεύθυνσης του περιηγητή, για να έχουν πρόσβαση στου Moodle.</p>
 <p>Δεν είναι δυνατόν να έχετε πρόβαση στο Moodle χρησιμοποιώντας πολλαπλές διευθύνσεις. Εάν ο ιστότοπος θα είναι προσβάσιμος μέσω πολλαπλών διευθύνσεων τότε επιλέξτε την ευκολότερη και εγκαταστήστε μια μόνιμη ανακατεύθυνση για καθεμία από τις άλλες διευθύνσεις.</p>
index 6375cf9..d1735c7 100644 (file)
@@ -34,4 +34,4 @@ $string['language'] = 'Γλώσσα';
 $string['moodlelogo'] = 'Λογότυπο Moodle';
 $string['next'] = 'Επόμενο';
 $string['previous'] = 'Προηγούμενο';
-$string['reload'] = 'Eπαναφόρτωση';
+$string['reload'] = 'Επαναφόρτωση';
index 61f701b..5d5524e 100644 (file)
@@ -34,7 +34,7 @@ $string['admindirname'] = 'Pasta de administração';
 $string['availablelangs'] = 'Pacotes linguísticos disponíveis';
 $string['chooselanguagehead'] = 'Selecione um idioma';
 $string['chooselanguagesub'] = 'Selecione o idioma a utilizar durante a instalação. Poderá depois selecionar outro(s) idioma(s) para o site e para os utilizadores.';
-$string['clialreadyconfigured'] = 'O ficheiro config.php já existe, use admin/cli/install_database.php para instalar o Moodle para este site.';
+$string['clialreadyconfigured'] = 'O ficheiro config.php já existe. Use \'admin/cli/install_database.php\' para instalar o Moodle para este site.';
 $string['clialreadyinstalled'] = 'O ficheiro config.php já existe, use admin/cli/install_database.php para atualizar o Moodle para este site.';
 $string['cliinstallheader'] = 'Programa para instalação do Moodle <b>{$a}</b> através da linha de comandos';
 $string['databasehost'] = 'Servidor da base de dados';
index 629745d..d4a8f67 100644 (file)
@@ -538,6 +538,8 @@ $string['enableglobalsearch_desc'] = 'If enabled, data will be indexed and synch
 $string['enablegravatar'] = 'Enable Gravatar';
 $string['enablegravatar_help'] = 'When enabled Moodle will attempt to fetch a user profile picture from Gravatar if the user has not uploaded an image.';
 $string['enablemobilewebservice'] = 'Enable web services for mobile devices';
+$string['enablemoodlenet'] = 'Enable integration with MoodleNet instances';
+$string['enablemoodlenet_desc'] = 'If enabled, and provided the MoodleNet plugin is installed, users can import content from MoodleNet into this site.';
 $string['enablerecordcache'] = 'Enable record cache';
 $string['enablerssfeeds'] = 'Enable RSS feeds';
 $string['enablesafebrowserintegration'] = 'Enable Safe Exam Browser integration';
index 355f523..c1a4494 100644 (file)
@@ -91,11 +91,14 @@ $string['h5p'] = 'H5P';
 $string['h5ptitle'] = 'Visit h5p.org to check out more content.';
 $string['h5pfilenotfound'] = 'H5P file not found';
 $string['h5pinvalidurl'] = 'Invalid H5P content URL.';
+$string['h5plibraryhandler'] = 'H5P framework handler';
+$string['h5plibraryhandler_help'] = 'The H5P framework used to display any H5P content.';
 $string['h5pprivatefile'] = 'This H5P content can\'t be displayed because you don\'t have access to the .h5p file.';
 $string['h5pmanage'] = 'Manage H5P content types';
 $string['h5poverview'] = 'H5P overview';
 $string['h5ppackage'] = 'H5P content type';
 $string['h5ppackage_help'] = 'An H5P content type is a file with an H5P or ZIP extension containing all libraries required to display the content.';
+$string['h5psettings'] = 'H5P settings';
 $string['hideadvanced'] = 'Hide advanced';
 $string['installedcontentlibraries'] = 'Installed H5P libraries';
 $string['installedcontenttypes'] = 'Installed H5P content types';
@@ -145,8 +148,9 @@ $string['missingmbstring'] = 'The mbstring PHP extension is not loaded. It is re
 $string['missinguploadpermissions'] = 'Note that the libraries may exist in the file you uploaded, but you\'re not allowed to upload new libraries. Please contact your administrator.';
 $string['nocopyright'] = 'No copyright information available for this content.';
 $string['noextension'] = 'The file you uploaded is not a valid HTML5 Package. (It doesn\'t have the .h5p file extension.)';
-$string['nopermissiontodeploy'] = 'This file can\'t be displayed because it has been uploaded by a user without the required capability to deploy H5P content.';
+$string['noh5plibhandlerdefined'] = 'There isn\'t any H5P framework handler installed, so H5P content can\'t be displayed.';
 $string['nojson'] = 'The main h5p.json file is not valid';
+$string['nopermissiontodeploy'] = 'This file can\'t be displayed because it has been uploaded by a user without the required capability to deploy H5P content.';
 $string['notrustablefile'] = 'This file can\'t be displayed because it has been uploaded by a user without the capability to update H5P content types.  Please contact your administrator to ask for the content type to be installed.';
 $string['nounzip'] = 'The file you uploaded is not a valid HTML5 Package. (It is not possible to unzip it.)';
 $string['offlineDialogBody'] = 'We were unable to send information about your completion of this task. Please check your internet connection.';
index 3df03ba..f931fd9 100644 (file)
@@ -22,6 +22,8 @@
  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
+$string['activeparticipantnumberaverage'] = 'Average number of recently active participants ({$a})';
+$string['activeusersnumber'] = 'Number of recently active users ({$a})';
 $string['analyticsactions'] = 'Number of actions taken on generated predictions ({$a})';
 $string['analyticsactionsnotuseful'] = 'Number of actions marking a prediction as not useful ({$a})';
 $string['analyticsenabledmodels'] = 'Number of enabled prediction models ({$a})';
index 39595d0..bec67cc 100644 (file)
@@ -1291,6 +1291,7 @@ $string['moodleversion'] = 'Moodle version';
 $string['moodlerelease'] = 'Moodle release';
 $string['more'] = 'more';
 $string['morehelp'] = 'More help';
+$string['morehelpaboutmodule'] = 'More help about the {$a} module';
 $string['moreinfo'] = 'More info';
 $string['moreinformation'] = 'More information about this error';
 $string['moreprofileinfoneeded'] = 'Please tell us more about yourself';
index 94c5616..caaf671 100644 (file)
@@ -149,6 +149,8 @@ $string['type_gradereport'] = 'Gradebook report';
 $string['type_gradereport_plural'] = 'Gradebook reports';
 $string['type_gradingform'] = 'Advanced grading method';
 $string['type_gradingform_plural'] = 'Advanced grading methods';
+$string['type_h5plib'] = 'H5P framework';
+$string['type_h5plib_plural'] = 'H5P frameworks';
 $string['type_mlbackend'] = 'Machine learning backend';
 $string['type_mlbackend_plural'] = 'Machine learning backends';
 $string['type_local'] = 'Local plugin';
index 3485ec6..bc6d094 100644 (file)
@@ -29,6 +29,7 @@ $string['add'] = 'Add';
 $string['addfile'] = 'Add...';
 $string['addfiletext'] = 'Add file';
 $string['addplugin'] = 'Add a repository plugin';
+$string['aliaseschange'] = 'There are {$a} alias/shortcut files that use this file as their source. If you proceed then those aliases will be converted to true copies.';
 $string['allowexternallinks'] = 'Allow external links';
 $string['areamainfile'] = 'Main file';
 $string['coursebackup'] = 'Course backups';
@@ -68,7 +69,7 @@ $string['configsyncimagetimeout'] = 'Timeout in seconds for downloading an image
 $string['confirmdelete'] = 'Are you sure you want to delete the repository {$a}? If you choose "Continue and download", file references to external contents will be downloaded to Moodle. This could take a long time to process.';
 $string['confirmdeletefile'] = 'Are you sure you want to delete this file?';
 $string['confirmdeleteselectedfile'] = 'Are you sure you want to delete the selected {$a} file(s)?';
-$string['confirmrenamefile'] = 'Are you sure you want to rename/move this file? There are {$a} alias/shortcut files that use this file as their source. If you proceed then those aliases will be converted to true copies.';
+$string['confirmrenamefile'] = 'Are you sure you want to rename/move this file?';
 $string['confirmdeletefilewithhref'] = 'Are you sure you want to delete this file? There are {$a} alias/shortcut files that use this file as their source. If you proceed then those aliases will be converted to true copies.';
 $string['confirmdeletefolder'] = 'Are you sure you want to delete this folder? All files and subfolders will be deleted.';
 $string['confirmremove'] = 'Are you sure you want to remove this repository plugin, its options and <strong>all of its instances</strong> - {$a}? If you choose "Continue and download", file references to external contents will be downloaded to Moodle. This could take a long time to process.';
@@ -187,6 +188,8 @@ $string['norepositoriesexternalavailable'] = 'Sorry, none of your current reposi
 $string['notyourinstances'] = 'You can not view/edit repository instances of another user';
 $string['off'] = 'Enabled but hidden';
 $string['original'] = 'Original';
+$string['originalextensionchange'] = 'The original file extension has been modified as a part of the file name change. Changing the extension from ".{$a->originalextension}" to ".{$a->newextension}" could potentially cause some side effects.';
+$string['originalextensionremove'] = 'The original file extension has been removed as a part of the file name change. Removing the extension ".{$a}" could potentially cause some side effects.';
 $string['openpicker'] = 'Choose a file...';
 $string['operation'] = 'Operation';
 $string['on'] = 'Enabled and visible';
index 78020e0..ed26c91 100644 (file)
@@ -23,4 +23,5 @@
  */
 
 $string['downloadas'] = 'Download table data as';
-
+$string['missingrequiredfields'] = 'One or more required filters were missing ({$a})';
+$string['privacy:metadata'] = 'The Table API does not currently store any user data';
index 854486a..3f0dc85 100644 (file)
@@ -11208,3 +11208,44 @@ class admin_setting_configthemepreset extends admin_setting_configselect {
         return true;
     }
 }
+
+/**
+ * Selection of plugins that can work as H5P libraries handlers
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright 2020 Sara Arjona <sara@moodle.com>
+ */
+class admin_settings_h5plib_handler_select extends admin_setting_configselect {
+
+    /**
+     * Constructor
+     * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
+     *        for ones in config_plugins.
+     * @param string $visiblename localised
+     * @param string $description long localised info
+     * @param string $defaultsetting
+     */
+    public function __construct($name, $visiblename, $description, $defaultsetting = '') {
+        parent::__construct($name, $visiblename, $description, $defaultsetting, null);
+    }
+
+    /**
+     * Lazy-load the available choices for the select box
+     */
+    public function load_choices() {
+        if (during_initial_install()) {
+            return false;
+        }
+        if (is_array($this->choices)) {
+            return true;
+        }
+
+        $this->choices = \core_h5p\local\library\autoloader::get_all_handlers();
+        foreach ($this->choices as $name => $class) {
+            $this->choices[$name] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
+                ['name' => new lang_string('pluginname', $name), 'component' => $name]);
+        }
+
+        return true;
+    }
+}
index e54631a..3269e6e 100644 (file)
Binary files a/lib/amd/build/modal.min.js and b/lib/amd/build/modal.min.js differ
index 0a501e5..5b24706 100644 (file)
Binary files a/lib/amd/build/modal.min.js.map and b/lib/amd/build/modal.min.js.map differ
index f38a752..2fa4d54 100644 (file)
Binary files a/lib/amd/build/modal_factory.min.js and b/lib/amd/build/modal_factory.min.js differ
index 32561b5..ff93873 100644 (file)
Binary files a/lib/amd/build/modal_factory.min.js.map and b/lib/amd/build/modal_factory.min.js.map differ
index 16469ae..aeefaed 100644 (file)
@@ -32,7 +32,8 @@ define([
     'core/event',
     'core/modal_events',
     'core/local/aria/focuslock',
-], function($, Templates, Notification, KeyCodes, CustomEvents, ModalBackdrop, Event, ModalEvents, FocusLock) {
+    'core/pending',
+], function($, Templates, Notification, KeyCodes, CustomEvents, ModalBackdrop, Event, ModalEvents, FocusLock, Pending) {
 
     var SELECTORS = {
         CONTAINER: '[data-region="modal-container"]',
@@ -615,6 +616,8 @@ define([
             return;
         }
 
+        var pendingPromise = new Pending('core/modal:show');
+
         if (this.hasFooterContent()) {
             this.showFooter();
         } else {
@@ -625,7 +628,8 @@ define([
             this.attachToDOM();
         }
 
-        this.getBackdrop().done(function(backdrop) {
+        this.getBackdrop()
+        .then(function(backdrop) {
             var currentIndex = this.calculateZIndex();
             var newIndex = currentIndex + 2;
             var newBackdropIndex = newIndex - 1;
@@ -638,7 +642,10 @@ define([
             this.getModal().focus();
             $('body').addClass('modal-open');
             this.root.trigger(ModalEvents.shown, this);
-        }.bind(this));
+
+            return;
+        }.bind(this))
+        .then(pendingPromise.resolve);
     };
 
     /**
index 4e9381e..2b304bc 100644 (file)
  */
 define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
         'core/modal_save_cancel', 'core/modal_cancel',
-        'core/templates', 'core/notification', 'core/custom_interaction_events'],
+        'core/templates', 'core/notification', 'core/custom_interaction_events',
+        'core/pending'],
     function($, ModalEvents, ModalRegistry, Modal, ModalSaveCancel,
-        ModalCancel, Templates, Notification, CustomEvents) {
+        ModalCancel, Templates, Notification, CustomEvents, Pending) {
 
     // The templates for each type of modal.
     var TEMPLATES = {
@@ -64,6 +65,7 @@ define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
         var hasPreShowCallback = (typeof modalConfig.preShowCallback == 'function');
         // Function to handle the trigger element being activated.
         var triggeredCallback = function(e, data) {
+            var pendingPromise = new Pending('core/modal_factory:setUpTrigger:triggeredCallback');
             actualTriggerElement = $(e.currentTarget);
             modalPromise.then(function(modal) {
                 if (hasPreShowCallback) {
@@ -75,7 +77,8 @@ define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
                 modal.show();
 
                 return modal;
-            });
+            })
+            .then(pendingPromise.resolve);
             data.originalEvent.preventDefault();
         };
 
index 1d2f972..d1604e6 100644 (file)
@@ -1027,14 +1027,25 @@ function signup_validate_data($data, $files) {
         $errors['email'] = get_string('invalidemail');
 
     } else if (empty($CFG->allowaccountssameemail)) {
-        // Make a case-insensitive query for the given email address.
-        $select = $DB->sql_equal('email', ':email', false) . ' AND mnethostid = :mnethostid';
+        // Emails in Moodle as case-insensitive and accents-sensitive. Such a combination can lead to very slow queries
+        // on some DBs such as MySQL. So we first get the list of candidate users in a subselect via more effective
+        // accent-insensitive query that can make use of the index and only then we search within that limited subset.
+        $sql = "SELECT 'x'
+                  FROM {user}
+                 WHERE " . $DB->sql_equal('email', ':email1', false, true) . "
+                   AND id IN (SELECT id
+                                FROM {user}
+                               WHERE " . $DB->sql_equal('email', ':email2', false, false) . "
+                                 AND mnethostid = :mnethostid)";
+
         $params = array(
-            'email' => $data['email'],
+            'email1' => $data['email'],
+            'email2' => $data['email'],
             'mnethostid' => $CFG->mnet_localhost_id,
         );
+
         // If there are other user(s) that already have the same email, show an error.
-        if ($DB->record_exists_select('user', $select, $params)) {
+        if ($DB->record_exists_sql($sql, $params)) {
             $forgotpasswordurl = new moodle_url('/login/forgot_password.php');
             $forgotpasswordlink = html_writer::link($forgotpasswordurl, get_string('emailexistshintlink'));
             $errors['email'] = get_string('emailexists') . ' ' . get_string('emailexistssignuphint', 'moodle', $forgotpasswordlink);
index 5338be1..267406b 100644 (file)
@@ -34,6 +34,7 @@ require_once(__DIR__ . '/../../filelib.php');
 require_once(__DIR__ . '/../../clilib.php');
 
 use Behat\Mink\Session;
+use Behat\Mink\Exception\ExpectationException;
 
 /**
  * Init/reset utilities for Behat database and dataroot
index 938313c..122059b 100644 (file)
@@ -41,7 +41,7 @@ class group_member_removed extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' removed the user with id '$this->relateduserid' to the group with " .
+        return "The user with id '$this->userid' removed the user with id '$this->relateduserid' from the group with " .
             "id '$this->objectid'.";
     }
 
index cd716ef..1fb94e3 100644 (file)
@@ -58,6 +58,8 @@ class registration {
         ],
         // Analytics stats added in Moodle 3.7.
         2019022200 => ['analyticsenabledmodels', 'analyticspredictions', 'analyticsactions', 'analyticsactionsnotuseful'],
+        // Active users stats added in Moodle 3.9.
+        2020022600 => ['activeusers', 'activeparticipantnumberaverage'],
     ];
 
     /** @var Site privacy: not displayed */
@@ -168,6 +170,7 @@ class registration {
         // Statistical data.
         $siteinfo['courses'] = $DB->count_records('course') - 1;
         $siteinfo['users'] = $DB->count_records('user', array('deleted' => 0));
+        $siteinfo['activeusers'] = $DB->count_records_select('user', 'deleted = ? AND lastlogin > ?', [0, time() - DAYSECS * 30]);
         $siteinfo['enrolments'] = $DB->count_records('role_assignments');
         $siteinfo['posts'] = $DB->count_records('forum_posts');
         $siteinfo['questions'] = $DB->count_records('question');
@@ -175,6 +178,7 @@ class registration {
         $siteinfo['badges'] = $DB->count_records_select('badge', 'status <> ' . BADGE_STATUS_ARCHIVED);
         $siteinfo['issuedbadges'] = $DB->count_records('badge_issued');
         $siteinfo['participantnumberaverage'] = average_number_of_participants();
+        $siteinfo['activeparticipantnumberaverage'] = average_number_of_participants(true, time() - DAYSECS * 30);
         $siteinfo['modulenumberaverage'] = average_number_of_courses_modules();
 
         // Version and url.
@@ -229,6 +233,7 @@ class registration {
             'moodlerelease' => get_string('sitereleasenum', 'hub', $moodlerelease),
             'courses' => get_string('coursesnumber', 'hub', $siteinfo['courses']),
             'users' => get_string('usersnumber', 'hub', $siteinfo['users']),
+            'activeusers' => get_string('activeusersnumber', 'hub', $siteinfo['activeusers']),
             'enrolments' => get_string('roleassignmentsnumber', 'hub', $siteinfo['enrolments']),
             'posts' => get_string('postsnumber', 'hub', $siteinfo['posts']),
             'questions' => get_string('questionsnumber', 'hub', $siteinfo['questions']),
@@ -237,6 +242,8 @@ class registration {
             'issuedbadges' => get_string('issuedbadgesnumber', 'hub', $siteinfo['issuedbadges']),
             'participantnumberaverage' => get_string('participantnumberaverage', 'hub',
                 format_float($siteinfo['participantnumberaverage'], 2)),
+            'activeparticipantnumberaverage' => get_string('activeparticipantnumberaverage', 'hub',
+                format_float($siteinfo['activeparticipantnumberaverage'], 2)),
             'modulenumberaverage' => get_string('modulenumberaverage', 'hub',
                 format_float($siteinfo['modulenumberaverage'], 2)),
             'mobileservicesenabled' => get_string('mobileservicesenabled', 'hub', $mobileservicesenabled),
index 7ac652d..72be171 100644 (file)
@@ -1881,6 +1881,10 @@ class core_plugin_manager {
                 'rubric', 'guide'
             ),
 
+            'h5plib' => array(
+                'v124'
+            ),
+
             'local' => array(
             ),
 
@@ -1910,7 +1914,7 @@ class core_plugin_manager {
 
             'mod' => array(
                 'assign', 'assignment', 'book', 'chat', 'choice', 'data', 'feedback', 'folder',
-                'forum', 'glossary', 'imscp', 'label', 'lesson', 'lti', 'page',
+                'forum', 'glossary', 'h5pactivity', 'imscp', 'label', 'lesson', 'lti', 'page',
                 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'
             ),
 
diff --git a/lib/classes/plugininfo/h5plib.php b/lib/classes/plugininfo/h5plib.php
new file mode 100644 (file)
index 0000000..02765ee
--- /dev/null
@@ -0,0 +1,88 @@
+<?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/>.
+
+/**
+ * Defines classes used for plugin info.
+ *
+ * @package    core
+ * @copyright  2019 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\plugininfo;
+
+use moodle_url;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class for H5P libraries.
+ */
+class h5plib extends base {
+
+    /**
+     * Defines if there should be a way to uninstall the plugin via the administration UI.
+     *
+     * @return bool
+     */
+    public function is_uninstall_allowed(): bool {
+        return true;
+    }
+
+    /**
+     * H5P versions cannot be disabled.
+     *
+     * @return boolean
+     */
+    public function is_enabled(): bool {
+        return true;
+    }
+
+    /**
+     * Return URL used for management of plugins of this type.
+     * @return moodle_url
+     */
+    public static function get_manage_url(): \moodle_url {
+        return new moodle_url('/admin/settings.php', ['section' => 'h5psettings']);
+    }
+
+    /**
+     * Loads plugin settings to the settings tree
+     *
+     * This function usually includes settings.php file in plugins folder.
+     * Alternatively it can create a link to some settings page (instance of admin_externalpage)
+     *
+     * @param \part_of_admin_tree $adminroot
+     * @param string $parentnodename
+     * @param bool $hassiteconfig whether the current user has moodle/site:config capability
+     */
+    public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
+        global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them.
+        $ADMIN = $adminroot; // May be used in settings.php.
+        $plugininfo = $this; // Also can be used inside settings.php.
+
+        if (!$this->is_installed_and_upgraded()) {
+            return;
+        }
+
+        if (!$hassiteconfig) {
+            return;
+        }
+
+        if (file_exists($this->full_path('settings.php'))) {
+            include($this->full_path('settings.php'));
+        }
+    }
+}
\ No newline at end of file
index 680f7c0..1457de2 100644 (file)
@@ -36,7 +36,8 @@
         "cachelock": "cache\/locks",
         "fileconverter": "files\/converter",
         "theme": "theme",
-        "local": "local"
+        "local": "local",
+        "h5plib": "h5p\/h5plib"
     },
     "subsystems": {
         "access": null,
         "rss": "rss",
         "role": "admin\/roles",
         "search": "search",
-        "table": null,
+        "table": "lib\/table",
         "tag": "tag",
         "timezones": null,
         "user": "user",
index feefb27..5bac588 100644 (file)
 $string['browserepositories'] = 'Browse repositories...';
 $string['copyrightbutton'] = 'Copyright button';
 $string['downloadbutton'] = 'Allow download';
-$string['either'] = 'Either';
 $string['embedbutton'] = 'Embed button';
-$string['enterurl'] = 'URL or embed code';
 $string['h5p:addembed'] = 'Add embedded H5P';
 $string['h5pfile'] = 'H5P file upload';
+$string['h5pfileorurl'] = 'H5P URL or file upload';
 $string['h5poptions'] = 'H5P options';
-$string['h5pproperties'] = 'H5P properties';
 $string['h5purl'] = 'H5P URL';
 $string['invalidh5purl'] = 'Invalid URL';
-$string['instructions'] = 'You can insert H5P content by <strong>either</strong> entering a URL or embed code from an external H5P site <strong>or</strong> by uploading an H5P file.';
+$string['instructions'] = 'You can insert H5P content by <strong>either</strong> entering a URL <strong>or</strong> by uploading an H5P file.';
 $string['noh5pcontent'] = 'No H5P content added';
 $string['pluginname'] = 'Insert H5P';
 $string['privacy:metadata'] = 'The atto_h5p plugin does not store any personal data.';
-$string['or'] = 'or';
\ No newline at end of file
+
+// Deprecated since Moodle 3.9.
+$string['either'] = 'Either';
+$string['enterurl'] = 'URL or embed code';
+$string['h5pproperties'] = 'H5P properties';
+$string['or'] = 'or';
diff --git a/lib/editor/atto/plugins/h5p/lang/en/deprecated.txt b/lib/editor/atto/plugins/h5p/lang/en/deprecated.txt
new file mode 100644 (file)
index 0000000..baee94f
--- /dev/null
@@ -0,0 +1,4 @@
+either,atto_h5p
+enterurl,atto_h5p
+h5pproperties,atto_h5p
+or,atto_h5p
\ No newline at end of file
index 60b34e7..5aa3457 100644 (file)
@@ -24,6 +24,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+use core_h5p\local\library\autoloader;
+
 /**
  * Set params for this button.
  *
@@ -67,21 +69,18 @@ function atto_h5p_strings_for_js() {
         'copyrightbutton',
         'downloadbutton',
         'instructions',
-        'either',
         'embedbutton',
-        'enterurl',
         'h5pfile',
         'h5poptions',
-        'h5pproperties',
         'h5purl',
+        'h5pfileorurl',
         'invalidh5purl',
         'noh5pcontent',
-        'or',
         'pluginname'
     );
 
     $PAGE->requires->strings_for_js($strings, 'atto_h5p');
-    $PAGE->requires->js(new moodle_url('/lib/h5p/js/h5p-resizer.js'));
+    $PAGE->requires->js(autoloader::get_h5p_core_library_url('js/h5p-resizer.js'));
 }
 
 
index 4ea2e13..5e224f0 100644 (file)
@@ -17,7 +17,7 @@ Feature: Add h5ps to Atto
       | page     | PageName1  | PageDesc1  | 1           | C1     | H5Ptest  | 1             | 1        |
     And the "displayh5p" filter is "on"
     And the following config values are set as admin:
-      | allowedsources | https://moodle.h5p.com/content/[id]/embed | filter_displayh5p |
+      | allowedsources | https://moodle.h5p.com/content/[id] | filter_displayh5p |
 
   @javascript @external
   Scenario: Insert an embedded h5p
@@ -27,7 +27,7 @@ Feature: Add h5ps to Atto
     And I follow "PageName1"
     And I navigate to "Edit settings" in current page administration
     And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
-    And I set the field with xpath "//textarea[@data-region='h5purl']" to "https://moodle.h5p.com/content/1290772960722742119/embed"
+    And I set the field with xpath "//input[@data-region='h5pfile']" to "https://moodle.h5p.com/content/1290772960722742119"
     And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
     And I wait until the page is ready
     When I click on "Save and display" "button"
@@ -40,7 +40,7 @@ Feature: Add h5ps to Atto
   Scenario: Insert an h5p file
     Given I log in as "admin"
     And I follow "Manage private files..."
-    And I upload "lib/editor/atto/tests/fixtures/guess-the-answer.h5p" file to "Files" filemanager
+    And I upload "h5p/tests/fixtures/guess-the-answer.h5p" file to "Files" filemanager
     And I click on "Save changes" "button"
     And I am on "Course 1" course homepage
     And I follow "PageName1"
@@ -64,7 +64,7 @@ Feature: Add h5ps to Atto
     And I navigate to "Edit settings" in current page administration
     And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
 #   This is not a real external URL, so this scenario shouldn't be labeled as external.
-    And I set the field with xpath "//textarea[@data-region='h5purl']" to "ftp://moodle.h5p.com/content/1290772960722742119/embed"
+    And I set the field with xpath "//input[@data-region='h5pfile']" to "ftp://moodle.h5p.com/content/1290772960722742119"
     When I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
     And I wait until the page is ready
     Then I should see "Invalid URL" in the "Insert H5P" "dialogue"
@@ -91,7 +91,9 @@ Feature: Add h5ps to Atto
     And I follow "PageName1"
     When I navigate to "Edit settings" in current page administration
     And I click on "Insert H5P" "button"
-    Then I should not see "URL or embed code" in the "Insert H5P" "dialogue"
+    Then I should not see "H5P URL" in the "Insert H5P" "dialogue"
+    And I should see "H5P file upload" in the "Insert H5P" "dialogue"
+    And I should see "H5P options" in the "Insert H5P" "dialogue"
 
   @javascript
   Scenario: No upload h5p capabilities
@@ -104,6 +106,8 @@ Feature: Add h5ps to Atto
     When I navigate to "Edit settings" in current page administration
     And I click on "Insert H5P" "button"
     Then I should not see "H5P file upload" in the "Insert H5P" "dialogue"
+    And I should see "H5P URL" in the "Insert H5P" "dialogue"
+    And I should not see "H5P options" in the "Insert H5P" "dialogue"
 
   @javascript @external
   Scenario: Edit H5P content
@@ -132,7 +136,7 @@ Feature: Add h5ps to Atto
     And I click on ".h5p-placeholder" "css_element"
     And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
 #   External URL
-    And I set the field with xpath "//textarea[@data-region='h5purl']" to "https://moodle.h5p.com/content/1290772960722742119/embed"
+    And I set the field with xpath "//input[@data-region='h5pfile']" to "https://moodle.h5p.com/content/1290772960722742119"
     And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
     And I wait until the page is ready
     And I click on "Save and display" "button"
@@ -145,7 +149,7 @@ Feature: Add h5ps to Atto
   Scenario: Enable/disable H5P options
     Given I log in as "admin"
     And I follow "Manage private files..."
-    And I upload "lib/editor/atto/tests/fixtures/guess-the-answer.h5p" file to "Files" filemanager
+    And I upload "h5p/tests/fixtures/guess-the-answer.h5p" file to "Files" filemanager
     And I click on "Save changes" "button"
     And I am on "Course 1" course homepage
     And I follow "PageName1"
@@ -199,6 +203,32 @@ Feature: Add h5ps to Atto
     And I should see "Embed"
     And I should see "Rights of use"
 
+  @javascript @external
+  Scenario: H5P options are ignored for H5P URLs
+    Given I log in as "admin"
+    And I change window size to "large"
+    And I am on "Course 1" course homepage
+    And I follow "PageName1"
+    And I navigate to "Edit settings" in current page administration
+    And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
+    And I set the field with xpath "//input[@data-region='h5pfile']" to "https://moodle.h5p.com/content/1290752078589054689"
+    And I click on "H5P options" "link"
+    And I click on "Embed button" "checkbox"
+    And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
+    And I wait until the page is ready
+    When I click on "Save and display" "button"
+    Then ".h5p-placeholder" "css_element" should exist
+    And I wait until the page is ready
+    And I switch to "h5pcontent" iframe
+    And I should see "History of strawberries"
+    And I should not see "Embed"
+    And I switch to the main frame
+    And I navigate to "Edit settings" in current page administration
+    And I click on ".h5p-placeholder" "css_element"
+    And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
+    And I click on "H5P options" "link"
+    And "input[aria-label=\"Embed button\"]:not([checked=checked])" "css_element" should exist
+
   @javascript
   Scenario: Private H5P files are shown to students
     Given the following "users" exist:
@@ -209,7 +239,7 @@ Feature: Add h5ps to Atto
       | student1 | C1 | student |
     And I log in as "admin"
     And I follow "Manage private files..."
-    And I upload "lib/editor/atto/tests/fixtures/guess-the-answer.h5p" file to "Files" filemanager
+    And I upload "h5p/tests/fixtures/guess-the-answer.h5p" file to "Files" filemanager
     And I click on "Save changes" "button"
     And I am on "Course 1" course homepage
     And I follow "PageName1"
index 29ed69b..98d1d42 100644 (file)
Binary files a/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-debug.js and b/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-debug.js differ
index 41c5f75..7fe48a7 100644 (file)
Binary files a/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-min.js and b/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-min.js differ
index 29ed69b..98d1d42 100644 (file)
Binary files a/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button.js and b/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button.js differ
index 4b753ba..2906734 100644 (file)
@@ -36,7 +36,6 @@ var CSS = {
         H5PBROWSER: 'openh5pbrowser',
         INPUTALT: 'atto_h5p_altentry',
         INPUTH5PFILE: 'atto_h5p_file',
-        INPUTH5PURL: 'atto_h5p_url',
         INPUTSUBMIT: 'atto_h5p_urlentrysubmit',
         OPTION_DOWNLOAD_BUTTON: 'atto_h5p_option_download_button',
         OPTION_COPYRIGHT_BUTTON: 'atto_h5p_option_copyright_button',
@@ -47,7 +46,6 @@ var CSS = {
         CONTENTWARNING: '.' + CSS.CONTENTWARNING,
         H5PBROWSER: '.' + CSS.H5PBROWSER,
         INPUTH5PFILE: '.' + CSS.INPUTH5PFILE,
-        INPUTH5PURL: '.' + CSS.INPUTH5PURL,
         INPUTSUBMIT: '.' + CSS.INPUTSUBMIT,
         OPTION_DOWNLOAD_BUTTON: '.' + CSS.OPTION_DOWNLOAD_BUTTON,
         OPTION_COPYRIGHT_BUTTON: '.' + CSS.OPTION_COPYRIGHT_BUTTON,
@@ -62,65 +60,71 @@ var CSS = {
                 '<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.CONTENTWARNING}}">' +
                     '{{get_string "noh5pcontent" component}}' +
                 '</div>' +
-                '{{#if canUploadAndEmbed}}' +
-                    '<div class="mt-2 attoh5pinstructions">{{{get_string "instructions" component}}}</div>' +
-                    '<div class="my-2"><strong>{{get_string "either" component}}</strong></div>' +
-                '{{/if}}' +
-                '{{#if canEmbed}}' +
-                '<div class="mb-4">' +
-                    '<label for="{{elementid}}_{{CSS.INPUTH5PURL}}">{{get_string "enterurl" component}}</label>' +
-                    '<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.URLWARNING}}">' +
-                        '{{get_string "invalidh5purl" component}}' +
-                    '</div>' +
-                    '<textarea rows="3