Merge branch 'MDL-65025-master' of git://github.com/jleyva/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 16 Sep 2019 22:01:51 +0000 (00:01 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 16 Sep 2019 22:01:51 +0000 (00:01 +0200)
417 files changed:
.eslintrc
.jshintignore [new file with mode: 0644]
.nvmrc [new file with mode: 0644]
Gruntfile.js
admin/classes/form/purge_caches.php
admin/classes/local/settings/filesize.php [new file with mode: 0644]
admin/roles/classes/define_role_table_advanced.php
admin/settings/appearance.php
admin/settings/security.php
admin/settings/subsystems.php
admin/settings/users.php
admin/templates/setting_configfilesize.mustache [new file with mode: 0644]
admin/tests/behat/behat_admin.php
admin/tests/behat/filter_users_settings.feature [new file with mode: 0644]
admin/tests/behat/manage_tokens.feature
admin/tool/behat/tests/behat/edit_permissions.feature
admin/tool/capability/renderer.php
admin/tool/lp/amd/build/competencies.min.js
admin/tool/lp/amd/build/competencies.min.js.map
admin/tool/lp/amd/build/competencyactions.min.js
admin/tool/lp/amd/build/competencyactions.min.js.map
admin/tool/lp/amd/build/competencypicker.min.js
admin/tool/lp/amd/build/competencypicker.min.js.map
admin/tool/lp/amd/build/course_competency_settings.min.js
admin/tool/lp/amd/build/course_competency_settings.min.js.map
admin/tool/lp/amd/build/grade_dialogue.min.js
admin/tool/lp/amd/build/grade_dialogue.min.js.map
admin/tool/lp/amd/build/grade_user_competency_inline.min.js
admin/tool/lp/amd/build/grade_user_competency_inline.min.js.map
admin/tool/lp/amd/src/competencies.js
admin/tool/lp/amd/src/competencyactions.js
admin/tool/lp/amd/src/competencypicker.js
admin/tool/lp/amd/src/course_competency_settings.js
admin/tool/lp/amd/src/grade_dialogue.js
admin/tool/lp/amd/src/grade_user_competency_inline.js
admin/tool/lp/tests/behat/framework_crud.feature
admin/tool/lp/tests/behat/template_crud.feature
admin/tool/messageinbound/lang/en/tool_messageinbound.php
admin/tool/mobile/classes/api.php
admin/tool/mobile/classes/external.php
admin/tool/mobile/lang/en/tool_mobile.php
admin/tool/mobile/launch.php
admin/tool/mobile/settings.php
admin/tool/mobile/tests/externallib_test.php
admin/tool/mobile/tests/fixtures/output/mobile.php
admin/tool/uploaduser/index.php
admin/tool/uploaduser/locallib.php
admin/tool/uploaduser/tests/behat/upload_users.feature
admin/tool/usertours/classes/privacy/provider.php
admin/tool/usertours/tests/privacy_provider_test.php
analytics/classes/local/analyser/by_course.php
analytics/classes/local/analysis/result_file.php
analytics/classes/manager.php
analytics/classes/model.php
analytics/classes/privacy/provider.php
analytics/tests/behat/manage_models.feature
analytics/tests/fixtures/test_indicator_multiclass.php [new file with mode: 0644]
analytics/tests/fixtures/test_target_course_users.php
analytics/tests/fixtures/test_target_shortname_multiclass.php [new file with mode: 0644]
analytics/tests/manager_test.php
analytics/tests/model_test.php
analytics/tests/prediction_test.php
auth/ldap/lang/en/auth_ldap.php
auth/ldap/lang/en/deprecated.txt [deleted file]
babel-plugin-add-module-to-define.js
backup/util/checks/backup_check.class.php
backup/util/checks/restore_check.class.php
backup/util/ui/renderer.php
backup/util/ui/tests/behat/restore_moodle2_courses.feature
blocks/community/block_community.php [deleted file]
blocks/community/classes/privacy/provider.php [deleted file]
blocks/community/communitycourse.php [deleted file]
blocks/community/db/access.php [deleted file]
blocks/community/db/install.xml [deleted file]
blocks/community/db/upgrade.php [deleted file]
blocks/community/forms.php [deleted file]
blocks/community/lang/en/block_community.php [deleted file]
blocks/community/locallib.php [deleted file]
blocks/community/renderer.php [deleted file]
blocks/community/styles.css [deleted file]
blocks/community/tests/privacy_test.php [deleted file]
blocks/community/yui/comments/comments.js [deleted file]
blocks/community/yui/imagegallery/imagegallery.js [deleted file]
blocks/myoverview/amd/build/view.min.js
blocks/myoverview/amd/build/view.min.js.map
blocks/myoverview/amd/src/view.js
blocks/myoverview/classes/output/main.php
blocks/myoverview/lang/en/block_myoverview.php
blocks/myoverview/lib.php
blocks/myoverview/settings.php
blocks/myoverview/styles.css [new file with mode: 0644]
blocks/myoverview/templates/nav-grouping-selector.mustache
blocks/myoverview/templates/view-cards.mustache
blocks/myoverview/templates/view-list.mustache
blocks/myoverview/templates/view-summary.mustache
blocks/myoverview/tests/behat/block_myoverview_adminsettings.feature [new file with mode: 0644]
blocks/myoverview/tests/behat/block_myoverview_dashboard.feature
blocks/myoverview/tests/behat/block_myoverview_favourite.feature
blocks/myoverview/tests/behat/block_myoverview_hidden.feature
blocks/myoverview/tests/behat/block_myoverview_pagelimit_persistence.feature
blocks/myoverview/tests/behat/block_myoverview_progress.feature
blocks/myoverview/tests/privacy_test.php
blocks/myoverview/version.php
blocks/recentlyaccesseditems/db/upgrade.php
blocks/site_main_menu/tests/behat/behat_block_site_main_menu.php
blocks/social_activities/tests/behat/behat_block_social_activities.php
blocks/timeline/amd/build/event_list.min.js
blocks/timeline/amd/build/event_list.min.js.map
blocks/timeline/amd/src/event_list.js
blocks/upgrade.txt
blog/rsslib.php
cache/renderer.php
cache/stores/redis/lib.php
calendar/classes/local/event/forms/create.php
calendar/externallib.php
calendar/lib.php
calendar/renderer.php
comment/classes/external.php
comment/lib.php
comment/tests/externallib_test.php
comment/upgrade.txt [new file with mode: 0644]
composer.json
composer.lock
config-dist.php
course/amd/build/repository.min.js
course/amd/build/repository.min.js.map
course/amd/src/repository.js
course/classes/analytics/indicator/activities_due.php
course/classes/external/course_summary_exporter.php
course/edit.php
course/externallib.php
course/format/singleactivity/lib.php
course/format/singleactivity/tests/behat/create_course.feature [new file with mode: 0644]
course/lib.php
course/management.php
course/publish/backup.php [deleted file]
course/publish/forms.php [deleted file]
course/publish/index.php [deleted file]
course/publish/metadata.php [deleted file]
course/renderer.php
course/search.php
course/templates/activity_navigation.mustache
course/templates/course_search_form.mustache
course/templates/coursecard.mustache
course/tests/behat/behat_course.php
course/tests/behat/course_creation.feature
course/tests/courselib_test.php
course/tests/externallib_test.php
course/tests/indicators_test.php
course/upgrade.txt
dataformat/json/classes/writer.php
enrol/editenrolment.php
enrol/externallib.php
enrol/tests/externallib_test.php
filter/mediaplugin/styles.css
grade/grading/form/rubric/tests/behat/grade_calculation.feature
grade/grading/form/rubric/tests/behat/negative_points.feature
grade/report/history/tests/behat/basic_functionality.feature
grade/report/singleview/classes/local/ui/dropdown_attribute.php
grade/report/singleview/templates/dropdown_attribute.mustache
grade/report/singleview/tests/behat/singleview.feature
grade/tests/behat/grade_to_pass.feature
group/overview.php
install/lang/cs/install.php
install/lang/de_wp/langconfig.php
install/lang/el/error.php
install/lang/el/install.php
install/lang/el_wp/admin.php
install/lang/fr_wp/langconfig.php [moved from blocks/community/version.php with 56% similarity]
install/lang/ro_wp/moodle.php
install/lang/zh_cn_wp/langconfig.php [moved from course/publish/lib.php with 54% similarity]
install/lang/zh_tw_wp/langconfig.php [moved from course/publish/hubselector.php with 52% similarity]
lang/en/access.php
lang/en/admin.php
lang/en/analytics.php
lang/en/backup.php
lang/en/badges.php
lang/en/cache.php
lang/en/calendar.php
lang/en/completion.php
lang/en/deprecated.txt
lang/en/error.php
lang/en/hub.php
lang/en/install.php
lang/en/langconfig.php
lang/en/message.php
lang/en/moodle.php
lang/en/my.php
lang/en/notes.php
lang/en/role.php
lib/accesslib.php
lib/adminlib.php
lib/amd/build/form-autocomplete.min.js
lib/amd/build/form-autocomplete.min.js.map
lib/amd/build/paged_content_factory.min.js
lib/amd/build/paged_content_factory.min.js.map
lib/amd/build/paged_content_paging_bar.min.js
lib/amd/build/paged_content_paging_bar.min.js.map
lib/amd/build/pubsub.min.js
lib/amd/build/pubsub.min.js.map
lib/amd/build/templates.min.js
lib/amd/build/templates.min.js.map
lib/amd/src/form-autocomplete.js
lib/amd/src/paged_content_factory.js
lib/amd/src/paged_content_paging_bar.js
lib/amd/src/pubsub.js
lib/amd/src/templates.js
lib/behat/behat_base.php
lib/behat/classes/behat_selectors.php
lib/behat/classes/partial_named_selector.php
lib/classes/event/context_locked.php [new file with mode: 0644]
lib/classes/event/context_unlocked.php [new file with mode: 0644]
lib/classes/hub/api.php
lib/classes/hub/course_publication_form.php [deleted file]
lib/classes/hub/publication.php [deleted file]
lib/classes/hub/registration.php
lib/classes/output/mustache_engine.php [new file with mode: 0644]
lib/classes/output/mustache_helper_collection.php [new file with mode: 0644]
lib/classes/output/mustache_template_source_loader.php
lib/classes/plugin_manager.php
lib/classes/requirejs.php
lib/csslib.php
lib/db/access.php
lib/db/install.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/dml/moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/editor/atto/lang/en/editor_atto.php
lib/editor/tinymce/tests/behat/disablecontrol.feature
lib/filelib.php
lib/form/amd/build/submit.min.js [new file with mode: 0644]
lib/form/amd/build/submit.min.js.map [new file with mode: 0644]
lib/form/amd/src/submit.js [new file with mode: 0644]
lib/form/templates/element-submit-inline.mustache
lib/form/templates/element-submit.mustache
lib/mlbackend/php/classes/processor.php
lib/mlbackend/python/classes/processor.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputfragmentrequirementslib.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/setup.php
lib/templates/filemanager_modal_generallayout.mustache
lib/templates/paged_content_paging_bar.mustache
lib/tests/behat/behat_deprecated.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_permissions.php
lib/tests/calendar_cron_task_test.php
lib/tests/core_renderer_template_exploit_test.php [new file with mode: 0644]
lib/tests/event_context_locked_test.php [new file with mode: 0644]
lib/tests/filelib_test.php
lib/tests/fixtures/upload_users_enrol_date_period.csv [new file with mode: 0644]
lib/tests/htmlpurifier_test.php
lib/tests/moodlelib_test.php
lib/tests/output_mustache_helper_collection_test.php [new file with mode: 0644]
lib/tests/outputrequirementslib_test.php
lib/tests/string_manager_standard_test.php
lib/upgrade.txt
lib/weblib.php
lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception-debug.js
lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js
lib/yui/src/notification/js/ajaxexception.js
lib/yui/src/notification/js/dialogue.js
message/amd/build/message_drawer.min.js
message/amd/build/message_drawer.min.js.map
message/amd/src/message_drawer.js
message/classes/api.php
message/templates/message_drawer.mustache
message/templates/message_drawer_view_conversation_body_day.mustache
message/templates/message_drawer_view_conversation_body_message.mustache
message/tests/api_test.php
message/tests/behat/behat_message.php
message/tests/behat/block_user.feature
mod/assign/amd/build/override_form.min.js [new file with mode: 0644]
mod/assign/amd/build/override_form.min.js.map [new file with mode: 0644]
mod/assign/amd/src/override_form.js [new file with mode: 0644]
mod/assign/externallib.php
mod/assign/feedback/editpdf/ajax.php
mod/assign/feedback/editpdf/styles.css
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/override_form.php
mod/assign/overrideedit.php
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/styles.css
mod/assign/submission/file/lang/en/assignsubmission_file.php
mod/assign/submission/file/lang/en/deprecated.txt [deleted file]
mod/assign/submission/file/locallib.php
mod/assign/templates/grading_app.mustache
mod/assign/templates/override_form_user_defaults.mustache [new file with mode: 0644]
mod/assign/tests/behat/relative_dates.feature [new file with mode: 0644]
mod/assign/tests/events_test.php
mod/assign/tests/externallib_test.php
mod/assign/tests/feedback_test.php [new file with mode: 0644]
mod/assign/tests/locallib_test.php
mod/assign/upgrade.txt
mod/book/lib.php
mod/book/tests/lib_test.php
mod/book/upgrade.txt
mod/data/field.php
mod/data/locallib.php
mod/feedback/feedback.js [deleted file]
mod/feedback/lang/en/feedback.php
mod/feedback/lib.php
mod/feedback/show_nonrespondents.php
mod/feedback/upgrade.txt
mod/forum/classes/output/big_search_form.php
mod/forum/classes/subscriptions.php
mod/forum/deprecatedlib.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/post.php
mod/forum/search.php
mod/forum/subscribe.php
mod/forum/templates/big_search_form.mustache
mod/forum/tests/behat/advanced_search.feature
mod/forum/tests/subscriptions_test.php
mod/forum/upgrade.txt
mod/forum/view.php
mod/glossary/editcategories.html
mod/glossary/editcategories.php
mod/glossary/lib.php
mod/glossary/tests/behat/categories.feature
mod/glossary/upgrade.txt
mod/lesson/lang/en/lesson.php
mod/lesson/locallib.php
mod/lesson/report.php
mod/lti/lib.php
mod/lti/upgrade.txt
mod/quiz/attempt.php
mod/quiz/attemptlib.php
mod/quiz/lang/en/deprecated.txt
mod/quiz/lang/en/quiz.php
mod/quiz/review.php
mod/quiz/summary.php
mod/quiz/tests/attempt_test.php
mod/scorm/classes/external.php
mod/scorm/tests/externallib_test.php
mod/wiki/lib.php
mod/wiki/upgrade.txt
mod/workshop/lang/en/deprecated.txt [deleted file]
mod/workshop/lang/en/workshop.php
mod/workshop/lib.php
mod/workshop/upgrade.txt
npm-shrinkwrap.json
package.json
question/tests/generator/lib.php
question/type/ddimageortext/styles.css
question/type/ddwtos/styles.css
rating/classes/external.php
report/participation/index.php
repository/filepicker.js
search/classes/engine.php
search/classes/manager.php
search/engine/simpledb/classes/engine.php
search/engine/simpledb/tests/engine_test.php
search/engine/solr/classes/engine.php
search/engine/solr/lang/en/search_solr.php
search/engine/solr/tests/engine_test.php
search/engine/solr/tests/fixtures/testable_engine.php
search/tests/behat/search_by_user.feature
search/tests/fixtures/mock_search_engine.php
search/tests/manager_test.php
search/upgrade.txt
theme/boost/amd/build/aria.min.js
theme/boost/amd/build/aria.min.js.map
theme/boost/amd/build/loader.min.js
theme/boost/amd/build/loader.min.js.map
theme/boost/amd/src/aria.js
theme/boost/amd/src/loader.js
theme/boost/scss/moodle/message.scss
theme/boost/scss/moodle/modules.scss
theme/boost/style/moodle.css
theme/boost/templates/columns1.mustache
theme/boost/templates/columns2.mustache
theme/boost/templates/embedded.mustache
theme/boost/templates/login.mustache
theme/boost/templates/maintenance.mustache
theme/boost/templates/secure.mustache
theme/classic/style/moodle.css
theme/classic/templates/columns.mustache
theme/classic/templates/contentonly.mustache
theme/classic/templates/secure.mustache
theme/classic/tests/behat/behat_theme_classic_behat_admin.php
theme/styles.php
theme/styles_debug.php
theme/upgrade.txt
user/amd/build/participants.min.js
user/amd/build/participants.min.js.map
user/amd/build/status_field.min.js
user/amd/build/status_field.min.js.map
user/amd/src/participants.js
user/amd/src/status_field.js
user/classes/participants_table.php
user/editadvanced_form.php
user/filters/date.php
user/filters/lib.php
user/index.php
user/selector/module.js
user/tests/behat/behat_user.php
user/tests/behat/bulk_editenrolment.feature
user/tests/behat/set_default_homepage.feature
user/tests/behat/view_participants.feature
version.php
webservice/externallib.php
webservice/tests/externallib_test.php
webservice/upgrade.txt

index 2388717..e44591a 100644 (file)
--- a/.eslintrc
+++ b/.eslintrc
       }
     },
     {
-      files: ["**/amd/src/*.js", "**/amd/src/**/*.js"],
+      files: ["**/amd/src/*.js", "**/amd/src/**/*.js", "Gruntfile*.js", "babel-plugin-add-module-to-define.js"],
       // We support es6 now. Woot!
       env: {
         es6: true
diff --git a/.jshintignore b/.jshintignore
new file mode 100644 (file)
index 0000000..5e61b7c
--- /dev/null
@@ -0,0 +1,2 @@
+**/amd/**
+/*.js
diff --git a/.nvmrc b/.nvmrc
new file mode 100644 (file)
index 0000000..7796292
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+v8.16.1
index 771bed1..bd41cf3 100644 (file)
@@ -24,6 +24,7 @@
  * Grunt configuration
  */
 
+/* eslint-env node */
 module.exports = function(grunt) {
     var path = require('path'),
         tasks = {},
@@ -575,6 +576,7 @@ module.exports = function(grunt) {
                 };
 
                 if (relativePath) {
+                    /* eslint-disable camelcase */
                     sub.relative_root = relativePath;
                 }
 
index 3151445..ce44d1e 100644 (file)
@@ -50,6 +50,7 @@ class purge_caches extends \moodleform {
             $mform->createElement('advcheckbox', 'theme', '', get_string('purgethemecache', 'admin')),
             $mform->createElement('advcheckbox', 'lang', '', get_string('purgelangcache', 'admin')),
             $mform->createElement('advcheckbox', 'js', '', get_string('purgejscache', 'admin')),
+            $mform->createElement('advcheckbox', 'template', '', get_string('purgetemplates', 'admin')),
             $mform->createElement('advcheckbox', 'filter', '', get_string('purgefiltercache', 'admin')),
             $mform->createElement('advcheckbox', 'muc', '', get_string('purgemuc', 'admin')),
             $mform->createElement('advcheckbox', 'other', '', get_string('purgeothercaches', 'admin'))
diff --git a/admin/classes/local/settings/filesize.php b/admin/classes/local/settings/filesize.php
new file mode 100644 (file)
index 0000000..e5a6edb
--- /dev/null
@@ -0,0 +1,194 @@
+<?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/>.
+
+/**
+ * File size admin setting.
+ *
+ * @package    core_admin
+ * @copyright  2019 Shamim Rezaie <shamim@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_admin\local\settings;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/adminlib.php');
+
+/**
+ * An admin setting to support entering and displaying of file sizes in Bytes, KB, MB or GB.
+ *
+ * @copyright   2019 Shamim Rezaie <shamim@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class filesize extends \admin_setting {
+
+    /** @var int The byte unit. Number of bytes in a byte */
+    const UNIT_B = 1;
+
+    /** @var int The kilobyte unit (number of bytes in a kilobyte) */
+    const UNIT_KB = 1024;
+
+    /** @var int The megabyte unit (number of bytes in a megabyte) */
+    const UNIT_MB = 1048576;
+
+    /** @var int The gigabyte unit (number of bytes in a gigabyte) */
+    const UNIT_GB = 1073741824;
+
+    /** @var int default size unit */
+    protected $defaultunit;
+
+    /**
+     * 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 name
+     * @param string    $description    localised long description
+     * @param int|null  $defaultvalue   Value of the settings in bytes
+     * @param int|null  $defaultunit    GB, MB, etc. (in bytes)
+     */
+    public function __construct(string $name, string $visiblename, string $description,
+            int $defaultvalue = null, int $defaultunit = null) {
+
+        $defaultsetting = self::parse_bytes($defaultvalue);
+
+        if ($defaultunit && array_key_exists($defaultunit, self::get_units())) {
+            $this->defaultunit = $defaultunit;
+        } else {
+            $this->defaultunit = self::UNIT_MB;
+        }
+        parent::__construct($name, $visiblename, $description, $defaultsetting);
+    }
+
+    /**
+     * Returns selectable units.
+     *
+     * @return  array
+     */
+    protected static function get_units(): array {
+        return [
+            self::UNIT_GB => get_string('sizegb'),
+            self::UNIT_MB => get_string('sizemb'),
+            self::UNIT_KB => get_string('sizekb'),
+            self::UNIT_B  => get_string('sizeb'),
+        ];
+    }
+
+    /**
+     * Converts bytes to some more user friendly string.
+     *
+     * @param   int     $bytes  The number of bytes we want to convert from
+     * @return  string
+     */
+    protected static function get_size_text(int $bytes): string {
+        if (empty($bytes)) {
+            return get_string('none');
+        }
+        return display_size($bytes);
+    }
+
+    /**
+     * Finds suitable units for given file size.
+     *
+     * @param   int     $bytes  The number of bytes
+     * @return  array           Parsed file size in the format of ['v' => value, 'u' => unit]
+     */
+    protected static function parse_bytes(int $bytes): array {
+        foreach (self::get_units() as $unit => $unused) {
+            if ($bytes % $unit === 0) {
+                return ['v' => (int)($bytes / $unit), 'u' => $unit];
+            }
+        }
+        return ['v' => (int)$bytes, 'u' => self::UNIT_B];
+    }
+
+    /**
+     * Get the selected file size as array.
+     *
+     * @return  array|null  An array containing 'v' => xx, 'u' => xx, or null if not set
+     */
+    public function get_setting(): ?array {
+        $bytes = $this->config_read($this->name);
+        if (is_null($bytes)) {
+            return null;
+        }
+
+        return self::parse_bytes($bytes);
+    }
+
+    /**
+     * Store the file size as bytes.
+     *
+     * @param   array   $data   Must be form 'h' => xx, 'm' => xx
+     * @return  string          The error string if any
+     */
+    public function write_setting($data): string {
+        if (!is_array($data)) {
+            return '';
+        }
+
+        if (!is_numeric($data['v']) || $data['v'] < 0) {
+            return get_string('errorsetting', 'admin');
+        }
+
+        $bytes = $data['v'] * $data['u'];
+
+        $result = $this->config_write($this->name, $bytes);
+        return ($result ? '' : get_string('errorsetting', 'admin'));
+    }
+
+    /**
+     * Returns file size text+select fields.
+     *
+     * @param   array   $data   The current setting value. Must be form 'v' => xx, 'u' => xx.
+     * @param   string  $query  Admin search query to be highlighted.
+     * @return  string          File size text+select fields and wrapping div(s).
+     */
+    public function output_html($data, $query = ''): string {
+        global $OUTPUT;
+
+        $default = $this->get_defaultsetting();
+        if (is_number($default)) {
+            $defaultinfo = self::get_size_text($default);
+        } else if (is_array($default)) {
+            $defaultinfo = self::get_size_text($default['v'] * $default['u']);
+        } else {
+            $defaultinfo = null;
+        }
+
+        $inputid = $this->get_id() . 'v';
+        $units = self::get_units();
+        $defaultunit = $this->defaultunit;
+
+        $context = (object) [
+            'id' => $this->get_id(),
+            'name' => $this->get_full_name(),
+            'value' => $data['v'],
+            'options' => array_map(function($unit, $title) use ($data, $defaultunit) {
+                return [
+                    'value' => $unit,
+                    'name' => $title,
+                    'selected' => ($data['v'] == 0 && $unit == $defaultunit) || $unit == $data['u']
+                ];
+            }, array_keys($units), $units)
+        ];
+
+        $element = $OUTPUT->render_from_template('core_admin/setting_configfilesize', $context);
+
+        return format_admin_setting($this, $this->visiblename, $element, $this->description, $inputid, '', $defaultinfo, $query);
+    }
+}
index 265119f..f4ab562 100644 (file)
@@ -652,7 +652,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
             echo "</label>\n";
         }
         if ($helpicon) {
-            echo '<span class="pull-xs-right text-nowrap">'.$helpicon.'</span>';
+            echo '<span class="float-sm-right text-nowrap">'.$helpicon.'</span>';
         }
         echo '</div>';
         if (isset($this->errors[$name])) {
index 4730c32..dc924f7 100644 (file)
@@ -275,4 +275,10 @@ preferences,moodle|/user/preferences.php|t/preferences',
     $temp->add(new admin_setting_configtextarea('additionalhtmlfooter', new lang_string('additionalhtmlfooter', 'admin'), new lang_string('additionalhtmlfooter_desc', 'admin'), '', PARAM_RAW));
     $ADMIN->add('appearance', $temp);
 
+    $setting = new admin_setting_configcheckbox('cachetemplates', new lang_string('cachetemplates', 'admin'),
+        new lang_string('cachetemplates_help', 'admin'), 1);
+    $setting->set_updatedcallback('template_reset_all_caches');
+    $temp = new admin_settingpage('templates', new lang_string('templates', 'admin'));
+    $temp->add($setting);
+    $ADMIN->add('appearance', $temp);
 } // end of speedup
index 564845b..1b91d6a 100644 (file)
@@ -1,4 +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/>.
+
+/**
+ * Adds security related settings links for security category to admin tree.
+ *
+ * @copyright  1999 Martin Dougiamas  http://dougiamas.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_admin\local\settings\filesize;
 
 if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
 
@@ -36,12 +61,9 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     // maxbytes set to 0 will allow the maximum server limit for uploads
     $temp->add(new admin_setting_configselect('maxbytes', new lang_string('maxbytes', 'admin'), new lang_string('configmaxbytes', 'admin'), 0, $max_upload_choices));
     // 100MB
-    $defaultuserquota = 104857600;
-    $params = new stdClass();
-    $params->bytes = $defaultuserquota;
-    $params->displaysize = display_size($defaultuserquota);
-    $temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'),
-                new lang_string('configuserquota', 'admin', $params), $defaultuserquota, PARAM_INT, 30));
+    $defaultuserquota = 100 * filesize::UNIT_MB;
+    $temp->add(new filesize('userquota', new lang_string('userquota', 'admin'),
+            new lang_string('userquota_desc', 'admin'), $defaultuserquota));
 
     $temp->add(new admin_setting_configcheckbox('allowobjectembed', new lang_string('allowobjectembed', 'admin'), new lang_string('configallowobjectembed', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('enabletrusttext', new lang_string('enabletrusttext', 'admin'), new lang_string('configenabletrusttext', 'admin'), 0));
index e5c660b..de5f75b 100644 (file)
@@ -48,7 +48,4 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
 
     $optionalsubsystems->add(new admin_setting_configcheckbox('allowstealth', new lang_string('allowstealthmodules'),
         new lang_string('allowstealthmodules_help'), 0, 1, 0));
-
-    $optionalsubsystems->add(new admin_setting_configcheckbox('enablecoursepublishing',
-        new lang_string('enablecoursepublishing', 'hub'), new lang_string('enablecoursepublishing_help', 'hub'), 0));
 }
index 901a38a..b9612cb 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 
-// This file defines settingpages and externalpages under the "users" category
+// This file defines settingpages and externalpages under the "users" category.
 
 $ADMIN->add('users', new admin_category('accounts', new lang_string('accounts', 'admin')));
 $ADMIN->add('users', new admin_category('roles', new lang_string('permissions', 'role')));
@@ -13,15 +13,47 @@ if ($hassiteconfig
  or has_capability('moodle/role:manage', $systemcontext)
  or has_capability('moodle/role:assign', $systemcontext)
  or has_capability('moodle/cohort:manage', $systemcontext)
- or has_capability('moodle/cohort:view', $systemcontext)) { // speedup for non-admins, add all caps used on this page
+ or has_capability('moodle/cohort:view', $systemcontext)) { // Speedup for non-admins, add all caps used on this page.
 
 
-    // stuff under the "accounts" subcategory
+    // Stuff under the "accounts" subcategory.
     $ADMIN->add('accounts', new admin_externalpage('editusers', new lang_string('userlist','admin'), "$CFG->wwwroot/$CFG->admin/user.php", array('moodle/user:update', 'moodle/user:delete')));
     $ADMIN->add('accounts', new admin_externalpage('userbulk', new lang_string('userbulk','admin'), "$CFG->wwwroot/$CFG->admin/user/user_bulk.php", array('moodle/user:update', 'moodle/user:delete')));
     $ADMIN->add('accounts', new admin_externalpage('addnewuser', new lang_string('addnewuser'), "$CFG->wwwroot/user/editadvanced.php?id=-1", 'moodle/user:create'));
 
-    // "User default preferences" settingpage.
+    // User management settingpage.
+    $temp = new admin_settingpage('usermanagement', new lang_string('usermanagement', 'admin'));
+    if ($ADMIN->fulltree) {
+        $choices = array();
+        $choices['realname'] = new lang_string('fullnameuser');
+        $choices['lastname'] = new lang_string('lastname');
+        $choices['firstname'] = new lang_string('firstname');
+        $choices['username'] = new lang_string('username');
+        $choices['email'] = new lang_string('email');
+        $choices['city'] = new lang_string('city');
+        $choices['country'] = new lang_string('country');
+        $choices['confirmed'] = new lang_string('confirmed', 'admin');
+        $choices['suspended'] = new lang_string('suspended', 'auth');
+        $choices['profile'] = new lang_string('profilefields', 'admin');
+        $choices['courserole'] = new lang_string('courserole', 'filters');
+        $choices['anycourses'] = new lang_string('anycourses', 'filters');
+        $choices['systemrole'] = new lang_string('globalrole', 'role');
+        $choices['cohort'] = new lang_string('idnumber', 'core_cohort');
+        $choices['firstaccess'] = new lang_string('firstaccess', 'filters');
+        $choices['lastaccess'] = new lang_string('lastaccess');
+        $choices['neveraccessed'] = new lang_string('neveraccessed', 'filters');
+        $choices['timemodified'] = new lang_string('lastmodified');
+        $choices['nevermodified'] = new lang_string('nevermodified', 'filters');
+        $choices['auth'] = new lang_string('authentication');
+        $choices['idnumber'] = new lang_string('idnumber');
+        $choices['lastip'] = new lang_string('lastip');
+        $choices['mnethostid'] = new lang_string('mnetidprovider', 'mnet');
+        $temp->add(new admin_setting_configmultiselect('userfiltersdefault', new lang_string('userfiltersdefault', 'admin'),
+            new lang_string('userfiltersdefault_desc', 'admin'), array('realname'), $choices));
+    }
+    $ADMIN->add('accounts', $temp);
+
+    // User default preferences settingpage.
     $temp = new admin_settingpage('userdefaultpreferences', new lang_string('userdefaultpreferences', 'admin'));
     if ($ADMIN->fulltree) {
         $choices = array();
@@ -62,9 +94,9 @@ if ($hassiteconfig
     $ADMIN->add('accounts', new admin_externalpage('cohorts', new lang_string('cohorts', 'cohort'), $CFG->wwwroot . '/cohort/index.php', array('moodle/cohort:manage', 'moodle/cohort:view')));
 
 
-    // stuff under the "roles" subcategory
+    // Stuff under the "roles" subcategory.
 
-    // "userpolicies" settingpage
+    // User policies settingpage.
     $temp = new admin_settingpage('userpolicies', new lang_string('userpolicies', 'admin'));
     if ($ADMIN->fulltree) {
         if (!during_initial_install()) {
@@ -138,7 +170,7 @@ if ($hassiteconfig
             $temp->add(new admin_setting_configselect('restorernewroleid', new lang_string('restorernewroleid', 'admin'),
                           new lang_string('restorernewroleid_help', 'admin'), $defaultteacherid, $restorersnewrole));
 
-            // release memory
+            // Release memory.
             unset($otherroles);
             unset($guestroles);
             unset($userroles);
@@ -209,7 +241,7 @@ if ($hassiteconfig
     $ADMIN->add('roles', new admin_externalpage('assignroles', new lang_string('assignglobalroles', 'role'), "$CFG->wwwroot/$CFG->admin/roles/assign.php?contextid=".$systemcontext->id, 'moodle/role:assign'));
     $ADMIN->add('roles', new admin_externalpage('checkpermissions', new lang_string('checkglobalpermissions', 'role'), "$CFG->wwwroot/$CFG->admin/roles/check.php?contextid=".$systemcontext->id, array('moodle/role:assign', 'moodle/role:safeoverride', 'moodle/role:override', 'moodle/role:manage')));
 
-} // end of speedup
+} // End of speedup.
 
 // Privacy settings.
 if ($hassiteconfig) {
diff --git a/admin/templates/setting_configfilesize.mustache b/admin/templates/setting_configfilesize.mustache
new file mode 100644 (file)
index 0000000..4716c6e
--- /dev/null
@@ -0,0 +1,50 @@
+{{!
+    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/>.
+}}
+{{!
+    @template core_admin/setting_configfilesize
+
+    Admin file size setting template.
+
+    Context variables required for this template:
+    * name - form element name
+    * options - list of options for units containing name, value, selected
+    * value - yes
+    * id - element id
+
+    Example context (json):
+    {
+        "name": "test",
+        "value": "5",
+        "id": "test0",
+        "options": [ { "name": "KB", "value": "1024", "selected": true } ]
+    }
+}}
+{{!
+    Setting configfilesize.
+}}
+<div class="form-filesize defaultsnext">
+    <div class="form-inline">
+        <input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr">
+        <label class="sr-only" for="{{id}}u">{{#str}}filesizeunits, admin{{/str}}</label>
+        <select id="{{id}}u" name="{{name}}[u]" class="form-control custom-select">
+            {{#options}}
+                <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
+            {{/options}}
+        </select>
+    </div>
+</div>
+
index 87a00b6..af027de 100644 (file)
@@ -48,13 +48,11 @@ class behat_admin extends behat_base {
      * @param TableNode $table
      */
     public function i_set_the_following_administration_settings_values(TableNode $table) {
-
         if (!$data = $table->getRowsHash()) {
             return;
         }
 
         foreach ($data as $label => $value) {
-
             $this->execute('behat_navigation::i_select_from_flat_navigation_drawer', [get_string('administrationsite')]);
 
             // Search by label.
@@ -76,38 +74,17 @@ class behat_admin extends behat_base {
                         "@id=//span[contains(normalize-space(.), $label)]/preceding-sibling::label[1]/@for]";
                 $fieldnode = $this->find('xpath', $fieldxpath, $exception);
 
-                $formfieldtypenode = $this->find('xpath', $fieldxpath .
-                        "/ancestor::div[contains(concat(' ', @class, ' '), ' form-setting ')]" .
-                        "/child::div[contains(concat(' ', @class, ' '),  ' form-')]/child::*/parent::div");
-
             } catch (ElementNotFoundException $e) {
-
                 // Multi element settings, interacting only the first one.
                 $fieldxpath = "//*[label[contains(., $label)]|span[contains(., $label)]]" .
                         "/ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' form-item ')]" .
                         "/descendant::div[contains(concat(' ', @class, ' '), ' form-group ')]" .
                         "/descendant::*[self::input | self::textarea | self::select]" .
                         "[not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]";
-                $fieldnode = $this->find('xpath', $fieldxpath);
-
-                // It is the same one that contains the type.
-                $formfieldtypenode = $fieldnode;
-            }
-
-            // Getting the class which contains the field type.
-            $classes = explode(' ', $formfieldtypenode->getAttribute('class'));
-            $type = false;
-            foreach ($classes as $class) {
-                if (substr($class, 0, 5) == 'form-') {
-                    $type = substr($class, 5);
-                }
             }
 
-            // Instantiating the appropiate field type.
-            $field = behat_field_manager::get_field_instance($type, $fieldnode, $this->getSession());
-            $field->set_value($value);
-
-            $this->find_button(get_string('savechanges'))->press();
+            $this->execute('behat_forms::i_set_the_field_with_xpath_to', [$fieldxpath, $value]);
+            $this->execute("behat_general::i_click_on", [get_string('savechanges'), 'button']);
         }
     }
 
diff --git a/admin/tests/behat/filter_users_settings.feature b/admin/tests/behat/filter_users_settings.feature
new file mode 100644 (file)
index 0000000..6916be6
--- /dev/null
@@ -0,0 +1,150 @@
+@core @core_admin @javascript
+Feature: An administrator can configure the available user list filters
+  In order to have all needed user filters instantly at hand
+  As an admin
+  I want to configure the filters which are shown as default
+
+  Scenario: Do not change the user filter default configuration
+    When I log in as "admin"
+    And I navigate to "Users > Accounts > Browse list of users" in site administration
+    Then I should see "User full name"
+    And I should not see "Surname" in the "New filter" "fieldset"
+    And I should not see "Firstname" in the "New filter" "fieldset"
+    And I should not see "Username" in the "New filter" "fieldset"
+    And I should not see "Email address" in the "New filter" "fieldset"
+    And I should not see "City/town" in the "New filter" "fieldset"
+    And I should not see "Country" in the "New filter" "fieldset"
+    And I should not see "Confirmed" in the "New filter" "fieldset"
+    And I should not see "Suspended account" in the "New filter" "fieldset"
+    And I should not see "User profile fields" in the "New filter" "fieldset"
+    And I should not see "Course role" in the "New filter" "fieldset"
+    And I should not see "Enrolled in any course" in the "New filter" "fieldset"
+    And I should not see "System role" in the "New filter" "fieldset"
+    And I should not see "Cohort ID" in the "New filter" "fieldset"
+    And I should not see "First access" in the "New filter" "fieldset"
+    And I should not see "Last access" in the "New filter" "fieldset"
+    And I should not see "Last modified" in the "New filter" "fieldset"
+    And I should not see "Authentication" in the "New filter" "fieldset"
+    And I should not see "ID number" in the "New filter" "fieldset"
+    And I should not see "Last IP address" in the "New filter" "fieldset"
+    And I should not see "MNet ID provider" in the "New filter" "fieldset"
+    And I navigate to "Users > Accounts > Bulk user actions" in site administration
+    Then I should see "User full name"
+    And I should not see "Surname" in the "New filter" "fieldset"
+    And I should not see "Firstname" in the "New filter" "fieldset"
+    And I should not see "Username" in the "New filter" "fieldset"
+    And I should not see "Email address" in the "New filter" "fieldset"
+    And I should not see "City/town" in the "New filter" "fieldset"
+    And I should not see "Country" in the "New filter" "fieldset"
+    And I should not see "Confirmed" in the "New filter" "fieldset"
+    And I should not see "Suspended account" in the "New filter" "fieldset"
+    And I should not see "User profile fields" in the "New filter" "fieldset"
+    And I should not see "Course role" in the "New filter" "fieldset"
+    And I should not see "Enrolled in any course" in the "New filter" "fieldset"
+    And I should not see "System role" in the "New filter" "fieldset"
+    And I should not see "Cohort ID" in the "New filter" "fieldset"
+    And I should not see "First access" in the "New filter" "fieldset"
+    And I should not see "Last access" in the "New filter" "fieldset"
+    And I should not see "Last modified" in the "New filter" "fieldset"
+    And I should not see "Authentication" in the "New filter" "fieldset"
+    And I should not see "ID number" in the "New filter" "fieldset"
+    And I should not see "Last IP address" in the "New filter" "fieldset"
+    And I should not see "MNet ID provider" in the "New filter" "fieldset"
+
+  Scenario: Change the user filter default configuration to something else
+    Given the following config values are set as admin:
+      | userfiltersdefault | realname,username,email |
+    And I log in as "admin"
+    And I navigate to "Users > Accounts > Browse list of users" in site administration
+    Then I should see "User full name"
+    And I should not see "Surname" in the "New filter" "fieldset"
+    And I should not see "Firstname" in the "New filter" "fieldset"
+    And I should see "Username" in the "New filter" "fieldset"
+    And I should see "Email address" in the "New filter" "fieldset"
+    And I should not see "City/town" in the "New filter" "fieldset"
+    And I should not see "Country" in the "New filter" "fieldset"
+    And I should not see "Confirmed" in the "New filter" "fieldset"
+    And I should not see "Suspended account" in the "New filter" "fieldset"
+    And I should not see "User profile fields" in the "New filter" "fieldset"
+    And I should not see "Course role" in the "New filter" "fieldset"
+    And I should not see "Enrolled in any course" in the "New filter" "fieldset"
+    And I should not see "System role" in the "New filter" "fieldset"
+    And I should not see "Cohort ID" in the "New filter" "fieldset"
+    And I should not see "First access" in the "New filter" "fieldset"
+    And I should not see "Last access" in the "New filter" "fieldset"
+    And I should not see "Last modified" in the "New filter" "fieldset"
+    And I should not see "Authentication" in the "New filter" "fieldset"
+    And I should not see "ID number" in the "New filter" "fieldset"
+    And I should not see "Last IP address" in the "New filter" "fieldset"
+    And I should not see "MNet ID provider" in the "New filter" "fieldset"
+    And I navigate to "Users > Accounts > Bulk user actions" in site administration
+    Then I should see "User full name"
+    And I should not see "Surname" in the "New filter" "fieldset"
+    And I should not see "Firstname" in the "New filter" "fieldset"
+    And I should see "Username" in the "New filter" "fieldset"
+    And I should see "Email address" in the "New filter" "fieldset"
+    And I should not see "City/town" in the "New filter" "fieldset"
+    And I should not see "Country" in the "New filter" "fieldset"
+    And I should not see "Confirmed" in the "New filter" "fieldset"
+    And I should not see "Suspended account" in the "New filter" "fieldset"
+    And I should not see "User profile fields" in the "New filter" "fieldset"
+    And I should not see "Course role" in the "New filter" "fieldset"
+    And I should not see "Enrolled in any course" in the "New filter" "fieldset"
+    And I should not see "System role" in the "New filter" "fieldset"
+    And I should not see "Cohort ID" in the "New filter" "fieldset"
+    And I should not see "First access" in the "New filter" "fieldset"
+    And I should not see "Last access" in the "New filter" "fieldset"
+    And I should not see "Last modified" in the "New filter" "fieldset"
+    And I should not see "Authentication" in the "New filter" "fieldset"
+    And I should not see "ID number" in the "New filter" "fieldset"
+    And I should not see "Last IP address" in the "New filter" "fieldset"
+    And I should not see "MNet ID provider" in the "New filter" "fieldset"
+
+  Scenario: Change the user filter default configuration to no filter at all
+    Given the following config values are set as admin:
+      | userfiltersdefault | |
+    And I log in as "admin"
+    And I navigate to "Users > Accounts > Browse list of users" in site administration
+    Then I should see "User full name"
+    And I should not see "Surname" in the "New filter" "fieldset"
+    And I should not see "Firstname" in the "New filter" "fieldset"
+    And I should not see "Username" in the "New filter" "fieldset"
+    And I should not see "Email address" in the "New filter" "fieldset"
+    And I should not see "City/town" in the "New filter" "fieldset"
+    And I should not see "Country" in the "New filter" "fieldset"
+    And I should not see "Confirmed" in the "New filter" "fieldset"
+    And I should not see "Suspended account" in the "New filter" "fieldset"
+    And I should not see "User profile fields" in the "New filter" "fieldset"
+    And I should not see "Course role" in the "New filter" "fieldset"
+    And I should not see "Enrolled in any course" in the "New filter" "fieldset"
+    And I should not see "System role" in the "New filter" "fieldset"
+    And I should not see "Cohort ID" in the "New filter" "fieldset"
+    And I should not see "First access" in the "New filter" "fieldset"
+    And I should not see "Last access" in the "New filter" "fieldset"
+    And I should not see "Last modified" in the "New filter" "fieldset"
+    And I should not see "Authentication" in the "New filter" "fieldset"
+    And I should not see "ID number" in the "New filter" "fieldset"
+    And I should not see "Last IP address" in the "New filter" "fieldset"
+    And I should not see "MNet ID provider" in the "New filter" "fieldset"
+    And I navigate to "Users > Accounts > Bulk user actions" in site administration
+    Then I should see "User full name"
+    And I should not see "Surname" in the "New filter" "fieldset"
+    And I should not see "Firstname" in the "New filter" "fieldset"
+    And I should not see "Username" in the "New filter" "fieldset"
+    And I should not see "Email address" in the "New filter" "fieldset"
+    And I should not see "City/town" in the "New filter" "fieldset"
+    And I should not see "Country" in the "New filter" "fieldset"
+    And I should not see "Confirmed" in the "New filter" "fieldset"
+    And I should not see "Suspended account" in the "New filter" "fieldset"
+    And I should not see "User profile fields" in the "New filter" "fieldset"
+    And I should not see "Course role" in the "New filter" "fieldset"
+    And I should not see "Enrolled in any course" in the "New filter" "fieldset"
+    And I should not see "System role" in the "New filter" "fieldset"
+    And I should not see "Cohort ID" in the "New filter" "fieldset"
+    And I should not see "First access" in the "New filter" "fieldset"
+    And I should not see "Last access" in the "New filter" "fieldset"
+    And I should not see "Last modified" in the "New filter" "fieldset"
+    And I should not see "Authentication" in the "New filter" "fieldset"
+    And I should not see "ID number" in the "New filter" "fieldset"
+    And I should not see "Last IP address" in the "New filter" "fieldset"
+    And I should not see "MNet ID provider" in the "New filter" "fieldset"
index d30e230..dc1cd94 100644 (file)
@@ -9,6 +9,7 @@ Feature: Manage tokens
     | username  | password  | firstname | lastname |
     | testuser  | testuser  | Joe | Bloggs |
     | testuser2 | testuser2 | TestFirstname | TestLastname |
+    And I change window size to "small"
     And I log in as "admin"
     And I am on site homepage
 
index 5de8e0a..e3ffc92 100644 (file)
@@ -20,12 +20,12 @@ Feature: Edit capabilities
     And I set the following system permissions of "Teacher" role:
       | capability | permission |
       | block/mnet_hosts:myaddinstance | Allow |
-      | moodle/community:add | Inherit |
+      | moodle/site:messageanyuser | Inherit |
       | moodle/grade:managesharedforms | Prevent |
       | moodle/course:request | Prohibit |
     When I follow "Edit Teacher role"
     Then "block/mnet_hosts:myaddinstance" capability has "Allow" permission
-    And "moodle/community:add" capability has "Not set" permission
+    And "moodle/site:messageanyuser" capability has "Not set" permission
     And "moodle/grade:managesharedforms" capability has "Prevent" permission
     And "moodle/course:request" capability has "Prohibit" permission
 
index dde7c3e..30a9628 100644 (file)
@@ -114,15 +114,17 @@ class tool_capability_renderer extends plugin_renderer_base {
         }
 
         // Start the list item, and print the context name as a link to the place to make changes.
-        if ($contextid == context_system::instance()->id) {
+        $context = context::instance_by_id($contextid);
+
+        if ($context instanceof context_system) {
             $url = new moodle_url('/admin/roles/manage.php');
-            $title = get_string('changeroles', 'tool_capability');
         } else {
-            $url = new moodle_url('/admin/roles/override.php', array('contextid' => $contextid));
-            $title = get_string('changeoverrides', 'tool_capability');
+            $url = new moodle_url('/admin/roles/permissions.php', ['contextid' => $contextid]);
         }
-        $context = context::instance_by_id($contextid);
-        $html = $this->output->heading(html_writer::link($url, $context->get_context_name(), array('title' => $title)), 3);
+
+        $title = get_string('permissionsincontext', 'core_role', $context->get_context_name());
+
+        $html = $this->output->heading(html_writer::link($url, $title), 3);
         $html .= html_writer::table($table);
         // If there are any child contexts, print them recursively.
         if (!empty($contexts[$contextid]->children)) {
@@ -133,4 +135,4 @@ class tool_capability_renderer extends plugin_renderer_base {
         return $html;
     }
 
-}
\ No newline at end of file
+}
index fd8cb9c..867ec94 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencies.min.js and b/admin/tool/lp/amd/build/competencies.min.js differ
index aa66f0b..b7aa8c0 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencies.min.js.map and b/admin/tool/lp/amd/build/competencies.min.js.map differ
index 5314ff2..838c624 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencyactions.min.js and b/admin/tool/lp/amd/build/competencyactions.min.js differ
index baafad8..3bdbdc7 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencyactions.min.js.map and b/admin/tool/lp/amd/build/competencyactions.min.js.map differ
index e9f3dff..ec38fee 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencypicker.min.js and b/admin/tool/lp/amd/build/competencypicker.min.js differ
index 822d4ea..44b0812 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencypicker.min.js.map and b/admin/tool/lp/amd/build/competencypicker.min.js.map differ
index dce39b1..496c31d 100644 (file)
Binary files a/admin/tool/lp/amd/build/course_competency_settings.min.js and b/admin/tool/lp/amd/build/course_competency_settings.min.js differ
index 329c557..9ab4d54 100644 (file)
Binary files a/admin/tool/lp/amd/build/course_competency_settings.min.js.map and b/admin/tool/lp/amd/build/course_competency_settings.min.js.map differ
index 9c0ecbd..806eeeb 100644 (file)
Binary files a/admin/tool/lp/amd/build/grade_dialogue.min.js and b/admin/tool/lp/amd/build/grade_dialogue.min.js differ
index 6f0202f..6af7b51 100644 (file)
Binary files a/admin/tool/lp/amd/build/grade_dialogue.min.js.map and b/admin/tool/lp/amd/build/grade_dialogue.min.js.map differ
index 4d52c20..ade6899 100644 (file)
Binary files a/admin/tool/lp/amd/build/grade_user_competency_inline.min.js and b/admin/tool/lp/amd/build/grade_user_competency_inline.min.js differ
index 4459d47..1095c0f 100644 (file)
Binary files a/admin/tool/lp/amd/build/grade_user_competency_inline.min.js.map and b/admin/tool/lp/amd/build/grade_user_competency_inline.min.js.map differ
index e3a5176..82052a4 100644 (file)
@@ -27,8 +27,9 @@ define(['jquery',
         'core/templates',
         'core/str',
         'tool_lp/competencypicker',
-        'tool_lp/dragdrop-reorder'],
-       function($, notification, ajax, templates, str, Picker, dragdrop) {
+        'tool_lp/dragdrop-reorder',
+        'core/pending'],
+       function($, notification, ajax, templates, str, Picker, dragdrop, Pending) {
 
     /**
      * Constructor
@@ -117,6 +118,7 @@ define(['jquery',
      * Pick a competency
      *
      * @method pickCompetency
+     * @return {Promise}
      */
     competencies.prototype.pickCompetency = function() {
         var self = this;
@@ -132,6 +134,7 @@ define(['jquery',
             self.pickerInstance = new Picker(self.pageContextId, false, pageContextIncludes);
             self.pickerInstance.on('save', function(e, data) {
                 var compIds = data.competencyIds;
+                var pendingPromise = new Pending();
 
                 if (self.itemtype === "course") {
                     requests = [];
@@ -181,17 +184,20 @@ define(['jquery',
                     pagerender = 'tool_lp/plan_page';
                     pageregion = 'plan-page';
                 }
-                ajax.call(requests)[requests.length - 1].then(function(context) {
+                ajax.call(requests)[requests.length - 1]
+                .then(function(context) {
                     return templates.render(pagerender, context);
-                }).then(function(html, js) {
-                    $('[data-region="' + pageregion + '"]').replaceWith(html);
-                    templates.runTemplateJS(js);
+                })
+                .then(function(html, js) {
+                    templates.replaceNode($('[data-region="' + pageregion + '"]'), html, js);
                     return;
-                }).catch(notification.exception);
+                })
+                .then(pendingPromise.resolve)
+                .catch(notification.exception);
             });
         }
 
-        self.pickerInstance.display();
+        return self.pickerInstance.display();
     };
 
     /**
@@ -302,6 +308,7 @@ define(['jquery',
         if (localthis.itemtype == 'course') {
             // Course completion rule handling.
             $('[data-region="coursecompetenciespage"]').on('change', 'select[data-field="ruleoutcome"]', function(e) {
+                var pendingPromise = new Pending();
                 var requests = [];
                 var pagerender = 'tool_lp/course_competencies_page';
                 var pageregion = 'coursecompetenciespage';
@@ -314,18 +321,24 @@ define(['jquery',
                       args: {courseid: localthis.itemid, moduleid: 0}}
                 ]);
 
-                requests[1].done(function(context) {
-                    templates.render(pagerender, context).done(function(html, js) {
-                        $('[data-region="' + pageregion + '"]').replaceWith(html);
-                        templates.runTemplateJS(js);
-                    }).fail(notification.exception);
-                }).fail(notification.exception);
+                requests[1].then(function(context) {
+                    return templates.render(pagerender, context);
+                })
+                .then(function(html, js) {
+                    return templates.replaceNode($('[data-region="' + pageregion + '"]'), html, js);
+                })
+                .then(pendingPromise.resolve)
+                .catch(notification.exception);
             });
         }
 
         $('[data-region="actions"] button').click(function(e) {
+            var pendingPromise = new Pending();
             e.preventDefault();
-            localthis.pickCompetency();
+
+            localthis.pickCompetency()
+                .then(pendingPromise.resolve)
+                .catch();
         });
         $('[data-action="delete-competency-link"]').click(function(e) {
             e.preventDefault();
index 60c04da..78a7cd3 100644 (file)
@@ -33,8 +33,12 @@ define(['jquery',
         'tool_lp/menubar',
         'tool_lp/competencypicker',
         'tool_lp/competency_outcomes',
-        'tool_lp/competencyruleconfig'],
-       function($, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig) {
+        'tool_lp/competencyruleconfig',
+        'core/pending',
+        ],
+       function(
+            $, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig, Pending
+        ) {
 
     // Private variables and functions.
     /** @var {Object} treeModel - This is an object representing the nodes in the tree. */
@@ -412,6 +416,7 @@ define(['jquery',
         if (!pickerInstance) {
             pickerInstance = new Picker(pageContextId, relatedTarget.competencyframeworkid);
             pickerInstance.on('save', function(e, data) {
+                var pendingPromise = new Pending();
                 var compIds = data.competencyIds;
 
                 var calls = [];
@@ -436,7 +441,9 @@ define(['jquery',
                     templates.runTemplateJS(js);
                     updatedRelatedCompetencies();
                     return;
-                }).catch(notification.exception);
+                })
+                .then(pendingPromise.resolve)
+                .catch(notification.exception);
             });
         }
 
index 23dce8a..c3cb0b1 100644 (file)
@@ -31,8 +31,10 @@ define(['jquery',
         'core/templates',
         'tool_lp/dialogue',
         'core/str',
-        'tool_lp/tree'],
-        function($, Notification, Ajax, Templates, Dialogue, Str, Tree) {
+        'tool_lp/tree',
+        'core/pending'
+        ],
+        function($, Notification, Ajax, Templates, Dialogue, Str, Tree, Pending) {
 
     /**
      * Competency picker class.
@@ -157,6 +159,7 @@ define(['jquery',
         // Add listener for add.
         self._find('[data-region="competencylinktree"] [data-action="add"]').click(function(e) {
             e.preventDefault();
+            var pendingPromise = new Pending();
             if (!self._selectedCompetencies.length) {
                 return;
             }
@@ -168,7 +171,10 @@ define(['jquery',
                 self._trigger('save', {competencyId: self._selectedCompetencies[0]});
             }
 
+            // The dialogue here is a YUI dialogue and doesn't support Promises at all.
+            // However, it is typically synchronous so this shoudl suffice.
             self.close();
+            pendingPromise.resolve();
         });
 
         // The list of selected competencies will be modified while looping (because of the listeners above).
index ff21ade..fd497d2 100644 (file)
@@ -26,8 +26,10 @@ define(['jquery',
         'tool_lp/dialogue',
         'core/str',
         'core/ajax',
-        'core/templates'],
-       function($, notification, Dialogue, str, ajax, templates) {
+        'core/templates',
+        'core/pending'
+        ],
+       function($, notification, Dialogue, str, ajax, templates, Pending) {
 
     /**
      * Constructor
@@ -48,6 +50,7 @@ define(['jquery',
      * @method configureSettings
      */
     settingsMod.prototype.configureSettings = function(e) {
+        var pendingPromise = new Pending();
         var courseid = $(e.target).closest('a').data('courseid');
         var currentValue = $(e.target).closest('a').data('pushratingstouserplans');
         var context = {
@@ -56,16 +59,21 @@ define(['jquery',
         };
         e.preventDefault();
 
-        templates.render('tool_lp/course_competency_settings', context).done(function(html) {
-            str.get_string('configurecoursecompetencysettings', 'tool_lp').done(function(title) {
-                this._dialogue = new Dialogue(
-                    title,
-                    html,
-                    this.addListeners.bind(this)
-                );
-            }.bind(this)).fail(notification.exception);
-        }.bind(this)).fail(notification.exception);
-
+        $.when(
+            str.get_string('configurecoursecompetencysettings', 'tool_lp'),
+            templates.render('tool_lp/course_competency_settings', context),
+        )
+        .then(function(title, templateResult) {
+            this._dialogue = new Dialogue(
+                title,
+                templateResult[0],
+                this.addListeners.bind(this)
+            );
+
+            return this._dialogue;
+        }.bind(this))
+        .then(pendingPromise.resolve)
+        .catch(notification.exception);
     };
 
     /**
@@ -108,6 +116,7 @@ define(['jquery',
      * @method saveSettings
      */
     settingsMod.prototype.saveSettings = function(e) {
+        var pendingPromise = new Pending();
         e.preventDefault();
 
         var newValue = this._find('input[name="pushratingstouserplans"]:checked').val();
@@ -117,9 +126,12 @@ define(['jquery',
         ajax.call([
             {methodname: 'core_competency_update_course_competency_settings',
               args: {courseid: courseId, settings: settings}}
-        ])[0].done(function() {
-            this.refreshCourseCompetenciesPage();
-        }.bind(this)).fail(notification.exception);
+        ])[0]
+        .then(function() {
+            return this.refreshCourseCompetenciesPage();
+        }.bind(this))
+        .then(pendingPromise.resolve)
+        .catch(notification.exception);
 
     };
 
@@ -131,18 +143,23 @@ define(['jquery',
      */
     settingsMod.prototype.refreshCourseCompetenciesPage = function() {
         var courseId = this._find('input[name="courseid"]').val();
+        var pendingPromise = new Pending();
 
         ajax.call([
             {methodname: 'tool_lp_data_for_course_competencies_page',
               args: {courseid: courseId, moduleid: 0}}
-        ])[0].done(function(context) {
-            templates.render('tool_lp/course_competencies_page', context).done(function(html, js) {
-                $('[data-region="coursecompetenciespage"]').replaceWith(html);
-                templates.runTemplateJS(js);
-                this._dialogue.close();
-            }.bind(this)).fail(notification.exception);
-        }.bind(this)).fail(notification.exception);
-
+        ])[0]
+        .then(function(context) {
+            return templates.render('tool_lp/course_competencies_page', context);
+        })
+        .then(function(html, js) {
+            templates.replaceNode($('[data-region="coursecompetenciespage"]'), html, js);
+            this._dialogue.close();
+
+            return;
+        }.bind(this))
+        .then(pendingPromise.resolve)
+        .catch(notification.exception);
     };
 
     return /** @alias module:tool_lp/configurecoursecompetencysettings */ settingsMod;
index 576b511..80ef97c 100644 (file)
@@ -102,15 +102,20 @@ define(['jquery',
      * @return {Promise}
      */
     Grade.prototype.display = function() {
-        return this._render().then(function(html) {
-            return Str.get_string('rate', 'tool_lp').then(function(title) {
-                this._popup = new Dialogue(
-                    title,
-                    html,
-                    this._afterRender.bind(this)
-                );
-            }.bind(this));
-        }.bind(this)).fail(Notification.exception);
+        return $.when(
+            Str.get_string('rate', 'tool_lp'),
+            this._render()
+        )
+        .then(function(title, templateResult) {
+            this._popup = new Dialogue(
+                title,
+                templateResult[0],
+                this._afterRender.bind(this)
+            );
+
+            return this._popup;
+        }.bind(this))
+        .catch(Notification.exception);
     };
 
     /**
index 7a030b6..81e4f35 100644 (file)
@@ -95,7 +95,7 @@ define(['jquery',
             self = this;
 
         var promise = ScaleValues.get_values(self._scaleId);
-        promise.done(function(scalevalues) {
+        promise.then(function(scalevalues) {
             options.push({
                 value: '',
                 name: self._chooseStr
@@ -109,8 +109,13 @@ define(['jquery',
                 });
             }
 
-            self._dialogue = new GradeDialogue(options);
-            self._dialogue.on('rated', function(e, data) {
+            return options;
+        })
+        .then(function(options) {
+            return new GradeDialogue(options);
+        })
+        .then(function(dialogue) {
+            dialogue.on('rated', function(e, data) {
                 var args = self._args;
                 args.grade = data.rating;
                 args.note = data.note;
@@ -123,7 +128,15 @@ define(['jquery',
                     fail: notification.exception
                 }]);
             });
-        }).fail(notification.exception);
+
+            return dialogue;
+        })
+        .then(function(dialogue) {
+            self._dialogue = dialogue;
+
+            return;
+        })
+        .fail(notification.exception);
     };
 
     /** @type {Number} The scale id for this competency. */
index df34c22..168a028 100644 (file)
@@ -6,6 +6,7 @@ Feature: Manage competency frameworks
 
   Background:
     Given I log in as "admin"
+    And I change window size to "small"
     And I am on site homepage
 
   Scenario: Create a new framework
index 0156243..a5386ff 100644 (file)
@@ -6,6 +6,7 @@ Feature: Manage plearning plan templates
 
   Background:
     Given I log in as "admin"
+    And I change window size to "small"
     And I am on site homepage
 
   Scenario: Create a new learning plan template
index 1711f5e..8daab15 100644 (file)
@@ -105,7 +105,7 @@ $string['requirevalidation'] = 'Validate sender address';
 $string['name'] = 'Name';
 $string['ssl'] = 'SSL (Auto-detect SSL version)';
 $string['sslv2'] = 'SSLv2 (Force SSL Version 2)';
-$string['sslv3'] = 'SSLv2 (Force SSL Version 3)';
+$string['sslv3'] = 'SSLv3 (Force SSL Version 3)';
 $string['taskcleanup'] = 'Cleanup of unverified incoming email';
 $string['taskpickup'] = 'Incoming email pickup';
 $string['tls'] = 'TLS (TLS; started via protocol-level negotiation over unencrypted channel; RECOMMENDED way of initiating secure connection)';
index 02027bd..978f950 100644 (file)
@@ -414,6 +414,7 @@ class api {
                 'NoDelegate_CoreRating' => new lang_string('ratings', 'rating'),
                 'NoDelegate_CoreTag' => new lang_string('tags'),
                 '$mmLoginEmailSignup' => new lang_string('startsignup'),
+                'NoDelegate_ForgottenPassword' => new lang_string('forgotten'),
                 'NoDelegate_ResponsiveMainMenuItems' => new lang_string('responsivemainmenuitems', 'tool_mobile'),
             ),
             "$mainmenu" => array(
index e9d1c67..73c81ff 100644 (file)
@@ -369,12 +369,12 @@ class external extends external_api {
 
     /**
      * Returns a piece of content to be displayed in the Mobile app, it usually returns a template, javascript and
-     * other structured data that will be used to render a view in the Mobile app..
+     * other structured data that will be used to render a view in the Mobile app.
      *
      * Callbacks (placed in \$component\output\mobile) that are called by this web service are responsible for doing the
      * appropriate security checks to access the information to be returned.
      *
-     * @param string $component fame of the component.
+     * @param string $component name of the component.
      * @param string $method function method name in class \$component\output\mobile.
      * @param array $args optional arguments for the method.
      * @return array HTML, JavaScript and other required data and information to create a view in the app.
@@ -423,6 +423,7 @@ class external extends external_api {
             'otherdata'  => $otherdata,
             'files'      => !empty($result['files']) ? $result['files'] : array(),
             'restrict'   => !empty($result['restrict']) ? $result['restrict'] : array(),
+            'disabled'   => !empty($result['disabled']) ? true : false,
         );
     }
 
@@ -465,7 +466,8 @@ class external extends external_api {
                         ),
                     ),
                     'Restrict this content to certain users or courses.'
-                )
+                ),
+                'disabled' => new external_value(PARAM_BOOL, 'Whether we consider this disabled or not.', VALUE_OPTIONAL),
             )
         );
     }
index 0b6ba81..b02ff9e 100644 (file)
@@ -58,7 +58,7 @@ $string['downloadcourse'] = 'Download course';
 $string['downloadcourses'] = 'Download courses';
 $string['enablesmartappbanners'] = 'Enable App Banners';
 $string['enablesmartappbanners_desc'] = 'If enabled, a banner promoting the mobile app will be displayed when accessing the site using a mobile browser.';
-$string['forcedurlscheme'] = 'If you want to allow only your custom branded app to be opened via a browser window, then specify its URL scheme here; otherwise leave the field empty.';
+$string['forcedurlscheme'] = 'If you want to allow only your custom branded app to be opened via a browser window, then specify its URL scheme here. If you want to allow only the official app, then set the default value. Leave the field empty if you want to allow any app.';
 $string['forcedurlscheme_key'] = 'URL scheme';
 $string['forcelogout'] = 'Force log out';
 $string['forcelogout_desc'] = 'If enabled, the app option \'Change site\' is replaced by \'Log out\'. This results in the user being completely logged out. They must then re-enter their password the next time they wish to access the site.';
@@ -82,7 +82,7 @@ $string['mobileappearance'] = 'Mobile appearance';
 $string['mobileauthentication'] = 'Mobile authentication';
 $string['mobilecssurl'] = 'CSS';
 $string['mobilefeatures'] = 'Mobile features';
-$string['mobilenotificationsdisabledwarning'] = 'Mobile notifications are not enabled. They should be enabled in Manage message outputs.';
+$string['mobilenotificationsdisabledwarning'] = 'Mobile notifications are not enabled. They should be enabled in Notification settings.';
 $string['mobilesettings'] = 'Mobile settings';
 $string['offlineuse'] = 'Offline use';
 $string['pluginname'] = 'Moodle app tools';
index 1e74e37..2c5fc18 100644 (file)
@@ -30,7 +30,7 @@ require_once($CFG->libdir . '/externallib.php');
 
 $serviceshortname  = required_param('service',  PARAM_ALPHANUMEXT);
 $passport          = required_param('passport',  PARAM_RAW);    // Passport send from the app to validate the response URL.
-$urlscheme         = optional_param('urlscheme', 'moodlemobile', PARAM_NOTAGS); // The URL scheme the app supports.
+$urlscheme         = optional_param('urlscheme', 'moodlemobile', PARAM_ALPHANUM); // The URL scheme the app supports.
 $confirmed         = optional_param('confirmed', false, PARAM_BOOL);  // If we are being redirected after user confirmation.
 $oauthsso          = optional_param('oauthsso', 0, PARAM_INT); // Id of the OpenID issuer (for OAuth direct SSO).
 
index 5a313ed..b5033d8 100644 (file)
@@ -63,7 +63,7 @@ if ($hassiteconfig) {
 
         $temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme',
                     new lang_string('forcedurlscheme_key', 'tool_mobile'),
-                    new lang_string('forcedurlscheme', 'tool_mobile'), '', PARAM_NOTAGS));
+                    new lang_string('forcedurlscheme', 'tool_mobile'), 'moodlemobile', PARAM_ALPHANUM));
 
         $ADMIN->add('mobileapp', $temp);
 
index 6bfea7f..955fbb9 100644 (file)
@@ -371,6 +371,19 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals(array(1, 2), $result['restrict']['users']);
         $this->assertEquals(array(3, 4), $result['restrict']['courses']);
         $this->assertEmpty($result['files']);
+        $this->assertFalse($result['disabled']);
+    }
+
+    /**
+     * Test get_content disabled.
+     */
+    public function test_get_content_disabled() {
+
+        $paramval = 16;
+        $result = external::get_content('tool_mobile', 'test_view_disabled',
+            array(array('name' => 'param1', 'value' => $paramval)));
+        $result = external_api::clean_returnvalue(external::get_content_returns(), $result);
+        $this->assertTrue($result['disabled']);
     }
 
     /**
index d803743..451b00f 100644 (file)
@@ -38,7 +38,6 @@ class mobile {
     /**
      * Returns a test view.
      * @param  array $args Arguments from tool_mobile_get_content WS
-     *
      * @return array       HTML, javascript and otherdata
      */
     public static function test_view($args) {
@@ -57,4 +56,27 @@ class mobile {
             'files' => array()
         );
     }
+
+    /**
+     * Returns a test view disabled.
+     * @param  array $args Arguments from tool_mobile_get_content WS
+     * @return array       HTML, javascript and otherdata
+     */
+    public static function test_view_disabled($args) {
+        $args = (object) $args;
+
+        return array(
+            'templates' => array(
+                array(
+                    'id' => 'main',
+                    'html' => 'The HTML code',
+                ),
+            ),
+            'javascript' => 'alert();',
+            'otherdata' => array('otherdata1' => $args->param1),
+            'restrict' => array('users' => array(1, 2), 'courses' => array(3, 4)),
+            'files' => array(),
+            'disabled' => true,
+        );
+    }
 }
index ec37326..dcf16e4 100644 (file)
@@ -1039,6 +1039,7 @@ if ($formdata = $mform2->is_cancelled()) {
                 if ($roleid) {
                     // Find duration and/or enrol status.
                     $timeend = 0;
+                    $timestart = $today;
                     $status = null;
 
                     if (isset($user->{'enrolstatus'.$i})) {
@@ -1054,16 +1055,23 @@ if ($formdata = $mform2->is_cancelled()) {
                         }
                     }
 
+                    if (!empty($user->{'enroltimestart'.$i})) {
+                        $parsedtimestart = strtotime($user->{'enroltimestart'.$i});
+                        if ($parsedtimestart !== false) {
+                            $timestart = $parsedtimestart;
+                        }
+                    }
+
                     if (!empty($user->{'enrolperiod'.$i})) {
                         $duration = (int)$user->{'enrolperiod'.$i} * 60*60*24; // convert days to seconds
                         if ($duration > 0) { // sanity check
-                            $timeend = $today + $duration;
+                            $timeend = $timestart + $duration;
                         }
                     } else if ($manualcache[$courseid]->enrolperiod > 0) {
-                        $timeend = $today + $manualcache[$courseid]->enrolperiod;
+                        $timeend = $timestart + $manualcache[$courseid]->enrolperiod;
                     }
 
-                    $manual->enrol_user($manualcache[$courseid], $user->id, $roleid, $today, $timeend, $status);
+                    $manual->enrol_user($manualcache[$courseid], $user->id, $roleid, $timestart, $timeend, $status);
 
                     $a = new stdClass();
                     $a->course = $shortname;
index e389f80..46b8bc4 100644 (file)
@@ -204,7 +204,7 @@ function uu_validate_user_upload_columns(csv_import_reader $cir, $stdfields, $pr
             // hack: somebody wrote uppercase in csv file, but the system knows only lowercase profile field
             $newfield = $lcfield;
 
-        } else if (preg_match('/^(sysrole|cohort|course|group|type|role|enrolperiod|enrolstatus)\d+$/', $lcfield)) {
+        } else if (preg_match('/^(sysrole|cohort|course|group|type|role|enrolperiod|enrolstatus|enroltimestart)\d+$/', $lcfield)) {
             // special fields for enrolments
             $newfield = $lcfield;
 
index ffd9140..7ac75ba 100644 (file)
@@ -145,3 +145,29 @@ Feature: Upload users
     And I should see "Users created: 4"
     And I press "Continue"
     And I log out
+
+  @javascript
+  Scenario: Upload users setting their enrol date and period
+    Given the following "courses" exist:
+      | fullname | shortname | category |
+      | Maths    | math102   | 0        |
+    # Upload the users.
+    And I change window size to "large"
+    And I log in as "admin"
+    And I navigate to "Users > Accounts > Upload users" in site administration
+    When I upload "lib/tests/fixtures/upload_users_enrol_date_period.csv" file to "File" filemanager
+    And I press "Upload users"
+    Then I should see "Upload users preview"
+    And I press "Upload users"
+    # Check user enrolment start date and period
+    And I am on "Maths" course homepage
+    Then I navigate to course participants
+    And I click on "Manual enrolments" "link" in the "Student One" "table_row"
+    Then I should see "1 January 2019" in the "Enrolment starts" "table_row"
+    And I should not see "Enrolment ends"
+    And I click on "Close" "button"
+    And I click on "Manual enrolments" "link" in the "Student Two" "table_row"
+    Then I should see "2 January 2020" in the "Enrolment starts" "table_row"
+    And I should see "12 January 2020" in the "Enrolment ends" "table_row"
+    And I click on "Close" "button"
+    And I log out
index 4441ba8..3713d07 100644 (file)
@@ -77,18 +77,22 @@ class provider implements
             }
 
             if ($descriptionidentifier !== null) {
-                $time = transform::datetime($value);
-                $tour = \tool_usertours\tour::instance($tourid);
+                try {
+                    $tour = \tool_usertours\tour::instance($tourid);
+                    $time = transform::datetime($value);
 
-                writer::export_user_preference(
-                    'tool_usertours',
-                    $name,
-                    $time,
-                    get_string($descriptionidentifier, 'tool_usertours', (object) [
-                        'name' => $tour->get_name(),
-                        'time' => $time,
-                    ])
-                );
+                    writer::export_user_preference(
+                        'tool_usertours',
+                        $name,
+                        $time,
+                        get_string($descriptionidentifier, 'tool_usertours', (object) [
+                            'name' => $tour->get_name(),
+                            'time' => $time,
+                        ])
+                    );
+                } catch (\dml_missing_record_exception $ex) {
+                    // The tour related to this user preference no longer exists.
+                }
             }
         }
     }
index 8cfc81a..db0139d 100644 (file)
@@ -112,4 +112,37 @@ class tool_usertours_privacy_testcase extends \core_privacy\tests\provider_testc
 
         $this->assertCount(2, (array) $prefs);
     }
+
+    /**
+     * Ensure that export_user_preferences excludes deleted tours.
+     */
+    public function test_export_user_preferences_deleted_tour() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        $user = \core_user::get_user_by_username('admin');
+
+        $alltours = $DB->get_records('tool_usertours_tours');
+
+        $tour1 = tour::instance(array_shift($alltours)->id);
+        $tour1->mark_user_completed();
+
+        $tour2 = tour::instance(array_shift($alltours)->id);
+        $tour2->mark_user_completed();
+        $tour2->remove();
+
+        $writer = writer::with_context(\context_system::instance());
+
+        provider::export_user_preferences($user->id);
+        $this->assertTrue($writer->has_any_data());
+
+        // We should have one preference.
+        $prefs = $writer->get_user_preferences('tool_usertours');
+        $this->assertCount(1, (array) $prefs);
+
+        // The preference should be related to the first tour.
+        $this->assertContains($tour1->get_name(), reset($prefs)->description);
+    }
 }
index 99e70c1..c0d308b 100644 (file)
@@ -50,12 +50,11 @@ abstract class by_course extends base {
         if (!empty($this->options['filter'])) {
             $courses = array();
             foreach ($this->options['filter'] as $courseid) {
-                $courses[$courseid] = new \stdClass();
-                $courses[$courseid]->id = $courseid;
+                $courses[$courseid] = intval($courseid);
             }
 
             list($coursesql, $courseparams) = $DB->get_in_or_equal($courses, SQL_PARAMS_NAMED);
-            $sql .= " AND c.id IN $coursesql";
+            $sql .= " AND c.id $coursesql";
             $params = $params + $courseparams;
         }
 
index 7a6b61e..16ef9f1 100644 (file)
@@ -78,9 +78,9 @@ class result_file extends result {
         // if this analyser was analysed less that 1 week ago we skip generating a new one. This
         // helps scale the evaluation process as sites with tons of courses may need a lot of time to
         // complete an evaluation.
-        if (!empty($options['evaluation']) && !empty($options['reuseprevanalysed'])) {
+        if (!empty($this->options['evaluation']) && !empty($this->options['reuseprevanalysed'])) {
 
-            $previousanalysis = \core_analytics\dataset_manager::get_evaluation_analysable_file($this->analyser->get_modelid(),
+            $previousanalysis = \core_analytics\dataset_manager::get_evaluation_analysable_file($this->modelid,
                 $analysable->get_id(), $timesplitting->get_id());
             // 1 week is a partly random time interval, no need to worry about DST.
             $boundary = time() - WEEKSECS;
index 2f47610..03b02e9 100644 (file)
@@ -553,30 +553,32 @@ class manager {
     public static function cleanup() {
         global $DB;
 
-        // Clean up stuff that depends on contexts that do not exist anymore.
-        $sql = "SELECT DISTINCT ap.contextid FROM {analytics_predictions} ap
-                  LEFT JOIN {context} ctx ON ap.contextid = ctx.id
-                 WHERE ctx.id IS NULL";
-        $apcontexts = $DB->get_records_sql($sql);
+        $DB->execute("DELETE FROM {analytics_prediction_actions} WHERE predictionid IN
+                          (SELECT ap.id FROM {analytics_predictions} ap
+                        LEFT JOIN {context} ctx ON ap.contextid = ctx.id
+                            WHERE ctx.id IS NULL)");
 
-        $sql = "SELECT DISTINCT aic.contextid FROM {analytics_indicator_calc} aic
-                  LEFT JOIN {context} ctx ON aic.contextid = ctx.id
-                 WHERE ctx.id IS NULL";
-        $indcalccontexts = $DB->get_records_sql($sql);
-
-        $contexts = $apcontexts + $indcalccontexts;
-        if ($contexts) {
-            list($sql, $params) = $DB->get_in_or_equal(array_keys($contexts));
-            $DB->execute("DELETE FROM {analytics_prediction_actions} WHERE predictionid IN
-                (SELECT ap.id FROM {analytics_predictions} ap WHERE ap.contextid $sql)", $params);
-
-            $DB->delete_records_select('analytics_predictions', "contextid $sql", $params);
-            $DB->delete_records_select('analytics_indicator_calc', "contextid $sql", $params);
-        }
+        $contextsql = "SELECT id FROM {context} ctx";
+        $DB->delete_records_select('analytics_predictions', "contextid NOT IN ($contextsql)");
+        $DB->delete_records_select('analytics_indicator_calc', "contextid NOT IN ($contextsql)");
 
         // Clean up stuff that depends on analysable ids that do not exist anymore.
+
         $models = self::get_all_models();
         foreach ($models as $model) {
+
+            // We first dump into memory the list of analysables we have in the database (we could probably do this with 1 single
+            // query for the 3 tables, but it may be safer to do it separately).
+            $predictsamplesanalysableids = $DB->get_fieldset_select('analytics_predict_samples', 'DISTINCT analysableid',
+                'modelid = :modelid', ['modelid' => $model->get_id()]);
+            $predictsamplesanalysableids = array_flip($predictsamplesanalysableids);
+            $trainsamplesanalysableids = $DB->get_fieldset_select('analytics_train_samples', 'DISTINCT analysableid',
+                'modelid = :modelid', ['modelid' => $model->get_id()]);
+            $trainsamplesanalysableids = array_flip($trainsamplesanalysableids);
+            $usedanalysablesanalysableids = $DB->get_fieldset_select('analytics_used_analysables', 'DISTINCT analysableid',
+                'modelid = :modelid', ['modelid' => $model->get_id()]);
+            $usedanalysablesanalysableids = array_flip($usedanalysablesanalysableids);
+
             $analyser = $model->get_analyser(array('notimesplitting' => true));
             $analysables = $analyser->get_analysables_iterator();
 
@@ -585,17 +587,28 @@ class manager {
                 if (!$analysable) {
                     continue;
                 }
-                $analysableids[] = $analysable->get_id();
-            }
-            if (empty($analysableids)) {
-                continue;
+                unset($predictsamplesanalysableids[$analysable->get_id()]);
+                unset($trainsamplesanalysableids[$analysable->get_id()]);
+                unset($usedanalysablesanalysableids[$analysable->get_id()]);
             }
 
-            list($notinsql, $params) = $DB->get_in_or_equal($analysableids, SQL_PARAMS_NAMED, 'param', false);
-            $params['modelid'] = $model->get_id();
+            $param = ['modelid' => $model->get_id()];
 
-            $DB->delete_records_select('analytics_predict_samples', "modelid = :modelid AND analysableid $notinsql", $params);
-            $DB->delete_records_select('analytics_train_samples', "modelid = :modelid AND analysableid $notinsql", $params);
+            if ($predictsamplesanalysableids) {
+                list($idssql, $idsparams) = $DB->get_in_or_equal(array_flip($predictsamplesanalysableids), SQL_PARAMS_NAMED);
+                $DB->delete_records_select('analytics_predict_samples', "modelid = :modelid AND analysableid $idssql",
+                    $param + $idsparams);
+            }
+            if ($trainsamplesanalysableids) {
+                list($idssql, $idsparams) = $DB->get_in_or_equal(array_flip($trainsamplesanalysableids), SQL_PARAMS_NAMED);
+                $DB->delete_records_select('analytics_train_samples', "modelid = :modelid AND analysableid $idssql",
+                    $param + $idsparams);
+            }
+            if ($usedanalysablesanalysableids) {
+                list($idssql, $idsparams) = $DB->get_in_or_equal(array_flip($usedanalysablesanalysableids), SQL_PARAMS_NAMED);
+                $DB->delete_records_select('analytics_used_analysables', "modelid = :modelid AND analysableid $idssql",
+                    $param + $idsparams);
+            }
         }
     }
 
index 195c00e..e5ca153 100644 (file)
@@ -1030,7 +1030,7 @@ class model {
         }
 
         // Get all samples data.
-        list($sampleids, $samplesdata) = $this->get_analyser()->get_samples($sampleids);
+        list($sampleids, $samplesdata) = $this->get_samples($sampleids);
 
         // Calculate the targets.
         $predictions = array();
@@ -1344,7 +1344,7 @@ class model {
             return $prediction->sampleid;
         }, $predictions);
 
-        list($unused, $samplesdata) = $this->get_analyser()->get_samples($sampleids);
+        list($unused, $samplesdata) = $this->get_samples($sampleids);
 
         $current = 0;
 
@@ -1410,7 +1410,7 @@ class model {
      */
     public function prediction_sample_data($predictionobj) {
 
-        list($unused, $samplesdata) = $this->get_analyser()->get_samples(array($predictionobj->sampleid));
+        list($unused, $samplesdata) = $this->get_samples(array($predictionobj->sampleid));
 
         if (empty($samplesdata[$predictionobj->sampleid])) {
             throw new \moodle_exception('errorsamplenotavailable', 'analytics');
@@ -1722,12 +1722,8 @@ class model {
             $predictor->clear_model($this->get_unique_id(), $this->get_output_dir());
         }
 
-        $predictionids = $DB->get_fieldset_select('analytics_predictions', 'id', 'modelid = :modelid',
-            array('modelid' => $this->get_id()));
-        if ($predictionids) {
-            list($sql, $params) = $DB->get_in_or_equal($predictionids);
-            $DB->delete_records_select('analytics_prediction_actions', "predictionid $sql", $params);
-        }
+        $DB->delete_records_select('analytics_prediction_actions', "predictionid IN
+            (SELECT id FROM {analytics_predictions} WHERE modelid = :modelid)", ['modelid' => $this->get_id()]);
 
         $DB->delete_records('analytics_predictions', array('modelid' => $this->model->id));
         $DB->delete_records('analytics_predict_samples', array('modelid' => $this->model->id));
@@ -1833,30 +1829,94 @@ class model {
         $contextids = array_map(function($predictionobj) {
             return $predictionobj->contextid;
         }, $predictionrecords);
-        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
-
-        // We select the fields that will allow us to map ids to $predictionrecords. Given that we already filter by modelid
-        // we have enough with sampleid and rangeindex. The reason is that the sampleid relation to a site is N - 1.
-        $fields = 'id, sampleid, rangeindex';
-
-        // We include the contextid and the timecreated filter to reduce the number of records in $dbpredictions. We can not
-        // add as many OR conditions as records in $predictionrecords.
-        $sql = "SELECT $fields
-                  FROM {analytics_predictions}
-                 WHERE modelid = :modelid
-                       AND contextid $contextsql
-                       AND timecreated >= :firsttimecreated";
-        $params = $contextparams + ['modelid' => $this->model->id, 'firsttimecreated' => $firstprediction->timecreated];
-        $dbpredictions = $DB->get_recordset_sql($sql, $params);
-        foreach ($dbpredictions as $id => $dbprediction) {
-            // The append_rangeindex implementation is the same regardless of the time splitting method in use.
-            $uniqueid = $this->get_time_splitting()->append_rangeindex($dbprediction->sampleid, $dbprediction->rangeindex);
-            $predictionrecords[$uniqueid]->id = $dbprediction->id;
+
+        // Limited to 30000 records as a middle point between the ~65000 params limit in pgsql and the size limit for mysql which
+        // can be increased if required up to a reasonable point.
+        $chunks = array_chunk($contextids, 30000);
+        foreach ($chunks as $contextidschunk) {
+            list($contextsql, $contextparams) = $DB->get_in_or_equal($contextidschunk, SQL_PARAMS_NAMED);
+
+            // We select the fields that will allow us to map ids to $predictionrecords. Given that we already filter by modelid
+            // we have enough with sampleid and rangeindex. The reason is that the sampleid relation to a site is N - 1.
+            $fields = 'id, sampleid, rangeindex';
+
+            // We include the contextid and the timecreated filter to reduce the number of records in $dbpredictions. We can not
+            // add as many OR conditions as records in $predictionrecords.
+            $sql = "SELECT $fields
+                      FROM {analytics_predictions}
+                     WHERE modelid = :modelid
+                           AND contextid $contextsql
+                           AND timecreated >= :firsttimecreated";
+            $params = $contextparams + ['modelid' => $this->model->id, 'firsttimecreated' => $firstprediction->timecreated];
+            $dbpredictions = $DB->get_recordset_sql($sql, $params);
+            foreach ($dbpredictions as $id => $dbprediction) {
+                // The append_rangeindex implementation is the same regardless of the time splitting method in use.
+                $uniqueid = $this->get_time_splitting()->append_rangeindex($dbprediction->sampleid, $dbprediction->rangeindex);
+                $predictionrecords[$uniqueid]->id = $dbprediction->id;
+            }
         }
 
         return $predictionrecords;
     }
 
+    /**
+     * Wrapper around analyser's get_samples to skip DB's max-number-of-params exception.
+     *
+     * @param  array  $sampleids
+     * @return array
+     */
+    public function get_samples(array $sampleids): array {
+
+        if (empty($sampleids)) {
+            throw new \coding_exception('No sample ids provided');
+        }
+
+        $chunksize = count($sampleids);
+
+        // We start with just 1 chunk, if it is too large for the db we split the list of sampleids in 2 and we
+        // try again. We repeat this process until the chunk is small enough for the db engine to process. The
+        // >= has been added in case there are other \dml_read_exceptions unrelated to the max number of params.
+        while (empty($done) && $chunksize >= 1) {
+
+            $chunks = array_chunk($sampleids, $chunksize);
+            $allsampleids = [];
+            $allsamplesdata = [];
+
+            foreach ($chunks as $index => $chunk) {
+
+                try {
+                    list($chunksampleids, $chunksamplesdata) = $this->get_analyser()->get_samples($chunk);
+                } catch (\dml_read_exception $e) {
+
+                    // Reduce the chunksize, we use floor() so the $chunksize is always less than the previous $chunksize value.
+                    $chunksize = floor($chunksize / 2);
+                    break;
+                }
+
+                // We can sum as these two arrays are indexed by sampleid and there are no collisions.
+                $allsampleids = $allsampleids + $chunksampleids;
+                $allsamplesdata = $allsamplesdata + $chunksamplesdata;
+
+                if ($index === count($chunks) - 1) {
+                    // We successfully processed all the samples in all chunks, we are done.
+                    $done = true;
+                }
+            }
+        }
+
+        if (empty($done)) {
+            if (!empty($e)) {
+                // Throw the last exception we caught, the \dml_read_exception we have been catching is unrelated to the max number
+                // of param's exception.
+                throw new \dml_read_exception($e);
+            } else {
+                throw new \coding_exception('We should never reach this point, there is a bug in ' .
+                    'core_analytics\\model::get_samples\'s code');
+            }
+        }
+        return [$allsampleids, $allsamplesdata];
+    }
+
     /**
      * Purges the insights cache.
      */
index 36f6069..c147bdf 100644 (file)
@@ -306,13 +306,9 @@ class provider implements
             $idssql = "SELECT ap.id FROM {analytics_predictions} ap
                         WHERE ap.contextid = :contextid AND ap.modelid = :modelid";
             $idsparams = ['contextid' => $context->id, 'modelid' => $modelid];
-            $predictionids = $DB->get_fieldset_sql($idssql, $idsparams);
-            if ($predictionids) {
-                list($predictionidssql, $params) = $DB->get_in_or_equal($predictionids, SQL_PARAMS_NAMED);
 
-                $DB->delete_records_select('analytics_prediction_actions', "predictionid IN ($idssql)", $idsparams);
-                $DB->delete_records_select('analytics_predictions', "id $predictionidssql", $params);
-            }
+            $DB->delete_records_select('analytics_prediction_actions', "predictionid IN ($idssql)", $idsparams);
+            $DB->delete_records_select('analytics_predictions', "contextid = :contextid AND modelid = :modelid", $idsparams);
         }
 
         // We delete them all this table is just a cache and we don't know which model filled it.
index 8a668c9..f8f4826 100644 (file)
@@ -51,8 +51,8 @@ Feature: Manage analytics models
     And I navigate to "Analytics > Analytics models" in site administration
 
   Scenario: Create a model
-    When I click on "New model" "link"
-    And I click on "Create model" "link"
+    When I open the action menu in ".top-nav" "css_element"
+    And I choose "Create model" in the open action menu
     And I set the field "Enabled" to "Enable"
     And I select "__core_course__analytics__target__course_completion" from the "target" singleselect
     And I open the autocomplete suggestions list
@@ -84,22 +84,22 @@ Feature: Manage analytics models
     And I click on "Save changes" "button"
     And I am on site homepage
     And I navigate to "Analytics > Analytics models" in site administration
-    And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Evaluate" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Evaluate" in the open action menu
     And I press "Evaluate"
     And I should see "Evaluate model"
     And I press "Continue"
     # Evaluation log
-    And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Evaluation log" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Evaluation log" in the open action menu
     And I should see "Configuration"
     And I click on "View" "link"
     And I should see "Log extra info"
     And I click on "Close" "button"
     And I click on "Analytics models" "link"
     # Execute scheduled analysis
-    And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Execute scheduled analysis" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Execute scheduled analysis" in the open action menu
     And I should see "Training results"
     And I press "Continue"
     # Check notifications
@@ -111,53 +111,52 @@ Feature: Manage analytics models
     And I navigate to "Analytics > Analytics models" in site administration
     # View predictions
     When I select "C3" from the "contextid" singleselect
-    #And I click on "#dropdown-3" "css_element"
-    And I click on "Actions" "link" in the "Student 6" "table_row"
-    And I click on "View prediction details" "link"
+    And I open the action menu in "Student 6" "table_row"
+    And I choose "View prediction details" in the open action menu
     And I should see "Prediction details"
     And I should see "Any write action"
     And I should see "Read actions amount"
-    And I click on "Actions" "link"
-    And I click on "Acknowledged" "link"
-    And I click on "Actions" "link"
-    And I click on "View prediction details" "link"
-    And I click on "Actions" "link"
-    And I click on "Not useful" "link"
+    And I open the action menu in "Student 6" "table_row"
+    And I choose "Acknowledged" in the open action menu
+    And I open the action menu in "Student 5" "table_row"
+    And I choose "View prediction details" in the open action menu
+    And I open the action menu in "Student 5" "table_row"
+    And I choose "Not useful" in the open action menu
     And I should see "No insights reported"
     # Clear predictions
     When I am on site homepage
     And I navigate to "Analytics > Analytics models" in site administration
     And I should see "No insights reported" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Clear predictions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Clear predictions" in the open action menu
     And I press "Clear predictions"
     Then I should see "No predictions available yet" in the "Students at risk of not meeting the course completion conditions" "table_row"
 
   Scenario: Edit a model
-    When I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Edit" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Edit" in the open action menu
     And I click on "Read actions amount" "text" in the ".form-autocomplete-selection" "css_element"
     And I press "Save changes"
     And I should not see "Read actions amount"
 
   Scenario: Disable a model
-    When I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Disable" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Disable" in the open action menu
     Then I should see "Disabled model" in the "Students at risk of not meeting the course completion conditions" "table_row"
 
   Scenario: Export model
-    When I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Export" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Export" in the open action menu
     And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
     And following "Export" should download between "100" and "500" bytes
 
   Scenario: Check invalid site elements
-    When I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Invalid site elements" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Invalid site elements" in the open action menu
     Then I should see "Invalid analysable elements"
 
   Scenario: Delete model
-    When I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Delete" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Delete" in the open action menu
     And I click on "Delete" "button" in the "Confirm" "dialogue"
     Then I should not see "Students at risk of not meeting the course completion conditions"
diff --git a/analytics/tests/fixtures/test_indicator_multiclass.php b/analytics/tests/fixtures/test_indicator_multiclass.php
new file mode 100644 (file)
index 0000000..64872a8
--- /dev/null
@@ -0,0 +1,92 @@
+<?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/>.
+
+/**
+ * Multiclass test indicator.
+ *
+ * @package   core_analytics
+ * @copyright 2019 Vlad Apetrei
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Multiclass test indicator.
+ *
+ * @package   core_analytics
+ * @copyright 2019 Vlad Apetrei
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class test_indicator_multiclass extends \core_analytics\local\indicator\linear {
+
+    /**
+     * Returns a lang_string object representing the name for the indicator.
+     *
+     * Used as column identificator.
+     *
+     * If there is a corresponding '_help' string this will be shown as well.
+     *
+     * @return \lang_string
+     */
+    public static function get_name() : \lang_string {
+        // Using a string that exists and contains a corresponding '_help' string.
+        return new \lang_string('allowstealthmodules');
+    }
+
+    /**
+     * include_averages
+     *
+     * @return bool
+     */
+    protected static function include_averages() {
+        return false;
+    }
+
+    /**
+     * required_sample_data
+     *
+     * @return string[]
+     */
+    public static function required_sample_data() {
+        return array('course');
+    }
+
+    /**
+     * calculate_sample
+     *
+     * @param int $sampleid
+     * @param string $samplesorigin
+     * @param int $starttime
+     * @param int $endtime
+     * @return float
+     */
+    protected function calculate_sample($sampleid, $samplesorigin, $starttime, $endtime) {
+
+        $course = $this->retrieve('course', $sampleid);
+
+        $firstchar = substr($course->fullname, 0, 1);
+        if ($firstchar === 'a') {
+            return 1;
+        } else if ($firstchar === 'b') {
+            return -1;
+        } else if ($firstchar === 'c') {
+            return 1;
+        } else {
+            return self::MAX_VALUE;
+        }
+    }
+}
index 8907a3c..7be0a87 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-require_once(__DIR__ . '/test_target_shortname.php');
+require_once(__DIR__ . '/test_target_site_users.php');
 
 /**
  * Test target.
diff --git a/analytics/tests/fixtures/test_target_shortname_multiclass.php b/analytics/tests/fixtures/test_target_shortname_multiclass.php
new file mode 100644 (file)
index 0000000..91c5e35
--- /dev/null
@@ -0,0 +1,211 @@
+<?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/>.
+
+/**
+ * Multi-class classifier target.
+ *
+ * @package   core_analytics
+ * @copyright 2019 Apetrei Vlad
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Multi-class classifier target.
+ *
+ * @package   core_analytics
+ * @copyright 2019 Apetrei Vlad
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class test_target_shortname_multiclass extends \core_analytics\local\target\discrete {
+
+    /**
+     * Returns a lang_string object representing the name for the indicator.
+     *
+     * Used as column identificator.
+     *
+     * If there is a corresponding '_help' string this will be shown as well.
+     *
+     * @return \lang_string
+     */
+    public static function get_name() : \lang_string {
+        // Using a string that exists and contains a corresponding '_help' string.
+        return new \lang_string('allowstealthmodules');
+    }
+
+    /**
+     * predictions
+     *
+     * @var array
+     */
+    protected $predictions = array();
+
+    /**
+     * is_linear
+     *
+     * @return bool
+     */
+    public function is_linear() {
+        return false;
+    }
+
+    /**
+     * Returns the target discrete values.
+     *
+     * Only useful for targets using discrete values, must be overwriten if it is the case.
+     *
+     * @return array
+     */
+    public static final function get_classes() {
+        return array(0, 1, 2);
+    }
+
+    /**
+     * Is the calculated value a positive outcome of this target?
+     *
+     * @param string $value
+     * @param string $ignoredsubtype
+     * @return int
+     */
+    public function get_calculation_outcome($value, $ignoredsubtype = false) {
+
+        if (!self::is_a_class($value)) {
+            throw new \moodle_exception('errorpredictionformat', 'analytics');
+        }
+
+        if (in_array($value, $this->ignored_predicted_classes(), false)) {
+            // Just in case, if it is ignored the prediction should not even be recorded but if it would, it is ignored now,
+            // which should mean that is it nothing serious.
+            return self::OUTCOME_VERY_POSITIVE;
+        }
+
+        // By default binaries are danger when prediction = 1.
+        if ($value) {
+            return self::OUTCOME_VERY_NEGATIVE;
+        }
+        return self::OUTCOME_VERY_POSITIVE;
+    }
+
+    /**
+     * get_analyser_class
+     *
+     * @return string
+     */
+    public function get_analyser_class() {
+        return '\core\analytics\analyser\site_courses';
+    }
+
+    /**
+     * We don't want to discard results.
+     * @return float
+     */
+    protected function min_prediction_score() {
+        return null;
+    }
+
+    /**
+     * We don't want to discard results.
+     * @return array
+     */
+    public function ignored_predicted_classes() {
+        return array();
+    }
+
+    /**
+     * is_valid_analysable
+     *
+     * @param \core_analytics\analysable $analysable
+     * @param bool $fortraining
+     * @return bool
+     */
+    public function is_valid_analysable(\core_analytics\analysable $analysable, $fortraining = true) {
+        // This is testing, let's make things easy.
+        return true;
+    }
+
+    /**
+     * is_valid_sample
+     *
+     * @param int $sampleid
+     * @param \core_analytics\analysable $analysable
+     * @param bool $fortraining
+     * @return bool
+     */
+    public function is_valid_sample($sampleid, \core_analytics\analysable $analysable, $fortraining = true) {
+        // We skip not-visible courses during training as a way to emulate the training data / prediction data difference.
+        // In normal circumstances is_valid_sample will return false when they receive a sample that can not be
+        // processed.
+        if (!$fortraining) {
+            return true;
+        }
+
+        $sample = $this->retrieve('course', $sampleid);
+        if ($sample->visible == 0) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * classes_description
+     *
+     * @return string[]
+     */
+    protected static function classes_description() {
+        return array(
+            get_string('first class'),
+            get_string('second class'),
+            get_string('third class')
+        );
+    }
+
+    /**
+     * calculate_sample
+     *
+     * @param int $sampleid
+     * @param \core_analytics\analysable $analysable
+     * @param int $starttime
+     * @param int $endtime
+     * @return float
+     */
+    protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false) {
+
+        $sample = $this->retrieve('course', $sampleid);
+
+        $firstchar = substr($sample->shortname, 0, 1);
+        switch ($firstchar) {
+            case 'a':
+                return 0;
+            case 'b':
+                return 1;
+            case 'c':
+                return 2;
+        }
+    }
+
+    /**
+     * Can the provided time-splitting method be used on this target?.
+     *
+     * Time-splitting methods not matching the target requirements will not be selectable by models based on this target.
+     *
+     * @param \core_analytics\local\time_splitting\base $timesplitting
+     * @return bool
+     */
+    public function can_use_timesplitting(\core_analytics\local\time_splitting\base $timesplitting):bool {
+        return true;
+    }
+}
index 167d41d..74e6721 100644 (file)
@@ -134,8 +134,9 @@ class analytics_manager_testcase extends advanced_testcase {
         $model->train();
         $model->predict();
 
-        $npredictsamples = $DB->count_records('analytics_predict_samples');
-        $ntrainsamples = $DB->count_records('analytics_train_samples');
+        $this->assertNotEmpty($DB->count_records('analytics_predict_samples'));
+        $this->assertNotEmpty($DB->count_records('analytics_train_samples'));
+        $this->assertNotEmpty($DB->count_records('analytics_used_analysables'));
 
         // Now we delete an analysable, stored predict and training samples should be deleted.
         $deletedcontext = \context_course::instance($coursepredict1->id);
@@ -145,6 +146,7 @@ class analytics_manager_testcase extends advanced_testcase {
 
         $this->assertEmpty($DB->count_records('analytics_predict_samples', array('analysableid' => $coursepredict1->id)));
         $this->assertEmpty($DB->count_records('analytics_train_samples', array('analysableid' => $coursepredict1->id)));
+        $this->assertEmpty($DB->count_records('analytics_used_analysables', array('analysableid' => $coursepredict1->id)));
 
         set_config('enabled_stores', '', 'tool_log');
         get_log_manager(true);
index 8e82a47..5559651 100644 (file)
@@ -505,6 +505,61 @@ class analytics_model_testcase extends advanced_testcase {
         $this->assertArrayHasKey('\core\analytics\time_splitting\quarters', $this->model->get_potential_timesplittings());
     }
 
+    /**
+     * Tests model::get_samples()
+     *
+     * @return null
+     */
+    public function test_get_samples() {
+        $this->resetAfterTest();
+
+        if (!PHPUNIT_LONGTEST) {
+            $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
+        }
+
+        // 10000 should be enough to make oracle and mssql fail, if we want pgsql to fail we need around 70000
+        // users, that is a few minutes just to create the users.
+        $nusers = 10000;
+
+        $userids = [];
+        for ($i = 0; $i < $nusers; $i++) {
+            $user = $this->getDataGenerator()->create_user();
+            $userids[] = $user->id;
+        }
+
+        $upcomingactivities = null;
+        foreach (\core_analytics\manager::get_all_models() as $model) {
+            if (get_class($model->get_target()) === 'core_user\\analytics\\target\\upcoming_activities_due') {
+                $upcomingactivities = $model;
+            }
+        }
+
+        list($sampleids, $samplesdata) = $upcomingactivities->get_samples($userids);
+        $this->assertCount($nusers, $sampleids);
+        $this->assertCount($nusers, $samplesdata);
+
+        $subset = array_slice($userids, 0, 100);
+        list($sampleids, $samplesdata) = $upcomingactivities->get_samples($subset);
+        $this->assertCount(100, $sampleids);
+        $this->assertCount(100, $samplesdata);
+
+        $subset = array_slice($userids, 0, 2);
+        list($sampleids, $samplesdata) = $upcomingactivities->get_samples($subset);
+        $this->assertCount(2, $sampleids);
+        $this->assertCount(2, $samplesdata);
+
+        $subset = array_slice($userids, 0, 1);
+        list($sampleids, $samplesdata) = $upcomingactivities->get_samples($subset);
+        $this->assertCount(1, $sampleids);
+        $this->assertCount(1, $samplesdata);
+
+        // Unexisting, so nothing returned, but still 2 arrays.
+        list($sampleids, $samplesdata) = $upcomingactivities->get_samples([1231231231231231]);
+        $this->assertEmpty($sampleids);
+        $this->assertEmpty($samplesdata);
+
+    }
+
     /**
      * Generates a model log record.
      */
index 4294cad..c3aeab3 100644 (file)
@@ -30,7 +30,9 @@ require_once(__DIR__ . '/fixtures/test_indicator_min.php');
 require_once(__DIR__ . '/fixtures/test_indicator_null.php');
 require_once(__DIR__ . '/fixtures/test_indicator_fullname.php');
 require_once(__DIR__ . '/fixtures/test_indicator_random.php');
+require_once(__DIR__ . '/fixtures/test_indicator_multiclass.php');
 require_once(__DIR__ . '/fixtures/test_target_shortname.php');
+require_once(__DIR__ . '/fixtures/test_target_shortname_multiclass.php');
 require_once(__DIR__ . '/fixtures/test_static_target_shortname.php');
 
 require_once(__DIR__ . '/../../course/lib.php');
@@ -433,6 +435,70 @@ class core_analytics_prediction_testcase extends advanced_testcase {
         return $this->add_prediction_processors($cases);
     }
 
+    /**
+     * Tests correct multi-classification.
+     *
+     * @dataProvider provider_test_multi_classifier
+     * @param string $timesplittingid
+     * @param string $predictionsprocessorclass
+     * @throws coding_exception
+     * @throws moodle_exception
+     */
+    public function test_ml_multi_classifier($timesplittingid, $predictionsprocessorclass) {
+        global $DB;
+
+        $this->resetAfterTest(true);
+        $this->setAdminuser();
+        set_config('enabled_stores', 'logstore_standard', 'tool_log');
+
+        $predictionsprocessor = \core_analytics\manager::get_predictions_processor($predictionsprocessorclass, false);
+        if ($predictionsprocessor->is_ready() !== true) {
+            $this->markTestSkipped('Skipping ' . $predictionsprocessorclass . ' as the predictor is not ready.');
+        }
+        // Generate training courses.
+        $ncourses = 5;
+        $this->generate_courses_multiclass($ncourses);
+        $model = $this->add_multiclass_model();
+        $model->update(true, false, $timesplittingid, get_class($predictionsprocessor));
+        $results = $model->train();
+
+        $params = [
+            'startdate' => mktime(0, 0, 0, 10, 24, 2015),
+            'enddate' => mktime(0, 0, 0, 2, 24, 2016),
+        ];
+        $courseparams = $params + array('shortname' => 'aaaaaa', 'fullname' => 'aaaaaa', 'visible' => 0);
+        $course1 = $this->getDataGenerator()->create_course($courseparams);
+        $courseparams = $params + array('shortname' => 'bbbbbb', 'fullname' => 'bbbbbb', 'visible' => 0);
+        $course2 = $this->getDataGenerator()->create_course($courseparams);
+        $courseparams = $params + array('shortname' => 'cccccc', 'fullname' => 'cccccc', 'visible' => 0);
+        $course3 = $this->getDataGenerator()->create_course($courseparams);
+
+        // They will not be skipped for prediction though.
+        $result = $model->predict();
+        // The $course1 predictions should be 0 == 'a', $course2 should be 1 == 'b' and $course3 should be 2 == 'c'.
+        $correct = array($course1->id => 0, $course2->id => 1, $course3->id => 2);
+        foreach ($result->predictions as $uniquesampleid => $predictiondata) {
+            list($sampleid, $rangeindex) = $model->get_time_splitting()->infer_sample_info($uniquesampleid);
+
+            // The range index is not important here, both ranges prediction will be the same.
+            $this->assertEquals($correct[$sampleid], $predictiondata->prediction);
+        }
+    }
+
+    /**
+     * Provider for the multi_classification test.
+     *
+     * @return array
+     */
+    public function provider_test_multi_classifier() {
+        $cases = array(
+            'notimesplitting' => array('\core\analytics\time_splitting\no_splitting'),
+        );
+
+        // Add all system prediction processors.
+        return $this->add_prediction_processors($cases);
+    }
+
     /**
      * Basic test to check that prediction processors work as expected.
      *
@@ -478,6 +544,13 @@ class core_analytics_prediction_testcase extends advanced_testcase {
             $message = 'The returned status code ' . $result->status . ' should include ' . $expected[$timesplitting];
             $filtered = $result->status & $expected[$timesplitting];
             $this->assertEquals($expected[$timesplitting], $filtered, $message);
+
+            $options = ['evaluation' => true, 'reuseprevanalysed' => true];
+            $result = new \core_analytics\local\analysis\result_file($model->get_id(), true, $options);
+            $timesplittingobj = \core_analytics\manager::get_time_splitting($timesplitting);
+            $analysable = new \core_analytics\site();
+            $cachedanalysis = $result->retrieve_cached_result($timesplittingobj, $analysable);
+            $this->assertInstanceOf(\stored_file::class, $cachedanalysis);
         }
 
         set_config('enabled_stores', '', 'tool_log');
@@ -663,7 +736,6 @@ class core_analytics_prediction_testcase extends advanced_testcase {
      * @return \core_analytics\model
      */
     protected function add_perfect_model($targetclass = 'test_target_shortname') {
-
         $target = \core_analytics\manager::get_target($targetclass);
         $indicators = array('test_indicator_max', 'test_indicator_min', 'test_indicator_fullname');
         foreach ($indicators as $key => $indicator) {
@@ -676,6 +748,25 @@ class core_analytics_prediction_testcase extends advanced_testcase {
         return new \core_analytics\model($model->get_id());
     }
 
+    /**
+     * Generates model for multi-classification
+     *
+     * @param string $targetclass
+     * @return \core_analytics\model
+     * @throws coding_exception
+     * @throws moodle_exception
+     */
+    public function add_multiclass_model($targetclass = 'test_target_shortname_multiclass') {
+        $target = \core_analytics\manager::get_target($targetclass);
+        $indicators = array('test_indicator_fullname', 'test_indicator_multiclass');
+        foreach ($indicators as $key => $indicator) {
+            $indicators[$key] = \core_analytics\manager::get_indicator($indicator);
+        }
+
+        $model = \core_analytics\model::create($target, $indicators);
+        return new \core_analytics\model($model->get_id());
+    }
+
     /**
      * Generates $ncourses courses
      *
@@ -702,6 +793,37 @@ class core_analytics_prediction_testcase extends advanced_testcase {
         }
     }
 
+    /**
+     * Generates ncourses for multi-classification
+     *
+     * @param int $ncourses The number of courses to be generated.
+     * @param array $params Course params
+     * @return null
+     */
+    protected function generate_courses_multiclass($ncourses, array $params = []) {
+
+        $params = $params + [
+                'startdate' => mktime(0, 0, 0, 10, 24, 2015),
+                'enddate' => mktime(0, 0, 0, 2, 24, 2016),
+            ];
+
+        for ($i = 0; $i < $ncourses; $i++) {
+            $name = 'a' . random_string(10);
+            $courseparams = array('shortname' => $name, 'fullname' => $name) + $params;
+            $this->getDataGenerator()->create_course($courseparams);
+        }
+        for ($i = 0; $i < $ncourses; $i++) {
+            $name = 'b' . random_string(10);
+            $courseparams = array('shortname' => $name, 'fullname' => $name) + $params;
+            $this->getDataGenerator()->create_course($courseparams);
+        }
+        for ($i = 0; $i < $ncourses; $i++) {
+            $name = 'c' . random_string(10);
+            $courseparams = array('shortname' => $name, 'fullname' => $name) + $params;
+            $this->getDataGenerator()->create_course($courseparams);
+        }
+    }
+
     /**
      * add_prediction_processors
      *
index 7a063ab..da0f7e9 100644 (file)
@@ -164,8 +164,4 @@ $string['diag_toooldversion'] = 'It is very unlikely a modern LDAP server uses L
 $string['diag_emptycontext'] = 'Empty context found.';
 $string['diag_contextnotfound'] = 'Context {$a} doesn\'t exist or can\'t be read by bind DN.';
 $string['diag_rolegroupnotfound'] = 'Group {$a->group} for role {$a->localname} doesn\'t exist or can\'t be read by bind DN.';
-
-// Deprecated since Moodle 3.4.
-$string['auth_ldap_creators'] = 'List of groups or contexts whose members are allowed to create new courses. Separate multiple groups with \';\'. Usually something like \'cn=teachers,ou=staff,o=myorg\'';
-$string['auth_ldap_creators_key'] = 'Creators';
 $string['privacy:metadata'] = 'The LDAP server authentication plugin does not store any personal data.';
diff --git a/auth/ldap/lang/en/deprecated.txt b/auth/ldap/lang/en/deprecated.txt
deleted file mode 100644 (file)
index fa73d1e..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-auth_ldap_creators,auth_ldap
-auth_ldap_creators_key,auth_ldap
index 133df3f..cdbd8a7 100644 (file)
@@ -33,8 +33,9 @@
  */
 
 "use strict";
+/* eslint-env node */
 
-module.exports = ({ template, types }) => {
+module.exports = ({template, types}) => {
     const fs = require('fs');
     const path = require('path');
     const glob = require('glob');
@@ -120,15 +121,20 @@ module.exports = ({ template, types }) => {
         throw new Error('Unable to find module name for ' + searchFileName);
     }
 
-    // This is heavily inspired by the babel-plugin-add-module-exports plugin.
-    // See: https://github.com/59naga/babel-plugin-add-module-exports
-    //
-    // This is used when we detect a module using "export default Foo;" to make
-    // sure the transpiled code just returns Foo directly rather than an object
-    // with the default property (i.e. {default: Foo}).
-    //
-    // Note: This means that we can't support modules that combine named exports
-    // with a default export.
+    /**
+     * This is heavily inspired by the babel-plugin-add-module-exports plugin.
+     * See: https://github.com/59naga/babel-plugin-add-module-exports
+     *
+     * This is used when we detect a module using "export default Foo;" to make
+     * sure the transpiled code just returns Foo directly rather than an object
+     * with the default property (i.e. {default: Foo}).
+     *
+     * Note: This means that we can't support modules that combine named exports
+     * with a default export.
+     *
+     * @param {String} path
+     * @param {String} exportObjectName
+     */
     function addModuleExportsDefaults(path, exportObjectName) {
         const rootPath = path.findParent(path => {
             return path.key === 'body' || !path.parentPath;
@@ -136,7 +142,7 @@ module.exports = ({ template, types }) => {
 
         // HACK: `path.node.body.push` instead of path.pushContainer(due doesn't work in Plugin.post).
         // This is hardcoded to work specifically with AMD.
-        rootPath.node.body.push(template(`return ${exportObjectName}.default`)())
+        rootPath.node.body.push(template(`return ${exportObjectName}.default`)());
     }
 
     return {
@@ -174,9 +180,9 @@ module.exports = ({ template, types }) => {
 
                             // Check for any Object.defineProperty('exports', 'default') calls.
                             if (!this.addedReturnForDefaultExport && path.get('callee').matchesPattern('Object.defineProperty')) {
-                                const [identifier, prop] = path.get('arguments')
-                                const objectName = identifier.get('name').node
-                                const propertyName = prop.get('value').node
+                                const [identifier, prop] = path.get('arguments');
+                                const objectName = identifier.get('name').node;
+                                const propertyName = prop.get('value').node;
 
                                 if ((objectName === 'exports' || objectName === '_exports') && propertyName === 'default') {
                                     addModuleExportsDefaults(path, objectName);
index 164da0b..7cd4c14 100644 (file)
@@ -128,15 +128,6 @@ abstract class backup_check {
         // Now, if backup mode is hub or import, check userid has permissions for those modes
         // other modes will perform common checks only (backupxxxx capabilities in $typecapstocheck)
         switch ($mode) {
-            case backup::MODE_HUB:
-                if (!has_capability('moodle/backup:backuptargethub', $coursectx, $userid)) {
-                    $a = new stdclass();
-                    $a->userid = $userid;
-                    $a->courseid = $courseid;
-                    $a->capability = 'moodle/backup:backuptargethub';
-                    throw new backup_controller_exception('backup_user_missing_capability', $a);
-                }
-                break;
             case backup::MODE_IMPORT:
                 if (!has_capability('moodle/backup:backuptargetimport', $coursectx, $userid)) {
                     $a = new stdclass();
index ee2b6cd..35fe2d9 100644 (file)
@@ -90,15 +90,6 @@ abstract class restore_check {
         // Now, if restore mode is hub or import, check userid has permissions for those modes
         // other modes will perform common checks only (restorexxxx capabilities in $typecapstocheck)
         switch ($mode) {
-            case backup::MODE_HUB:
-                if (!has_capability('moodle/restore:restoretargethub', $coursectx, $userid)) {
-                    $a = new stdclass();
-                    $a->userid = $userid;
-                    $a->courseid = $courseid;
-                    $a->capability = 'moodle/restore:restoretargethub';
-                    throw new restore_controller_exception('restore_user_missing_capability', $a);
-                }
-                break;
             case backup::MODE_IMPORT:
                 if (!has_capability('moodle/restore:restoretargetimport', $coursectx, $userid)) {
                     $a = new stdclass();
index a46499f..43e9dd7 100644 (file)
@@ -404,8 +404,10 @@ class core_backup_renderer extends plugin_renderer_base {
         $html .= $this->output->heading(get_string('importdatafrom'), 2, array('class' => 'header'));
         $html .= $this->backup_detail_pair(get_string('selectacourse', 'backup'), $this->render($courses));
         $attrs = array('type' => 'submit', 'value' => get_string('continue'), 'class' => 'btn btn-primary');
+        $html .= html_writer::start_tag('div', array('class' => 'mt-3'));
         $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', $attrs));
         $html .= html_writer::end_tag('div');
+        $html .= html_writer::end_tag('div');
         $html .= html_writer::end_tag('form');
         $html .= html_writer::end_tag('div');
         return $html;
@@ -787,7 +789,7 @@ class core_backup_renderer extends plugin_renderer_base {
         if ($component->get_count() === 0) {
             $output .= $this->output->notification(get_string('nomatchingcourses', 'backup'));
 
-            $output .= html_writer::start_tag('div', array('class' => 'ics-search'));
+            $output .= html_writer::start_tag('div', array('class' => 'ics-search form-inline'));
             $attrs = array(
                 'type' => 'text',
                 'name' => restore_course_search::$VAR_SEARCH,
@@ -799,7 +801,7 @@ class core_backup_renderer extends plugin_renderer_base {
                 'type' => 'submit',
                 'name' => 'searchcourses',
                 'value' => get_string('search'),
-                'class' => 'btn btn-secondary'
+                'class' => 'btn btn-secondary ml-1'
             );
             $output .= html_writer::empty_tag('input', $attrs);
             $output .= html_writer::end_tag('div');
@@ -845,7 +847,7 @@ class core_backup_renderer extends plugin_renderer_base {
         $output .= html_writer::table($table);
         $output .= html_writer::end_tag('div');
 
-        $output .= html_writer::start_tag('div', array('class' => 'ics-search'));
+        $output .= html_writer::start_tag('div', array('class' => 'ics-search form-inline'));
         $attrs = array(
             'type' => 'text',
             'name' => restore_course_search::$VAR_SEARCH,
@@ -856,7 +858,7 @@ class core_backup_renderer extends plugin_renderer_base {
             'type' => 'submit',
             'name' => 'searchcourses',
             'value' => get_string('search'),
-            'class' => 'btn btn-secondary'
+            'class' => 'btn btn-secondary ml-1'
         );
         $output .= html_writer::empty_tag('input', $attrs);
         $output .= html_writer::end_tag('div');
index bd24b15..d1bae45 100644 (file)
@@ -20,7 +20,7 @@ Feature: Restore Moodle 2 course backups
     And I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name |
       | Description | Test forum description |
-    And I add the "Community finder" block
+    And I add the "Activities" block
 
   @javascript
   Scenario: Restore a course in another existing course
@@ -28,7 +28,7 @@ Feature: Restore Moodle 2 course backups
       | Confirmation | Filename | test_backup.mbz |
     And I restore "test_backup.mbz" backup into "Course 2" course using this options:
     Then I should see "Course 2"
-    And I should see "Community finder" in the "Community finder" "block"
+    And I should see "Activities" in the "Activities" "block"
     And I should see "Test forum name"
 
   @javascript
@@ -38,7 +38,7 @@ Feature: Restore Moodle 2 course backups
     And I restore "test_backup.mbz" backup into a new course using this options:
       | Schema | Course name | Course 1 restored in a new course |
     Then I should see "Course 1 restored in a new course"
-    And I should see "Community finder" in the "Community finder" "block"
+    And I should see "Activities" in the "Activities" "block"
     And I should see "Test forum name"
     And I should see "Topic 15"
     And I should not see "Topic 16"
@@ -72,7 +72,7 @@ Feature: Restore Moodle 2 course backups
     Then I should see "Course 1"
     And I should not see "Section 3"
     And I should not see "Test forum post backup name"
-    And I should see "Community finder" in the "Community finder" "block"
+    And I should see "Activities" in the "Activities" "block"
     And I should see "Test forum name"
 
   @javascript
diff --git a/blocks/community/block_community.php b/blocks/community/block_community.php
deleted file mode 100644 (file)
index fc345ca..0000000
+++ /dev/null
@@ -1,104 +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/>.
-
-/**
- * @package block_community
- * @author     Jerome Mouneyrac <jerome@mouneyrac.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
- * @copyright  (C) 1999 onwards Martin Dougiamas  http://dougiamas.com
- *
- * The community block
- */
-
-class block_community extends block_list {
-
-    function init() {
-        $this->title = get_string('pluginname', 'block_community');
-    }
-
-    function user_can_addto($page) {
-        // Don't allow people to add the block if they can't even use it
-        if (!has_capability('moodle/community:add', $page->context)) {
-            return false;
-        }
-
-        return parent::user_can_addto($page);
-    }
-
-    function user_can_edit() {
-        // Don't allow people to edit the block if they can't even use it
-        if (!has_capability('moodle/community:add',
-                        context::instance_by_id($this->instance->parentcontextid))) {
-            return false;
-        }
-        return parent::user_can_edit();
-    }
-
-    function get_content() {
-        global $CFG, $OUTPUT, $USER;
-
-        $coursecontext = context::instance_by_id($this->instance->parentcontextid);
-
-        if (!has_capability('moodle/community:add', $coursecontext)
-                or $this->content !== NULL) {
-            return $this->content;
-        }
-
-        $this->content = new stdClass();
-        $this->content->items = array();
-        $this->content->icons = array();
-        $this->content->footer = '';
-
-        if (!isloggedin()) {
-            return $this->content;
-        }
-
-        $icon = $OUTPUT->pix_icon('i/group', get_string('group'));
-        $addcourseurl = new moodle_url('/blocks/community/communitycourse.php',
-                        array('add' => true, 'courseid' => $this->page->course->id));
-        $searchlink = html_writer::tag('a', $icon . get_string('addcourse', 'block_community'),
-                        array('href' => $addcourseurl->out(false)));
-        $this->content->items[] = $searchlink;
-
-        require_once($CFG->dirroot . '/blocks/community/locallib.php');
-        $communitymanager = new block_community_manager();
-        $courses = $communitymanager->block_community_get_courses($USER->id);
-        if ($courses) {
-            $this->content->items[] = html_writer::empty_tag('hr');
-            $this->content->icons[] = '';
-            $this->content->items[] = get_string('mycommunities', 'block_community');
-            $this->content->icons[] = '';
-            foreach ($courses as $course) {
-                //delete link
-                $deleteicon = $OUTPUT->pix_icon('t/delete', get_string('removecommunitycourse', 'block_community'));
-                $deleteurl = new moodle_url('/blocks/community/communitycourse.php',
-                                array('remove' => true,
-                                    'courseid' => $this->page->course->id,
-                                    'communityid' => $course->id, 'sesskey' => sesskey()));
-                $deleteatag = html_writer::tag('a', $deleteicon, array('href' => $deleteurl));
-
-                $courselink = html_writer::tag('a', $course->coursename,
-                                array('href' => $course->courseurl));
-                $this->content->items[] = $courselink . ' ' . $deleteatag;
-                $this->content->icons[] = '';
-            }
-        }
-
-        return $this->content;
-    }
-
-}
-
diff --git a/blocks/community/classes/privacy/provider.php b/blocks/community/classes/privacy/provider.php
deleted file mode 100644 (file)
index dd32b8f..0000000
+++ /dev/null
@@ -1,220 +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/>.
-
-/**
- * Privacy Subsystem implementation for block_community.
- *
- * @package    block_community
- * @copyright  2018 Zig Tan <zig@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-namespace block_community\privacy;
-
-defined('MOODLE_INTERNAL') || die();
-
-use core_privacy\local\request\approved_contextlist;
-use core_privacy\local\request\contextlist;
-use core_privacy\local\request\writer;
-use core_privacy\local\request\deletion_criteria;
-use core_privacy\local\metadata\collection;
-use core_privacy\local\request\userlist;
-use core_privacy\local\request\approved_userlist;
-
-/**
- * Privacy Subsystem implementation for block_community.
- *
- * @copyright  2018 Zig Tan <zig@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class provider implements
-        \core_privacy\local\metadata\provider,
-        \core_privacy\local\request\core_userlist_provider,
-        \core_privacy\local\request\plugin\provider {
-
-    /**
-     * Returns information about how block_community stores its data.
-     *
-     * @param   collection     $collection The initialised collection to add items to.
-     * @return  collection     A listing of user data stored through this system.
-     */
-    public static function get_metadata(collection $collection) : collection {
-        $collection->add_database_table(
-            'block_community',
-            [
-                'coursename' => 'privacy:metadata:block_community:coursename',
-                'coursedescription' => 'privacy:metadata:block_community:coursedescription',
-                'courseurl' => 'privacy:metadata:block_community:courseurl',
-                'imageurl' => 'privacy:metadata:block_community:imageurl',
-                'userid' => 'privacy:metadata:block_community:userid',
-            ],
-            'privacy:metadata:block_community'
-        );
-
-        return $collection;
-    }
-
-    /**
-     * Get the list of contexts that contain user information for the specified user.
-     *
-     * @param   int         $userid     The user to search.
-     * @return  contextlist   $contextlist  The contextlist containing the list of contexts used in this plugin.
-     */
-    public static function get_contexts_for_userid(int $userid) : contextlist {
-        $contextlist = new \core_privacy\local\request\contextlist();
-
-        // The block_community data is associated at the user context level, so retrieve the user's context id.
-        $sql = "SELECT c.id
-                  FROM {block_community} bc
-                  JOIN {context} c ON c.instanceid = bc.userid AND c.contextlevel = :contextuser
-                 WHERE bc.userid = :userid
-              GROUP BY c.id";
-
-        $params = [
-            'contextuser'   => CONTEXT_USER,
-            'userid'        => $userid
-        ];
-
-        $contextlist->add_from_sql($sql, $params);
-
-        return $contextlist;
-    }
-
-    /**
-     * Get the list of users within a specific context.
-     *
-     * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
-     */
-    public static function get_users_in_context(userlist $userlist) {
-        $context = $userlist->get_context();
-
-        if (!$context instanceof \context_user) {
-            return;
-        }
-
-        $sql = "SELECT userid
-                  FROM {block_community}
-                 WHERE userid = ?";
-        $params = [$context->instanceid];
-        $userlist->add_from_sql('userid', $sql, $params);
-    }
-
-    /**
-     * Export all user data for the specified user using the User context level.
-     *
-     * @param   approved_contextlist    $contextlist    The approved contexts to export information for.
-     */
-    public static function export_user_data(approved_contextlist $contextlist) {
-        global $DB;
-
-        // If the user has block_community data, then only the User context should be present so get the first context.
-        $contexts = $contextlist->get_contexts();
-        if (count($contexts) == 0) {
-            return;
-        }
-        $context = reset($contexts);
-
-        // Sanity check that context is at the User context level, then get the userid.
-        if ($context->contextlevel !== CONTEXT_USER) {
-            return;
-        }
-        $userid = $context->instanceid;
-
-        // The block_community data export is organised in: {User Context}/Community Finder/My communities/data.json.
-        $subcontext = [
-            get_string('pluginname', 'block_community'),
-            get_string('mycommunities', 'block_community')
-        ];
-
-        $sql = "SELECT bc.id as id,
-                       bc.coursename as name,
-                       bc.coursedescription as description,
-                       bc.courseurl as url,
-                       bc.imageurl as imageurl
-                  FROM {block_community} bc
-                 WHERE bc.userid = :userid
-              ORDER BY bc.coursename";
-
-        $params = [
-            'userid' => $userid
-        ];
-
-        $communities = $DB->get_records_sql($sql, $params);
-
-        $data = (object) [
-            'communities' => $communities
-        ];
-
-        writer::with_context($context)->export_data($subcontext, $data);
-    }
-
-    /**
-     * Delete all data for all users in the specified context.
-     *
-     * @param   context $context   The specific context to delete data for.
-     */
-    public static function delete_data_for_all_users_in_context(\context $context) {
-        global $DB;
-
-        // Sanity check that context is at the User context level, then get the userid.
-        if ($context->contextlevel !== CONTEXT_USER) {
-            return;
-        }
-        $userid = $context->instanceid;
-
-        $DB->delete_records('block_community', ['userid' => $userid]);
-    }
-
-    /**
-     * Delete multiple users within a single context.
-     *
-     * @param approved_userlist $userlist The approved context and user information to delete information for.
-     */
-    public static function delete_data_for_users(approved_userlist $userlist) {
-        global $DB;
-
-        $context = $userlist->get_context();
-
-        if ($context instanceof \context_user) {
-            $DB->delete_records('block_community', ['userid' => $context->instanceid]);
-        }
-    }
-
-    /**
-     * Delete all user data for the specified user.
-     *
-     * @param   approved_contextlist $contextlist  The approved contexts and user information to delete information for.
-     */
-    public static function delete_data_for_user(approved_contextlist $contextlist) {
-        global $DB;
-
-        // If the user has block_community data, then only the User context should be present so get the first context.
-        $contexts = $contextlist->get_contexts();
-        if (count($contexts) == 0) {
-            return;
-        }
-        $context = reset($contexts);
-
-        // Sanity check that context is at the User context level, then get the userid.
-        if ($context->contextlevel !== CONTEXT_USER) {
-            return;
-        }
-        $userid = $context->instanceid;
-
-        $DB->delete_records('block_community', ['userid' => $userid]);
-    }
-
-}
diff --git a/blocks/community/communitycourse.php b/blocks/community/communitycourse.php
deleted file mode 100644 (file)
index 67f024b..0000000
+++ /dev/null
@@ -1,208 +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/>.
-
-/**
- * Controller for various actions of the block.
- *
- * This page display the community course search form.
- * It also handles adding a course to the community block.
- * It also handles downloading a course template.
- *
- * @package    block_community
- * @author     Jerome Mouneyrac <jerome@mouneyrac.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
- * @copyright  (C) 1999 onwards Martin Dougiamas  http://dougiamas.com
- */
-
-require('../../config.php');
-require_once($CFG->dirroot . '/blocks/community/locallib.php');
-require_once($CFG->dirroot . '/blocks/community/forms.php');
-
-require_login();
-$courseid = required_param('courseid', PARAM_INT); //if no courseid is given
-$parentcourse = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
-
-$context = context_course::instance($courseid);
-$PAGE->set_course($parentcourse);
-$PAGE->set_url('/blocks/community/communitycourse.php');
-$PAGE->set_heading($SITE->fullname);
-$PAGE->set_pagelayout('incourse');
-$PAGE->set_title(get_string('searchcourse', 'block_community'));
-$PAGE->navbar->add(get_string('searchcourse', 'block_community'));
-
-$search = optional_param('search', null, PARAM_TEXT);
-
-//if no capability to search course, display an error message
-require_capability('moodle/community:add', $context);
-$usercandownload = has_capability('moodle/community:download', $context);
-
-$communitymanager = new block_community_manager();
-$renderer = $PAGE->get_renderer('block_community');
-
-/// Check if the page has been called with trust argument
-$add = optional_param('add', -1, PARAM_INT);
-$confirm = optional_param('confirmed', false, PARAM_INT);
-if ($add != -1 and $confirm and confirm_sesskey()) {
-    $course = new stdClass();
-    $course->name = optional_param('coursefullname', '', PARAM_TEXT);
-    $course->description = optional_param('coursedescription', '', PARAM_TEXT);
-    $course->url = optional_param('courseurl', '', PARAM_URL);
-    $course->imageurl = optional_param('courseimageurl', '', PARAM_URL);
-    $communitymanager->block_community_add_course($course, $USER->id);
-    echo $OUTPUT->header();
-    echo $renderer->save_link_success(
-            new moodle_url('/course/view.php', array('id' => $courseid)));
-    echo $OUTPUT->footer();
-    die();
-}
-
-/// Delete temp file when cancel restore
-$cancelrestore = optional_param('cancelrestore', false, PARAM_INT);
-if ($usercandownload and $cancelrestore and confirm_sesskey()) {
-    $filename = optional_param('filename', '', PARAM_ALPHANUMEXT);
-    //delete temp file
-    $backuptempdir = make_backup_temp_directory('');
-    unlink($backuptempdir . '/' . $filename . ".mbz");
-}
-
-/// Download
-$download = optional_param('download', -1, PARAM_INT);
-$downloadcourseid = optional_param('downloadcourseid', '', PARAM_INT);
-$coursefullname = optional_param('coursefullname', '', PARAM_ALPHANUMEXT);
-$backupsize = optional_param('backupsize', 0, PARAM_INT);
-if ($usercandownload and $download != -1 and !empty($downloadcourseid) and confirm_sesskey()) {
-    //OUTPUT: display restore choice page
-    echo $OUTPUT->header();
-    echo $OUTPUT->heading(get_string('downloadingcourse', 'block_community'), 3, 'main');
-    $sizeinfo = new stdClass();
-    $sizeinfo->total = number_format($backupsize / 1000000, 2);
-    echo html_writer::tag('div', get_string('downloadingsize', 'block_community', $sizeinfo),
-            array('class' => 'textinfo'));
-    if (ob_get_level()) {
-        ob_flush();
-    }
-    flush();
-    list($privatefilename, $tmpfilename) = \core\hub\publication::download_course_backup($downloadcourseid, $coursefullname);
-    echo html_writer::tag('div', get_string('downloaded', 'block_community'),
-            array('class' => 'textinfo'));
-    echo $OUTPUT->notification(get_string('downloadconfirmed', 'block_community',
-                    $privatefilename), 'notifysuccess');
-    echo $renderer->restore_confirmation_box($tmpfilename, $context);
-    echo $OUTPUT->footer();
-    die();
-}
-
-/// Remove community
-$remove = optional_param('remove', '', PARAM_INT);
-$communityid = optional_param('communityid', '', PARAM_INT);
-if ($remove != -1 and !empty($communityid) and confirm_sesskey()) {
-    $communitymanager->block_community_remove_course($communityid, $USER->id);
-    echo $OUTPUT->header();
-    echo $renderer->remove_success(new moodle_url('/course/view.php', array('id' => $courseid)));
-    echo $OUTPUT->footer();
-    die();
-}
-
-//Get form default/current values
-$fromformdata['coverage'] = optional_param('coverage', 'all', PARAM_TEXT);
-$fromformdata['licence'] = optional_param('licence', 'all', PARAM_ALPHANUMEXT);
-$fromformdata['subject'] = optional_param('subject', 'all', PARAM_ALPHANUMEXT);
-$fromformdata['audience'] = optional_param('audience', 'all', PARAM_ALPHANUMEXT);
-$fromformdata['language'] = optional_param('language', current_language(), PARAM_ALPHANUMEXT);
-$fromformdata['educationallevel'] = optional_param('educationallevel', 'all', PARAM_ALPHANUMEXT);
-$fromformdata['downloadable'] = optional_param('downloadable', $usercandownload, PARAM_ALPHANUM);
-$fromformdata['orderby'] = optional_param('orderby', 'newest', PARAM_ALPHA);
-$fromformdata['search'] = $search;
-$fromformdata['courseid'] = $courseid;
-$hubselectorform = new community_hub_search_form('', $fromformdata);
-$hubselectorform->set_data($fromformdata);
-
-//Retrieve courses by web service
-$courses = null;
-if (optional_param('executesearch', 0, PARAM_INT) and confirm_sesskey()) {
-    $downloadable = optional_param('downloadable', false, PARAM_INT);
-
-    $options = new stdClass();
-    if (!empty($fromformdata['coverage'])) {
-        $options->coverage = $fromformdata['coverage'];
-    }
-    if ($fromformdata['licence'] != 'all') {
-        $options->licenceshortname = $fromformdata['licence'];
-    }
-    if ($fromformdata['subject'] != 'all') {
-        $options->subject = $fromformdata['subject'];
-    }
-    if ($fromformdata['audience'] != 'all') {
-        $options->audience = $fromformdata['audience'];
-    }
-    if ($fromformdata['educationallevel'] != 'all') {
-        $options->educationallevel = $fromformdata['educationallevel'];
-    }
-    if ($fromformdata['language'] != 'all') {
-        $options->language = $fromformdata['language'];
-    }
-
-    $options->orderby = $fromformdata['orderby'];
-
-    //the range of course requested
-    $options->givememore = optional_param('givememore', 0, PARAM_INT);
-
-    list($courses, $coursetotal) = \core\hub\publication::search($search, $downloadable, $options);
-}
-
-// OUTPUT
-echo $OUTPUT->header();
-echo $OUTPUT->heading(get_string('searchcommunitycourse', 'block_community'), 3, 'main');
-echo $renderer->moodlenet_info();
-
-$hubselectorform->display();
-if (!empty($errormessage)) {
-    echo $errormessage;
-}
-
-//load javascript
-$commentedcourseids = array(); //result courses with comments only
-$courseids = array(); //all result courses
-$courseimagenumbers = array(); //number of screenshots of all courses (must be exact same order than $courseids)
-if (!empty($courses)) {
-    foreach ($courses as $course) {
-        if (!empty($course['comments'])) {
-            $commentedcourseids[] = $course['id'];
-        }
-        $courseids[] = $course['id'];
-        $courseimagenumbers[] = $course['screenshots'];
-    }
-}
-$PAGE->requires->yui_module('moodle-block_community-comments', 'M.blocks_community.init_comments',
-        array(array('commentids' => $commentedcourseids, 'closeButtonTitle' => get_string('close', 'editor'))));
-$PAGE->requires->yui_module('moodle-block_community-imagegallery', 'M.blocks_community.init_imagegallery',
-        array(array('imageids' => $courseids, 'imagenumbers' => $courseimagenumbers,
-                'huburl' => HUB_MOODLEORGHUBURL, 'closeButtonTitle' => get_string('close', 'editor'))));
-
-echo highlight($search, $renderer->course_list($courses, null, $courseid));
-
-//display givememore/Next link if more course can be displayed
-if (!empty($courses)) {
-    if (($options->givememore + count($courses)) < $coursetotal) {
-        $fromformdata['givememore'] = count($courses) + $options->givememore;
-        $fromformdata['executesearch'] = true;
-        $fromformdata['sesskey'] = sesskey();
-        echo $renderer->next_button($fromformdata);
-    }
-}
-
-echo $OUTPUT->footer();
diff --git a/blocks/community/db/access.php b/blocks/community/db/access.php
deleted file mode 100644 (file)
index 1ec36ed..0000000
+++ /dev/null
@@ -1,51 +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/>.
-
-/**
- * Community block caps.
- *
- * @package    block_community
- * @copyright  Mark Nelson <markn@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-$capabilities = array(
-
-    'block/community:myaddinstance' => array(
-        'captype' => 'write',
-        'contextlevel' => CONTEXT_SYSTEM,
-        'archetypes' => array(
-            'user' => CAP_ALLOW
-        ),
-
-        'clonepermissionsfrom' => 'moodle/my:manageblocks'
-    ),
-
-    'block/community:addinstance' => array(
-        'riskbitmask' => RISK_SPAM | RISK_XSS,
-
-        'captype' => 'write',
-        'contextlevel' => CONTEXT_BLOCK,
-        'archetypes' => array(
-            'editingteacher' => CAP_ALLOW,
-            'manager' => CAP_ALLOW
-        ),
-
-        'clonepermissionsfrom' => 'moodle/site:manageblocks'
-    ),
-);
diff --git a/blocks/community/db/install.xml b/blocks/community/db/install.xml
deleted file mode 100644 (file)
index 58705d7..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="blocks/community/db" VERSION="20120122" COMMENT="XMLDB file for Moodle blocks/community"
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
->
-  <TABLES>
-    <TABLE NAME="block_community" COMMENT="Community block">
-      <FIELDS>
-        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
-        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="coursename" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="coursedescription" TYPE="text" LENGTH="big" NOTNULL="false" SEQUENCE="false"/>
-        <FIELD NAME="courseurl" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="imageurl" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
-      </FIELDS>
-      <KEYS>
-        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
-      </KEYS>
-    </TABLE>
-  </TABLES>
-</XMLDB>
\ No newline at end of file
diff --git a/blocks/community/db/upgrade.php b/blocks/community/db/upgrade.php
deleted file mode 100644 (file)
index 1a593de..0000000
+++ /dev/null
@@ -1,65 +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/>.
-
-/**
- * This file keeps track of upgrades to the community block
- *
- * Sometimes, changes between versions involve alterations to database structures
- * and other major things that may break installations.
- *
- * The upgrade function in this file will attempt to perform all the necessary
- * actions to upgrade your older installation to the current version.
- *
- * If there's something it cannot do itself, it will tell you what you need to do.
- *
- * The commands in here will all be database-neutral, using the methods of
- * database_manager class
- *
- * Please do not forget to use upgrade_set_timeout()
- * before any action that may take longer time to finish.
- *
- * @since Moodle 2.0
- * @package block_community
- * @copyright 2010 Jerome Mouneyrac
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- *
- * @param int $oldversion
- */
-function xmldb_block_community_upgrade($oldversion) {
-    global $CFG;
-
-    // Automatically generated Moodle v3.3.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    // Automatically generated Moodle v3.4.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    // Automatically generated Moodle v3.5.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    // Automatically generated Moodle v3.6.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    // Automatically generated Moodle v3.7.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    return true;
-}
diff --git a/blocks/community/forms.php b/blocks/community/forms.php
deleted file mode 100644 (file)
index 8142b5c..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-<?php
-///////////////////////////////////////////////////////////////////////////
-//                                                                       //
-// This file is part of Moodle - http://moodle.org/                      //
-// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
-//                                                                       //
-// 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/>.       //
-//                                                                       //
-///////////////////////////////////////////////////////////////////////////
-
-/**
- * Form for community search
- *
- * @package    block_community
- * @author     Jerome Mouneyrac <jerome@mouneyrac.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
- * @copyright  (C) 1999 onwards Martin Dougiamas  http://dougiamas.com
- */
-
-require_once($CFG->libdir . '/formslib.php');
-
-class community_hub_search_form extends moodleform {
-
-    public function definition() {
-        global $CFG;
-        $mform = & $this->_form;
-
-        //set default value
-        $search = $this->_customdata['search'];
-        if (isset($this->_customdata['coverage'])) {
-            $coverage = $this->_customdata['coverage'];
-        } else {
-            $coverage = 'all';
-        }
-        if (isset($this->_customdata['licence'])) {
-            $licence = $this->_customdata['licence'];
-        } else {
-            $licence = 'all';
-        }
-        if (isset($this->_customdata['subject'])) {
-            $subject = $this->_customdata['subject'];
-        } else {
-            $subject = 'all';
-        }
-        if (isset($this->_customdata['audience'])) {
-            $audience = $this->_customdata['audience'];
-        } else {
-            $audience = 'all';
-        }
-        if (isset($this->_customdata['language'])) {
-            $language = $this->_customdata['language'];
-        } else {
-            $language = current_language();
-        }
-        if (isset($this->_customdata['educationallevel'])) {
-            $educationallevel = $this->_customdata['educationallevel'];
-        } else {
-            $educationallevel = 'all';
-        }
-        if (isset($this->_customdata['downloadable'])) {
-            $downloadable = $this->_customdata['downloadable'];
-        } else {
-            $downloadable = 1;
-        }
-        if (isset($this->_customdata['orderby'])) {
-            $orderby = $this->_customdata['orderby'];
-        } else {
-            $orderby = 'newest';
-        }
-
-        $mform->addElement('header', 'site', get_string('search', 'block_community'));
-
-        //add the course id (of the context)
-        $mform->addElement('hidden', 'courseid', $this->_customdata['courseid']);
-        $mform->setType('courseid', PARAM_INT);
-        $mform->addElement('hidden', 'executesearch', 1);
-        $mform->setType('executesearch', PARAM_INT);
-
-        // Display enrol/download select box if the USER has the download capability on the course.
-        if (has_capability('moodle/community:download',
-                        context_course::instance($this->_customdata['courseid']))) {
-            $options = array(0 => get_string('enrollable', 'block_community'),
-                1 => get_string('downloadable', 'block_community'));
-            $mform->addElement('select', 'downloadable', get_string('enroldownload', 'block_community'),
-                    $options);
-            $mform->addHelpButton('downloadable', 'enroldownload', 'block_community');
-
-            $mform->setDefault('downloadable', $downloadable);
-        } else {
-            $mform->addElement('hidden', 'downloadable', 0);
-        }
-        $mform->setType('downloadable', PARAM_INT);
-
-        $options = \core\hub\publication::audience_options(true);
-        $mform->addElement('select', 'audience', get_string('audience', 'block_community'), $options);
-        $mform->setDefault('audience', $audience);
-        unset($options);
-        $mform->addHelpButton('audience', 'audience', 'block_community');
-
-        $options = \core\hub\publication::educational_level_options(true);
-        $mform->addElement('select', 'educationallevel',
-                get_string('educationallevel', 'block_community'), $options);
-        $mform->setDefault('educationallevel', $educationallevel);
-        unset($options);
-        $mform->addHelpButton('educationallevel', 'educationallevel', 'block_community');
-
-        $options = \core\hub\publication::get_sorted_subjects();
-        $mform->addElement('searchableselector', 'subject', get_string('subject', 'block_community'),
-                $options, array('id' => 'communitysubject'));
-        $mform->setDefault('subject', $subject);
-        unset($options);
-        $mform->addHelpButton('subject', 'subject', 'block_community');
-
-        require_once($CFG->libdir . "/licenselib.php");
-        $licensemanager = new license_manager();
-        $licences = $licensemanager->get_licenses();
-        $options = array();
-        $options['all'] = get_string('any');
-        foreach ($licences as $license) {
-            $options[$license->shortname] = get_string($license->shortname, 'license');
-        }
-        $mform->addElement('select', 'licence', get_string('licence', 'block_community'), $options);
-        unset($options);
-        $mform->addHelpButton('licence', 'licence', 'block_community');
-        $mform->setDefault('licence', $licence);
-
-        $languages = get_string_manager()->get_list_of_languages();
-        core_collator::asort($languages);
-        $languages = array_merge(array('all' => get_string('any')), $languages);
-        $mform->addElement('select', 'language', get_string('language'), $languages);
-
-        $mform->setDefault('language', $language);
-        $mform->addHelpButton('language', 'language', 'block_community');
-
-        $mform->addElement('select', 'orderby', get_string('orderby', 'block_community'),
-            array('newest' => get_string('orderbynewest', 'block_community'),
-                'eldest' => get_string('orderbyeldest', 'block_community'),
-                'fullname' => get_string('orderbyname', 'block_community'),
-                'publisher' => get_string('orderbypublisher', 'block_community'),
-                'ratingaverage' => get_string('orderbyratingaverage', 'block_community')));
-
-        $mform->setDefault('orderby', $orderby);
-        $mform->addHelpButton('orderby', 'orderby', 'block_community');
-        $mform->setType('orderby', PARAM_ALPHA);
-
-        $mform->setAdvanced('audience');
-        $mform->setAdvanced('educationallevel');
-        $mform->setAdvanced('subject');
-        $mform->setAdvanced('licence');
-        $mform->setAdvanced('language');
-        $mform->setAdvanced('orderby');
-
-        $mform->addElement('text', 'search', get_string('keywords', 'block_community'),
-            array('size' => 30));
-        $mform->addHelpButton('search', 'keywords', 'block_community');
-        $mform->setType('search', PARAM_NOTAGS);
-
-        $mform->addElement('submit', 'submitbutton', get_string('search', 'block_community'));
-
-    }
-
-}
diff --git a/blocks/community/lang/en/block_community.php b/blocks/community/lang/en/block_community.php
deleted file mode 100644 (file)
index 73e65d4..0000000
+++ /dev/null
@@ -1,121 +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/>.
-
-/**
- * Strings for component 'block_community', language 'en', branch 'MOODLE_20_STABLE'
- *
- * @package   block_community
- * @author    Jerome Mouneyrac <jerome@mouneyrac.com>
- * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-$string['activities'] = 'Activities';
-$string['add'] = 'Add';
-$string['addedtoblock'] = 'A link to this course has been added in your community finder block';
-$string['addtocommunityblock'] = 'Save a link to this course';
-$string['addcommunitycourse'] = 'Add community course';
-$string['additionalcoursedesc'] = '{$a->lang} Creator: {$a->creatorname} - Publisher: {$a->publishername} - Subject: {$a->subject}
-    - Audience: {$a->audience} - Educational level: {$a->educationallevel} - License: {$a->license}';
-$string['addcourse'] = 'Search';
-$string['audience'] = 'Designed for';
-$string['audience_help'] = 'What kind of course are you looking for?  As well as traditional courses intended for students, you might search for communities of Educators or Moodle Administrators';
-$string['blocks'] = 'Blocks';
-$string['cannotselecttopsubject'] = 'Cannot select a top subject level';
-$string['comments'] = 'Comments ({$a})';
-$string['community:addinstance'] = 'Add a new community finder block';
-$string['community:myaddinstance'] = 'Add a new community finder block to Dashboard';
-$string['contentinfo'] = 'Subject: {$a->subject} - Audience: {$a->audience} - Educational level: {$a->educationallevel}';
-$string['continue'] = 'Continue';
-$string['contributors'] = ' - Contributors: {$a}';
-$string['coursedesc'] = 'Description';
-$string['courselang'] = 'Language';
-$string['coursename'] = 'Name';
-$string['courses'] = 'Courses';
-$string['coverage'] = 'Tags: {$a}';
-$string['donotrestore'] = 'No';
-$string['dorestore'] = 'Yes';
-$string['download'] = 'Download';
-$string['downloadable'] = 'courses I can download';
-$string['downloadablecourses'] = 'Downloadable courses';
-$string['downloadconfirmed'] = 'The backup has been saved in your private files {$a}';
-$string['downloaded'] = '...finished.';
-$string['downloadingcourse'] = 'Downloading course';
-$string['downloadingsize'] = 'Please wait the course file is downloading ({$a->total}Mb)...';
-$string['downloadtemplate'] = 'Create course from template';
-$string['educationallevel'] = 'Educational level';
-$string['educationallevel_help'] = 'What educational level are you searching for?  In the case of communities of educators, this level describes the level they are teaching.';
-$string['enroldownload'] = 'Find';
-$string['enroldownload_help'] = 'Some courses listed in the selected hub are being advertised so that people can come and participate in them on the original site.
-
-Others are course templates provided for you to download and use on your own Moodle site.';
-$string['enrollable'] = 'courses I can enrol in';
-$string['enrollablecourses'] = 'Enrollable courses';
-$string['errorcourselisting'] = 'An error occurred when retrieving the course listing from the selected hub, please try again later. ({$a})';
-$string['errorhublisting'] = 'An error occurred when retrieving the hub listing from Moodle.org, please try again later. ({$a})';
-$string['fileinfo'] = 'Language: {$a->lang} - License: {$a->license} -  Time updated: {$a->timeupdated}';
-$string['hideall'] = 'Hide hubs';
-$string['hub'] = 'hub';
-$string['hubnottrusted'] = 'Not trusted';
-$string['hubtrusted'] = 'This hub is trusted by Moodle.org';
-$string['install'] = 'Install';
-$string['keywords'] = 'Keywords';
-$string['keywords_help'] = 'You can search for courses containing specific text in the name, description and other fields of the database.';
-$string['langdesc'] = 'Language: {$a} - ';
-$string['language'] = 'Language';
-$string['language_help'] = 'You can search for courses written in a specific language.';
-$string['licence'] = 'License';
-$string['licence_help'] = 'You can search for courses that are licensed in a particular way.';
-$string['moredetails'] = 'More details';
-$string['mycommunities'] = 'My communities:';
-$string['next'] = 'Next >>>';
-$string['nocomments'] = 'No comments';
-$string['nocourse'] = 'No courses found';
-$string['noratings'] = 'No ratings';
-$string['operation'] = 'Operation';
-$string['orderby'] = 'Sort by';
-$string['orderby_help'] = 'The order the search results are displayed.';
-$string['orderbynewest'] = 'Newest';
-$string['orderbyeldest'] = 'Oldest';
-$string['orderbyname'] = 'Name';
-$string['orderbypublisher'] = 'Publisher';
-$string['orderbyratingaverage'] = 'Rating';
-$string['outcomes'] = 'Outcomes: {$a}';
-$string['pluginname'] = 'Community finder';
-$string['privacy:metadata:block_community'] = 'The Community block stores links to shared community courses users can enrol in.';
-$string['privacy:metadata:block_community:coursename'] = 'The name of the linked community course.';
-$string['privacy:metadata:block_community:coursedescription'] = 'The description of the linked community course.';
-$string['privacy:metadata:block_community:courseurl'] = 'The course URL of the linked community course.';
-$string['privacy:metadata:block_community:imageurl'] = 'The image URL of the linked community course.';
-$string['privacy:metadata:block_community:userid'] = 'The ID of the user who created the linked community course.';
-$string['rateandcomment'] = 'Rate and comment';
-$string['rating'] = 'Rating';
-$string['removecommunitycourse'] = 'Remove community course';
-$string['restorecourse'] = 'Restore course';
-$string['restorecourseinfo'] = 'Restore the course?';
-$string['screenshots'] = 'Screenshots';
-$string['search'] = 'Search';
-$string['searchcommunitycourse'] = 'Search for community course';
-$string['searchcourse'] = 'Search for community course';
-$string['selecthub'] = 'Select hub';
-$string['selecthub_help'] = 'Select hub where to search the courses.';
-$string['sites'] = 'Sites';
-$string['showall'] = 'Show all hubs';
-$string['subject'] = 'Subject';
-$string['subject_help'] = 'To narrow your search to courses about a particular subject, choose one from this list.';
-$string['userinfo'] = 'Creator: {$a->creatorname} - Publisher: {$a->publishername}';
-$string['visitdemo'] = 'Visit demo';
-$string['visitsite'] = 'Visit site';
diff --git a/blocks/community/locallib.php b/blocks/community/locallib.php
deleted file mode 100644 (file)
index d975078..0000000
+++ /dev/null
@@ -1,88 +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/>.
-
-/**
- * Community library
- *
- * @package    block_community
- * @author     Jerome Mouneyrac <jerome@mouneyrac.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
- * @copyright  (C) 1999 onwards Martin Dougiamas  http://dougiamas.com
- *
- *
- */
-
-class block_community_manager {
-
-    /**
-     * Add a community course
-     * @param object $course
-     * @param integer $userid
-     * @return id of course or false if already added
-     */
-    public function block_community_add_course($course, $userid) {
-        global $DB;
-
-        $community = $this->block_community_get_course($course->url, $userid);
-
-        if (empty($community)) {
-            $community = new stdClass();
-            $community->userid = $userid;
-            $community->coursename = $course->name;
-            $community->coursedescription = $course->description;
-            $community->courseurl = $course->url;
-            $community->imageurl = $course->imageurl;
-            return $DB->insert_record('block_community', $community);
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Return all community courses of a user
-     * @param integer $userid
-     * @return array of course
-     */
-    public function block_community_get_courses($userid) {
-        global $DB;
-        return $DB->get_records('block_community', array('userid' => $userid), 'coursename');
-    }
-
-    /**
-     * Return a community courses of a user
-     * @param integer $userid
-     * @param integer $userid
-     * @return array of course
-     */
-    public function block_community_get_course($courseurl, $userid) {
-        global $DB;
-        return $DB->get_record('block_community',
-                array('courseurl' => $courseurl, 'userid' => $userid));
-    }
-
-    /**
-     * Delete a community course
-     * @param integer $communityid
-     * @param integer $userid
-     * @return bool true
-     */
-    public function block_community_remove_course($communityid, $userid) {
-        global $DB, $USER;
-        return $DB->delete_records('block_community',
-                array('userid' => $userid, 'id' => $communityid));
-    }
-
-}
diff --git a/blocks/community/renderer.php b/blocks/community/renderer.php
deleted file mode 100644 (file)
index f415204..0000000
+++ /dev/null
@@ -1,400 +0,0 @@
-<?php
-
-///////////////////////////////////////////////////////////////////////////
-//                                                                       //
-// This file is part of Moodle - http://moodle.org/                      //
-// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
-//                                                                       //
-// 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/>.       //
-//                                                                       //
-///////////////////////////////////////////////////////////////////////////
-
-/**
- * Block community renderer.
- * @package   block_community
- * @copyright 2010 Moodle Pty Ltd (http://moodle.com)
- * @author    Jerome Mouneyrac
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class block_community_renderer extends plugin_renderer_base {
-
-    public function restore_confirmation_box($filename, $context) {
-        $restoreurl = new moodle_url('/backup/restore.php',
-                        array('filename' => $filename . ".mbz", 'contextid' => $context->id));
-        $searchurl = new moodle_url('/blocks/community/communitycourse.php',
-                        array('add' => 1, 'courseid' => $context->instanceid,
-                            'cancelrestore' => 1, 'sesskey' => sesskey(),
-                            'filename' => $filename));
-        $formrestore = new single_button($restoreurl,
-                        get_string('dorestore', 'block_community'));
-        $formsearch = new single_button($searchurl,
-                        get_string('donotrestore', 'block_community'));
-        return $this->output->confirm(get_string('restorecourseinfo', 'block_community'),
-                $formrestore, $formsearch);
-    }
-
-    /**
-     * Display remove community success message and a button to be redirected to te referer page
-     * @param moodle_url $url the page to be redirected to
-     * @return string html
-     */
-    public function remove_success(moodle_url $url) {
-        $html = $this->output->notification(get_string('communityremoved', 'hub'),
-                    'notifysuccess');
-        $continuebutton = new single_button($url,
-                        get_string('continue', 'block_community'));
-        $html .= html_writer::tag('div', $this->output->render($continuebutton),
-                array('class' => 'continuebutton'));
-        return $html;
-    }
-
-    /**
-     * Display add community course success message and a button to be redirected to te referer page
-     * @param moodle_url $url the page to be redirected to
-     * @return string html
-     */
-    public function save_link_success(moodle_url $url) {
-        $html = $this->output->notification(get_string('addedtoblock', 'block_community'),
-                    'notifysuccess');
-        $continuebutton = new single_button($url,
-                        get_string('continue', 'block_community'));
-        $html .= html_writer::tag('div', $this->output->render($continuebutton),
-                array('class' => 'continuebutton'));
-        return $html;
-    }
-
-    /**
-     * The 'Next'/'more course result' link for a courses search
-     * @param array $data - the form parameter to execute the search on more result
-     * @return string html code
-     */
-    public function next_button($data) {
-        $nextlink = html_writer::tag('a', get_string('next', 'block_community'),
-                array('href' => new moodle_url('', $data)));
-        return html_writer::tag('div', $nextlink, array( 'class' => 'nextlink'));
-    }
-
-    /**
-     * Displays information about moodle.net above course search form
-     *
-     * @return string
-     */
-    public function moodlenet_info() {
-        if (!$info = \core\hub\registration::get_moodlenet_info()) {
-            return '';
-        }
-
-        $image = html_writer::div(html_writer::img($info['imgurl'], $info['name']), 'hubimage');
-
-        $namelink = html_writer::link($info['url'], html_writer::tag('h2', $info['name']), array('class' => 'hubtitlelink'));
-        $description = clean_param($info['description'], PARAM_TEXT);
-        $descriptiontext = html_writer::div(format_text($description, FORMAT_PLAIN), 'hubdescription');
-
-        $additionaldesc = get_string('enrollablecourses', 'block_community') . ': ' . $info['enrollablecourses'] . ' - ' .
-            get_string('downloadablecourses', 'block_community') . ': ' . $info['downloadablecourses'];
-        $stats = html_writer::div(html_writer::tag('div', $additionaldesc), 'hubstats');
-
-        $text = html_writer::div($descriptiontext . $stats, 'hubtext');
-
-        $imgandtext = html_writer::div($image . $text, 'hubimgandtext');
-
-        $fulldesc = html_writer::div($namelink . $imgandtext, 'hubmainhmtl clearfix');
-
-        return html_writer::div($fulldesc, 'formlisting');
-    }
-
-    /**
-     * Display a list of courses
-     * @param array $courses
-     * @param mixed $unused parameter is not used
-     * @param int $contextcourseid context course id
-     * @return string
-     */
-    public function course_list($courses, $unused, $contextcourseid) {
-        global $CFG;
-
-        $renderedhtml = '';
-
-        if (empty($courses)) {
-            if (isset($courses)) {
-                $renderedhtml .= get_string('nocourse', 'block_community');
-            }
-        } else {
-            $courseiteration = 0;
-            foreach ($courses as $course) {
-                $course = (object) $course;
-                $courseiteration = $courseiteration + 1;
-
-                //create visit link html
-                if (!empty($course->courseurl)) {
-                    $courseurl = new moodle_url($course->courseurl);
-                    $linktext = get_string('visitsite', 'block_community');
-                } else {
-                    $courseurl = new moodle_url($course->demourl);
-                    $linktext = get_string('visitdemo', 'block_community');
-                }
-
-                $visitlinkhtml = html_writer::tag('a', $linktext,
-                                array('href' => $courseurl, 'class' => 'hubcoursedownload',
-                                    'onclick' => 'this.target="_blank"'));
-
-                //create title html
-                $coursename = html_writer::tag('h3', $course->fullname,
-                                array('class' => 'hubcoursetitle'));
-                $coursenamehtml = html_writer::tag('div', $coursename,
-                        array('class' => 'hubcoursetitlepanel'));
-
-                // create screenshots html
-                $screenshothtml = '';
-                if (!empty($course->screenshotbaseurl)) {
-                    $screenshothtml = html_writer::empty_tag('img',
-                        array('src' => $course->screenshotbaseurl, 'alt' => $course->fullname));
-                }
-                $coursescreenshot = html_writer::tag('div', $screenshothtml,
-                                array('class' => 'coursescreenshot',
-                                    'id' => 'image-' . $course->id));
-
-                //create description html
-                $deschtml = html_writer::tag('div', $course->description,
-                                array('class' => 'hubcoursedescription'));
-
-                //create users related information html
-                $courseuserinfo = get_string('userinfo', 'block_community', $course);
-                if ($course->contributornames) {
-                    $courseuserinfo .= ' - ' . get_string('contributors', 'block_community',
-                                    $course->contributornames);
-                }
-                $courseuserinfohtml = html_writer::tag('div', $courseuserinfo,
-                                array('class' => 'hubcourseuserinfo'));
-
-                //create course content related information html
-                $course->subject = (get_string_manager()->string_exists($course->subject, 'edufields')) ?
-                        get_string($course->subject, 'edufields') : get_string('none');
-                $course->audience = get_string('audience' . $course->audience, 'hub');
-                $course->educationallevel = get_string('edulevel' . $course->educationallevel, 'hub');
-                $coursecontentinfo = '';
-                if (empty($course->coverage)) {
-                    $course->coverage = '';
-                } else {
-                    $coursecontentinfo .= get_string('coverage', 'block_community', $course->coverage);
-                    $coursecontentinfo .= ' - ';
-                }
-                $coursecontentinfo .= get_string('contentinfo', 'block_community', $course);
-                $coursecontentinfohtml = html_writer::tag('div', $coursecontentinfo,
-                                array('class' => 'hubcoursecontentinfo'));
-
-                ///create course file related information html
-                //language
-                if (!empty($course->language)) {
-                    $languages = get_string_manager()->get_list_of_languages();
-                    $course->lang = $languages[$course->language];
-                } else {
-                    $course->lang = '';
-                }
-                //licence
-                require_once($CFG->libdir . "/licenselib.php");
-                $licensemanager = new license_manager();
-                $licenses = $licensemanager->get_licenses();
-                foreach ($licenses as $license) {
-                    if ($license->shortname == $course->licenceshortname) {
-                        $course->license = $license->fullname;
-                    }
-                }
-                $course->timeupdated = userdate($course->timemodified);
-                $coursefileinfo = get_string('fileinfo', 'block_community', $course);
-                $coursefileinfohtml = html_writer::tag('div', $coursefileinfo,
-                                array('class' => 'hubcoursefileinfo'));
-
-
-
-                //Create course content html
-                $blocks = core_component::get_plugin_list('block');
-                $activities = core_component::get_plugin_list('mod');
-                if (!empty($course->contents)) {
-                    $activitieshtml = '';
-                    $blockhtml = '';
-                    foreach ($course->contents as $content) {
-                        $content = (object) $content;
-                        if ($content->moduletype == 'block') {
-                            if (!empty($blockhtml)) {
-                                $blockhtml .= ' - ';
-                            }
-                            if (array_key_exists($content->modulename, $blocks)) {
-                                $blockname = get_string('pluginname', 'block_' . $content->modulename);
-                            } else {
-                                $blockname = $content->modulename;
-                            }
-                            $blockhtml .= $blockname . " (" . $content->contentcount . ")";
-                        } else {
-                            if (!empty($activitieshtml)) {
-                                $activitieshtml .= ' - ';
-                            }
-                            if (array_key_exists($content->modulename, $activities)) {
-                                $activityname = get_string('modulename', $content->modulename);
-                            } else {
-                                $activityname = $content->modulename;
-                            }
-                            $activitieshtml .= $activityname . " (" . $content->contentcount . ")";
-                        }
-                    }
-
-                    $blocksandactivities = html_writer::tag('div',
-                                    get_string('activities', 'block_community') . " : " . $activitieshtml);
-
-                    //Uncomment following lines to display blocks information
-//                    $blocksandactivities .= html_writer::tag('span',
-//                                    get_string('blocks', 'block_community') . " : " . $blockhtml);
-                }
-
-                //Create outcomes html
-                $outcomes= '';
-                if (!empty($course->outcomes)) {
-                    foreach ($course->outcomes as $outcome) {
-                        if (!empty($outcomes)) {
-                            $outcomes .= ', ';
-                        }
-                        $outcomes .= $outcome['fullname'];
-                    }
-                    $outcomes = get_string('outcomes', 'block_community',
-                            $outcomes);
-                }
-                $outcomeshtml = html_writer::tag('div', $outcomes, array('class' => 'hubcourseoutcomes'));
-
-                //create additional information html
-                $additionaldesc = $courseuserinfohtml . $coursecontentinfohtml
-                        . $coursefileinfohtml . $blocksandactivities . $outcomeshtml;
-                $additionaldeschtml = html_writer::tag('div', $additionaldesc,
-                                array('class' => 'additionaldesc'));
-
-                //Create add button html
-                $addbuttonhtml = "";
-                if ($course->enrollable) {
-                    $params = array('sesskey' => sesskey(), 'add' => 1, 'confirmed' => 1,
-                        'coursefullname' => $course->fullname, 'courseurl' => $courseurl,
-                        'coursedescription' => $course->description,
-                        'courseid' => $contextcourseid);
-                    $addurl = new moodle_url("/blocks/community/communitycourse.php", $params);
-                    $addbuttonhtml = html_writer::tag('a',
-                                    get_string('addtocommunityblock', 'block_community'),
-                                    array('href' => $addurl, 'class' => 'centeredbutton, hubcoursedownload'));
-                }
-
-                //create download button html
-                $downloadbuttonhtml = "";
-                if (!$course->enrollable) {
-                    $params = array('sesskey' => sesskey(), 'download' => 1, 'confirmed' => 1,
-                        'remotemoodleurl' => $CFG->wwwroot, 'courseid' => $contextcourseid,
-                        'downloadcourseid' => $course->id,
-                        'coursefullname' => $course->fullname, 'backupsize' => $course->backupsize);
-                    $downloadurl = new moodle_url("/blocks/community/communitycourse.php", $params);
-                    $downloadbuttonhtml = html_writer::tag('a', get_string('install', 'block_community'),
-                                    array('href' => $downloadurl, 'class' => 'centeredbutton, hubcoursedownload'));
-                }
-
-                //Create rating html
-                $rating = html_writer::tag('div', get_string('noratings', 'block_community'),
-                                array('class' => 'norating'));
-                if (!empty($course->rating)) {
-                    $course->rating = (object) $course->rating;
-                    if ($course->rating->count > 0) {
-
-                        //calculate size of the rating star
-                        $starimagesize = 20; //in px
-                        $numberofstars = 5;
-                        $size = ($course->rating->aggregate / $course->rating->scaleid)
-                                * $numberofstars * $starimagesize;
-                        $rating = html_writer::tag('li', '',
-                                        array('class' => 'current-rating',
-                                            'style' => 'width:' . $size . 'px;'));
-
-                        $rating = html_writer::tag('ul', $rating,
-                                        array('class' => 'star-rating clearfix'));
-                        $rating .= html_writer::tag('div', ' (' . $course->rating->count . ')',
-                                        array('class' => 'ratingcount clearfix'));
-                    }
-                }
-
-
-                //Create comments html
-                $coursecomments = html_writer::tag('div', get_string('nocomments', 'block_community'),
-                                array('class' => 'nocomments'));
-                $commentcount = 0;
-                if (!empty($course->comments)) {
-                    //display only if there is some comment if there is some comment
-                    $commentcount = count($course->comments);
-                    $coursecomments = html_writer::tag('div',
-                                    get_string('comments', 'block_community', $commentcount),
-                                    array('class' => 'commenttitle'));
-
-                    foreach ($course->comments as $comment) {
-                        $commentator = html_writer::tag('div',
-                                        $comment['commentator'],
-                                        array('class' => 'hubcommentator'));
-                        $commentdate = html_writer::tag('div',
-                                        ' - ' . userdate($comment['date'], '%e/%m/%y'),
-                                        array('class' => 'hubcommentdate clearfix'));
-
-                        $commenttext = html_writer::tag('div',
-                                        $comment['comment'],
-                                        array('class' => 'hubcommenttext'));
-
-                        $coursecomments .= html_writer::tag('div',
-                                        $commentator . $commentdate . $commenttext,
-                                        array('class' => 'hubcomment'));
-                    }
-                    $coursecommenticon = html_writer::tag('div',
-                                    get_string('comments', 'block_community', $commentcount),
-                                    array('class' => 'hubcoursecomments',
-                                        'id' => 'comments-' . $course->id));
-                    $coursecomments = $coursecommenticon . html_writer::tag('div',
-                                    $coursecomments,
-                                    array('class' => 'yui3-overlay-loading',
-                                        'id' => 'commentoverlay-' . $course->id));
-                }
-
-                //link rate and comment
-                $rateandcomment = html_writer::tag('div',
-                                html_writer::link($course->commenturl, get_string('rateandcomment', 'block_community'),
-                                            ['onclick' => 'this.target="_blank"']),
-                                array('class' => 'hubrateandcomment'));
-
-                //the main DIV tags
-                $buttonsdiv = html_writer::tag('div',
-                                $addbuttonhtml . $downloadbuttonhtml . $visitlinkhtml,
-                                array('class' => 'courseoperations'));
-                $screenshotbuttonsdiv = html_writer::tag('div',
-                                $coursescreenshot . $buttonsdiv,
-                                array('class' => 'courselinks'));
-
-                $coursedescdiv = html_writer::tag('div',
-                                $deschtml . $additionaldeschtml
-                                . $rating . $coursecomments . $rateandcomment,
-                                array('class' => 'coursedescription'));
-                $coursehtml =
-                        $coursenamehtml . html_writer::tag('div',
-                                $coursedescdiv . $screenshotbuttonsdiv,
-                                array('class' => 'hubcourseinfo clearfix'));
-
-                $renderedhtml .=html_writer::tag('div', $coursehtml,
-                                array('class' => 'fullhubcourse clearfix'));
-            }
-
-            $renderedhtml = html_writer::tag('div', $renderedhtml,
-                            array('class' => 'hubcourseresult'));
-        }
-
-        return $renderedhtml;
-    }
-
-}
diff --git a/blocks/community/styles.css b/blocks/community/styles.css
deleted file mode 100644 (file)
index f9ecac8..0000000
--- a/blocks/