Merge branch 'MDL-65849-master' of git://github.com/rezaies/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Tue, 7 Jan 2020 05:17:08 +0000 (13:17 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Tue, 7 Jan 2020 05:17:08 +0000 (13:17 +0800)
689 files changed:
.travis.yml
admin/antiviruses.php
admin/category.php
admin/cli/cron.php
admin/environment.xml
admin/message.php
admin/mnet/peer_forms.php
admin/qbehaviours.php
admin/qtypes.php
admin/renderer.php
admin/roles/classes/capability_table_base.php
admin/roles/classes/check_capability_table.php
admin/roles/classes/define_role_table_advanced.php
admin/roles/classes/override_permissions_table_advanced.php
admin/templates/setting.mustache
admin/templates/settings_search_results.mustache
admin/tests/behat/filter_users.feature
admin/tool/capability/classes/settings_form.php
admin/tool/capability/index.php
admin/tool/capability/lang/en/tool_capability.php
admin/tool/capability/renderer.php
admin/tool/capability/tests/behat/show_capabilies.feature [new file with mode: 0644]
admin/tool/capability/tests/behat/show_differences.feature [new file with mode: 0644]
admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js
admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js
admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js
admin/tool/capability/yui/src/search/js/search.js
admin/tool/cohortroles/classes/api.php
admin/tool/customlang/db/upgrade.php
admin/tool/customlang/locallib.php
admin/tool/dataprivacy/classes/api.php
admin/tool/dataprivacy/classes/task/process_data_request_task.php
admin/tool/dataprivacy/templates/component_status.mustache
admin/tool/filetypes/renderer.php
admin/tool/filetypes/styles.css
admin/tool/log/db/upgrade.php
admin/tool/log/store/database/db/upgrade.php
admin/tool/log/store/standard/db/upgrade.php
admin/tool/lp/db/renamedclasses.php [deleted file]
admin/tool/monitor/db/upgrade.php
admin/tool/task/cli/adhoc_task.php
admin/tool/task/lang/en/tool_task.php
admin/tool/task/schedule_task.php
admin/tool/uploadcourse/lang/en/tool_uploadcourse.php
admin/tool/usertours/amd/build/tour.min.js
admin/tool/usertours/amd/build/tour.min.js.map
admin/tool/usertours/amd/src/tour.js
admin/tool/usertours/classes/local/filter/accessdate.php [new file with mode: 0644]
admin/tool/usertours/db/upgrade.php
admin/tool/usertours/lang/en/tool_usertours.php
admin/tool/usertours/tests/accessdate_filter_test.php [new file with mode: 0644]
admin/tool/usertours/version.php
admin/tool/xmldb/actions/main_view/main_view.class.php
admin/upgrade.txt
admin/user.php
auth/cas/db/upgrade.php
auth/db/db/upgrade.php
auth/email/db/upgrade.php
auth/ldap/db/upgrade.php
auth/manual/db/upgrade.php
auth/mnet/db/upgrade.php
auth/none/db/upgrade.php
auth/oauth2/db/upgrade.php
auth/shibboleth/db/upgrade.php
auth/upgrade.txt
backup/import.php
backup/tests/automated_backup_test.php [new file with mode: 0644]
backup/util/helper/backup_cron_helper.class.php
backup/util/ui/base_ui.class.php
backup/util/ui/import_extensions.php
backup/util/ui/renderer.php
badges/assertion.php
badges/classes/output/issued_badge.php
badges/renderer.php
blocks/badges/db/upgrade.php
blocks/calendar_month/db/upgrade.php
blocks/calendar_upcoming/db/upgrade.php
blocks/completionstatus/db/upgrade.php
blocks/course_summary/db/upgrade.php
blocks/html/db/upgrade.php
blocks/navigation/db/upgrade.php
blocks/quiz_results/db/upgrade.php
blocks/recent_activity/db/upgrade.php
blocks/rss_client/db/upgrade.php
blocks/section_links/db/upgrade.php
blocks/selfcompletion/db/upgrade.php
blocks/settings/db/upgrade.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
cache/stores/mongodb/LICENSE [moved from cache/stores/mongodb/MongoDB/LICENSE with 100% similarity]
cache/stores/mongodb/MongoDB/BulkWriteResult.php
cache/stores/mongodb/MongoDB/ChangeStream.php
cache/stores/mongodb/MongoDB/Client.php
cache/stores/mongodb/MongoDB/Collection.php
cache/stores/mongodb/MongoDB/Database.php
cache/stores/mongodb/MongoDB/DeleteResult.php
cache/stores/mongodb/MongoDB/Exception/BadMethodCallException.php
cache/stores/mongodb/MongoDB/Exception/Exception.php
cache/stores/mongodb/MongoDB/Exception/InvalidArgumentException.php
cache/stores/mongodb/MongoDB/Exception/ResumeTokenException.php
cache/stores/mongodb/MongoDB/Exception/RuntimeException.php
cache/stores/mongodb/MongoDB/Exception/UnexpectedValueException.php
cache/stores/mongodb/MongoDB/Exception/UnsupportedException.php
cache/stores/mongodb/MongoDB/GridFS/Bucket.php
cache/stores/mongodb/MongoDB/GridFS/CollectionWrapper.php
cache/stores/mongodb/MongoDB/GridFS/Exception/CorruptFileException.php
cache/stores/mongodb/MongoDB/GridFS/Exception/FileNotFoundException.php
cache/stores/mongodb/MongoDB/GridFS/ReadableStream.php
cache/stores/mongodb/MongoDB/GridFS/StreamWrapper.php
cache/stores/mongodb/MongoDB/GridFS/WritableStream.php
cache/stores/mongodb/MongoDB/InsertManyResult.php
cache/stores/mongodb/MongoDB/InsertOneResult.php
cache/stores/mongodb/MongoDB/MapReduceResult.php
cache/stores/mongodb/MongoDB/Model/BSONArray.php
cache/stores/mongodb/MongoDB/Model/BSONDocument.php
cache/stores/mongodb/MongoDB/Model/BSONIterator.php
cache/stores/mongodb/MongoDB/Model/CachingIterator.php
cache/stores/mongodb/MongoDB/Model/ChangeStreamIterator.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/CollectionInfo.php
cache/stores/mongodb/MongoDB/Model/DatabaseInfo.php
cache/stores/mongodb/MongoDB/Model/DatabaseInfoLegacyIterator.php
cache/stores/mongodb/MongoDB/Model/IndexInfo.php
cache/stores/mongodb/MongoDB/Model/IndexInput.php
cache/stores/mongodb/MongoDB/Model/TypeMapArrayIterator.php [deleted file]
cache/stores/mongodb/MongoDB/Operation/Aggregate.php
cache/stores/mongodb/MongoDB/Operation/BulkWrite.php
cache/stores/mongodb/MongoDB/Operation/Count.php
cache/stores/mongodb/MongoDB/Operation/CountDocuments.php
cache/stores/mongodb/MongoDB/Operation/CreateCollection.php
cache/stores/mongodb/MongoDB/Operation/CreateIndexes.php
cache/stores/mongodb/MongoDB/Operation/DatabaseCommand.php
cache/stores/mongodb/MongoDB/Operation/Delete.php
cache/stores/mongodb/MongoDB/Operation/DeleteMany.php
cache/stores/mongodb/MongoDB/Operation/DeleteOne.php
cache/stores/mongodb/MongoDB/Operation/Distinct.php
cache/stores/mongodb/MongoDB/Operation/DropCollection.php
cache/stores/mongodb/MongoDB/Operation/DropDatabase.php
cache/stores/mongodb/MongoDB/Operation/DropIndexes.php
cache/stores/mongodb/MongoDB/Operation/EstimatedDocumentCount.php
cache/stores/mongodb/MongoDB/Operation/Explain.php
cache/stores/mongodb/MongoDB/Operation/Explainable.php
cache/stores/mongodb/MongoDB/Operation/Find.php
cache/stores/mongodb/MongoDB/Operation/FindAndModify.php
cache/stores/mongodb/MongoDB/Operation/FindOne.php
cache/stores/mongodb/MongoDB/Operation/FindOneAndDelete.php
cache/stores/mongodb/MongoDB/Operation/FindOneAndReplace.php
cache/stores/mongodb/MongoDB/Operation/FindOneAndUpdate.php
cache/stores/mongodb/MongoDB/Operation/InsertMany.php
cache/stores/mongodb/MongoDB/Operation/InsertOne.php
cache/stores/mongodb/MongoDB/Operation/ListCollections.php
cache/stores/mongodb/MongoDB/Operation/ListDatabases.php
cache/stores/mongodb/MongoDB/Operation/ListIndexes.php
cache/stores/mongodb/MongoDB/Operation/MapReduce.php
cache/stores/mongodb/MongoDB/Operation/ModifyCollection.php
cache/stores/mongodb/MongoDB/Operation/ReplaceOne.php
cache/stores/mongodb/MongoDB/Operation/Update.php
cache/stores/mongodb/MongoDB/Operation/UpdateMany.php
cache/stores/mongodb/MongoDB/Operation/UpdateOne.php
cache/stores/mongodb/MongoDB/Operation/Watch.php
cache/stores/mongodb/MongoDB/Operation/WithTransaction.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/UpdateResult.php
cache/stores/mongodb/MongoDB/functions.php
cache/stores/mongodb/MongoDB/readme_moodle.txt [deleted file]
cache/stores/mongodb/readme_moodle.txt [new file with mode: 0644]
cache/stores/mongodb/thirdpartylibs.xml
calendar/amd/build/repository.min.js
calendar/amd/build/repository.min.js.map
calendar/amd/build/view_manager.min.js
calendar/amd/build/view_manager.min.js.map
calendar/amd/src/repository.js
calendar/amd/src/view_manager.js
calendar/externallib.php
calendar/lib.php
calendar/templates/event_details.mustache
calendar/templates/month_mini.mustache
calendar/tests/externallib_test.php
calendar/tests/lib_test.php
competency/tests/privacy_test.php
composer.json
composer.lock
config-dist.php
course/format/topics/db/upgrade.php
course/format/topics/db/upgradelib.php [deleted file]
course/format/topics/tests/format_topics_upgrade_test.php [deleted file]
course/format/upgrade.txt
course/format/weeks/db/upgrade.php
course/format/weeks/db/upgradelib.php [deleted file]
course/format/weeks/tests/format_weeks_upgrade_test.php [deleted file]
course/modlib.php
course/templates/activityinstance.mustache
course/templates/bulkactivitycompletion.mustache
course/templates/defaultactivitycompletion.mustache
course/tests/externallib_test.php
enrol/database/db/upgrade.php
enrol/flatfile/db/upgrade.php
enrol/guest/db/upgrade.php
enrol/imsenterprise/db/upgrade.php
enrol/lti/db/upgrade.php
enrol/manual/amd/build/form-potential-user-selector.min.js
enrol/manual/amd/build/form-potential-user-selector.min.js.map
enrol/manual/amd/src/form-potential-user-selector.js
enrol/manual/db/upgrade.php
enrol/meta/lib.php
enrol/mnet/db/upgrade.php
enrol/paypal/db/upgrade.php
enrol/renderer.php
enrol/self/db/upgrade.php
files/converter/unoconv/classes/converter.php
filter/displayh5p/tests/behat/h5p_filter.feature
filter/mathjaxloader/db/upgrade.php
filter/mathjaxloader/db/upgradelib.php [deleted file]
filter/mathjaxloader/tests/upgradelib_test.php [deleted file]
filter/mediaplugin/db/upgrade.php
filter/mediaplugin/styles.css
filter/tex/db/upgrade.php
filter/upgrade.txt
grade/classes/grades/grader/gradingpanel/point/external/fetch.php
grade/classes/grades/grader/gradingpanel/point/external/store.php
grade/classes/grades/grader/gradingpanel/scale/external/fetch.php
grade/classes/grades/grader/gradingpanel/scale/external/store.php
grade/grading/form/guide/classes/grades/grader/gradingpanel/external/fetch.php
grade/grading/form/guide/db/upgrade.php
grade/grading/form/guide/tests/grades_grader_gradingpanel_guide_external_fetch_test.php
grade/grading/form/guide/tests/grades_grader_gradingpanel_guide_external_store_test.php
grade/grading/form/rubric/classes/grades/grader/gradingpanel/external/fetch.php
grade/grading/form/rubric/db/upgrade.php
grade/grading/form/rubric/lang/en/gradingform_rubric.php
grade/grading/form/rubric/renderer.php
grade/grading/form/rubric/tests/behat/grade_calculation.feature
grade/grading/form/rubric/tests/grades_grader_gradingpanel_rubric_external_fetch_test.php
grade/grading/form/rubric/tests/grades_grader_gradingpanel_rubric_external_store_test.php
grade/grading/renderer.php
grade/grading/tests/behat/behat_grading.php
grade/report/overview/db/upgrade.php
grade/report/user/db/upgrade.php
grade/tests/grades_grader_gradingpanel_point_external_fetch_test.php
grade/tests/grades_grader_gradingpanel_point_external_store_test.php
grade/tests/grades_grader_gradingpanel_scale_external_fetch_test.php
grade/tests/grades_grader_gradingpanel_scale_external_store_test.php
group/templates/group_details.mustache
install/lang/el/error.php
install/lang/eu/install.php
install/lang/fr_incl/langconfig.php [moved from mod/lti/service/memberships/db/renamedclasses.php with 59% similarity]
install/lang/se/install.php [new file with mode: 0644]
install/lang/se/moodle.php [moved from mod/lti/service/toolproxy/db/renamedclasses.php with 62% similarity]
install/lang/tpi/langconfig.php [moved from mod/lti/service/profile/db/renamedclasses.php with 62% similarity]
iplookup/tests/fixtures/GeoIP2-City-Test.mmdb [new file with mode: 0644]
iplookup/tests/fixtures/README.txt [new file with mode: 0644]
iplookup/tests/geoip_test.php
lang/en/admin.php
lang/en/antivirus.php
lang/en/course.php
lang/en/deprecated.txt
lang/en/error.php
lang/en/grades.php
lang/en/h5p.php
lang/en/moodle.php
lang/en/user.php
lib/accesslib.php
lib/adminlib.php
lib/adodb/LICENSE.md [moved from lib/adodb/license.txt with 100% similarity]
lib/adodb/README.md [new file with mode: 0644]
lib/adodb/adodb-active-record.inc.php
lib/adodb/adodb-active-recordx.inc.php
lib/adodb/adodb-csvlib.inc.php
lib/adodb/adodb-datadict.inc.php
lib/adodb/adodb-error.inc.php
lib/adodb/adodb-errorhandler.inc.php
lib/adodb/adodb-errorpear.inc.php
lib/adodb/adodb-exceptions.inc.php
lib/adodb/adodb-iterator.inc.php
lib/adodb/adodb-lib.inc.php
lib/adodb/adodb-memcache.lib.inc.php
lib/adodb/adodb-pager.inc.php
lib/adodb/adodb-pear.inc.php
lib/adodb/adodb-perf.inc.php
lib/adodb/adodb-php4.inc.php
lib/adodb/adodb-time.inc.php
lib/adodb/adodb.inc.php
lib/adodb/datadict/datadict-access.inc.php
lib/adodb/datadict/datadict-db2.inc.php
lib/adodb/datadict/datadict-firebird.inc.php
lib/adodb/datadict/datadict-generic.inc.php
lib/adodb/datadict/datadict-ibase.inc.php
lib/adodb/datadict/datadict-informix.inc.php
lib/adodb/datadict/datadict-mssql.inc.php
lib/adodb/datadict/datadict-mssqlnative.inc.php
lib/adodb/datadict/datadict-mysql.inc.php
lib/adodb/datadict/datadict-oci8.inc.php
lib/adodb/datadict/datadict-postgres.inc.php
lib/adodb/datadict/datadict-sapdb.inc.php
lib/adodb/datadict/datadict-sqlite.inc.php
lib/adodb/datadict/datadict-sybase.inc.php
lib/adodb/drivers/adodb-access.inc.php
lib/adodb/drivers/adodb-ado.inc.php
lib/adodb/drivers/adodb-ado5.inc.php
lib/adodb/drivers/adodb-ado_access.inc.php
lib/adodb/drivers/adodb-ado_mssql.inc.php
lib/adodb/drivers/adodb-borland_ibase.inc.php
lib/adodb/drivers/adodb-csv.inc.php
lib/adodb/drivers/adodb-db2.inc.php
lib/adodb/drivers/adodb-db2oci.inc.php
lib/adodb/drivers/adodb-db2ora.inc.php
lib/adodb/drivers/adodb-fbsql.inc.php
lib/adodb/drivers/adodb-firebird.inc.php
lib/adodb/drivers/adodb-ibase.inc.php
lib/adodb/drivers/adodb-informix.inc.php
lib/adodb/drivers/adodb-informix72.inc.php
lib/adodb/drivers/adodb-ldap.inc.php
lib/adodb/drivers/adodb-mssql.inc.php
lib/adodb/drivers/adodb-mssqlnative.inc.php
lib/adodb/drivers/adodb-mssqlpo.inc.php
lib/adodb/drivers/adodb-mysql.inc.php
lib/adodb/drivers/adodb-mysqli.inc.php
lib/adodb/drivers/adodb-mysqlpo.inc.php
lib/adodb/drivers/adodb-mysqlt.inc.php
lib/adodb/drivers/adodb-netezza.inc.php
lib/adodb/drivers/adodb-oci8.inc.php
lib/adodb/drivers/adodb-oci805.inc.php
lib/adodb/drivers/adodb-oci8po.inc.php
lib/adodb/drivers/adodb-oci8quercus.inc.php
lib/adodb/drivers/adodb-odbc.inc.php
lib/adodb/drivers/adodb-odbc_db2.inc.php
lib/adodb/drivers/adodb-odbc_mssql.inc.php
lib/adodb/drivers/adodb-odbc_oracle.inc.php
lib/adodb/drivers/adodb-odbtp.inc.php
lib/adodb/drivers/adodb-odbtp_unicode.inc.php
lib/adodb/drivers/adodb-oracle.inc.php
lib/adodb/drivers/adodb-pdo.inc.php
lib/adodb/drivers/adodb-pdo_mssql.inc.php
lib/adodb/drivers/adodb-pdo_mysql.inc.php
lib/adodb/drivers/adodb-pdo_oci.inc.php
lib/adodb/drivers/adodb-pdo_pgsql.inc.php
lib/adodb/drivers/adodb-pdo_sqlite.inc.php
lib/adodb/drivers/adodb-postgres.inc.php
lib/adodb/drivers/adodb-postgres64.inc.php
lib/adodb/drivers/adodb-postgres7.inc.php
lib/adodb/drivers/adodb-postgres8.inc.php
lib/adodb/drivers/adodb-postgres9.inc.php
lib/adodb/drivers/adodb-proxy.inc.php
lib/adodb/drivers/adodb-sapdb.inc.php
lib/adodb/drivers/adodb-sqlanywhere.inc.php
lib/adodb/drivers/adodb-sqlite.inc.php
lib/adodb/drivers/adodb-sqlite3.inc.php
lib/adodb/drivers/adodb-sqlitepo.inc.php
lib/adodb/drivers/adodb-sybase.inc.php
lib/adodb/drivers/adodb-sybase_ase.inc.php
lib/adodb/drivers/adodb-text.inc.php [deleted file]
lib/adodb/drivers/adodb-vfp.inc.php
lib/adodb/perf/perf-db2.inc.php
lib/adodb/perf/perf-informix.inc.php
lib/adodb/perf/perf-mssql.inc.php
lib/adodb/perf/perf-mssqlnative.inc.php
lib/adodb/perf/perf-mysql.inc.php
lib/adodb/perf/perf-oci8.inc.php
lib/adodb/perf/perf-postgres.inc.php
lib/adodb/pivottable.inc.php
lib/adodb/readme.txt [deleted file]
lib/adodb/readme_moodle.txt
lib/adodb/rsfilter.inc.php
lib/adodb/toexport.inc.php
lib/adodb/tohtml.inc.php
lib/amd/build/adapter.min.js
lib/amd/build/adapter.min.js.map
lib/amd/build/notification.min.js
lib/amd/build/notification.min.js.map
lib/amd/build/str.min.js
lib/amd/build/str.min.js.map
lib/amd/build/tag.min.js
lib/amd/build/tag.min.js.map
lib/amd/src/adapter.js
lib/amd/src/notification.js
lib/amd/src/str.js
lib/amd/src/tag.js
lib/antivirus/clamav/db/upgrade.php
lib/authlib.php
lib/babel-polyfill/polyfill.js
lib/babel-polyfill/polyfill.min.js
lib/behat/behat_base.php
lib/behat/classes/behat_core_generator.php
lib/behat/form_field/behat_form_autocomplete.php
lib/classes/filetypes.php
lib/classes/lock/postgres_lock_factory.php
lib/classes/output/icon_system_fontawesome.php
lib/classes/task/adhoc_task.php
lib/classes/task/automated_backup_task.php
lib/classes/task/course_backup_task.php [new file with mode: 0644]
lib/classes/task/manager.php
lib/classes/useragent.php
lib/cronlib.php
lib/db/renamedclasses.php
lib/db/upgrade.php
lib/db/upgradelib.php
lib/deprecatedlib.php
lib/editor/atto/db/upgrade.php
lib/editor/atto/plugins/equation/db/upgrade.php
lib/editor/atto/plugins/h5p/tests/behat/h5p.feature
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-button/moodle-atto_recordrtc-button-debug.js
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-button/moodle-atto_recordrtc-button-min.js
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-button/moodle-atto_recordrtc-button.js
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-debug.js
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-min.js
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording.js
lib/editor/atto/plugins/recordrtc/yui/src/button/js/button.js
lib/editor/atto/plugins/recordrtc/yui/src/recording/js/commonmodule.js
lib/editor/tests/fixtures/editor_form.php
lib/editor/tinymce/db/upgrade.php
lib/editor/tinymce/plugins/spellchecker/db/upgrade.php
lib/filelib.php
lib/filestorage/file_system.php
lib/form/duration.php
lib/form/filetypes.php
lib/form/float.php
lib/maxmind/GeoIp2/Compat/JsonSerializable.php [deleted file]
lib/maxmind/GeoIp2/Database/Reader.php
lib/maxmind/GeoIp2/Model/AnonymousIp.php
lib/maxmind/GeoIp2/Model/Asn.php
lib/maxmind/GeoIp2/Model/City.php
lib/maxmind/GeoIp2/Model/ConnectionType.php
lib/maxmind/GeoIp2/Model/Country.php
lib/maxmind/GeoIp2/Model/Domain.php
lib/maxmind/GeoIp2/Model/Enterprise.php
lib/maxmind/GeoIp2/Model/Insights.php
lib/maxmind/GeoIp2/Model/Isp.php
lib/maxmind/GeoIp2/Record/AbstractRecord.php
lib/maxmind/GeoIp2/Record/Country.php
lib/maxmind/GeoIp2/Record/Location.php
lib/maxmind/GeoIp2/Record/RepresentedCountry.php
lib/maxmind/GeoIp2/Record/Subdivision.php
lib/maxmind/GeoIp2/Record/Traits.php
lib/maxmind/GeoIp2/Util.php [new file with mode: 0644]
lib/maxmind/GeoIp2/WebService/Client.php
lib/maxmind/MaxMind/CHANGELOG.md [new file with mode: 0644]
lib/maxmind/MaxMind/Db/Reader.php
lib/maxmind/MaxMind/Db/Reader/Decoder.php
lib/maxmind/MaxMind/Db/Reader/InvalidDatabaseException.php
lib/maxmind/MaxMind/Db/Reader/Metadata.php
lib/maxmind/MaxMind/LICENSE [new file with mode: 0644]
lib/maxmind/MaxMind/README.md [new file with mode: 0644]
lib/maxmind/MaxMind/autoload.php [new file with mode: 0644]
lib/maxmind/MaxMind/composer.json [new file with mode: 0644]
lib/maxmind/readme_moodle.txt
lib/moodlelib.php
lib/mustache/readme_moodle.txt
lib/mustache/src/Mustache/Parser.php
lib/outputlib.php
lib/phpmailer/README.md
lib/phpmailer/README_MOODLE.txt
lib/phpmailer/VERSION
lib/phpmailer/language/phpmailer.lang-af.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-fa.php
lib/phpmailer/language/phpmailer.lang-pt_br.php
lib/phpmailer/language/phpmailer.lang-ru.php
lib/phpmailer/language/phpmailer.lang-uk.php
lib/phpmailer/src/Exception.php
lib/phpmailer/src/PHPMailer.php
lib/phpmailer/src/SMTP.php
lib/plagiarismlib.php
lib/questionlib.php
lib/requirejs.php
lib/scssphp/Compiler.php
lib/scssphp/moodle_readme.txt
lib/setup.php
lib/setuplib.php
lib/simplepie/README.markdown
lib/simplepie/library/SimplePie.php
lib/simplepie/library/SimplePie/Locator.php
lib/simplepie/library/SimplePie/Parse/Date.php
lib/simplepie/readme_moodle.txt
lib/tablelib.php
lib/tests/adhoc_task_test.php
lib/tests/behat/behat_permissions.php
lib/tests/fixtures/repeated_events.ics [new file with mode: 0644]
lib/tests/moodlelib_test.php
lib/tests/session_manager_test.php
lib/tests/task_manager_test.php [new file with mode: 0644]
lib/tests/upgradelib_test.php
lib/thirdpartylibs.xml
lib/upgrade.txt
lib/upgradelib.php
lib/weblib.php
message/classes/api.php
message/output/email/db/upgrade.php
message/output/jabber/db/upgrade.php
message/output/popup/db/upgrade.php
message/templates/message_drawer_view_contacts_body.mustache
message/templates/message_drawer_view_overview_header.mustache
message/templates/notification_preferences_component.mustache
message/templates/notification_preferences_component_notification.mustache
message/tests/api_test.php
message/tests/behat/message_manage_notification_preferences.feature [new file with mode: 0644]
message/tests/messagelib_test.php
message/upgrade.txt
mod/assign/db/upgrade.php
mod/assign/externallib.php
mod/assign/feedback/comments/db/upgrade.php
mod/assign/feedback/editpdf/db/upgrade.php
mod/assign/feedback/file/db/upgrade.php
mod/assign/mod_form.php
mod/assign/submission/comments/db/upgrade.php
mod/assign/submission/file/db/upgrade.php
mod/assign/submission/onlinetext/db/upgrade.php
mod/assign/tests/behat/assign_group_override.feature
mod/assign/tests/behat/assign_user_override.feature
mod/assign/tests/behat/steps_blind_marking.feature
mod/assign/tests/externallib_test.php
mod/assign/tests/locallib_test.php
mod/assign/upgrade.txt
mod/assign/upgradelib.php
mod/assignment/db/upgrade.php
mod/book/db/upgrade.php
mod/book/edit.php
mod/book/locallib.php
mod/book/styles.css
mod/book/tests/behat/show_hide_chapters.feature
mod/book/tool/importhtml/index.php
mod/book/view.php
mod/chat/db/upgrade.php
mod/choice/db/upgrade.php
mod/choice/report.php
mod/data/classes/external.php
mod/data/db/upgrade.php
mod/data/tests/externallib_test.php
mod/feedback/classes/responses_table.php
mod/feedback/db/upgrade.php
mod/folder/db/upgrade.php
mod/forum/amd/build/discussion_list.min.js
mod/forum/amd/build/discussion_list.min.js.map
mod/forum/amd/build/local/grades/grader.min.js
mod/forum/amd/build/local/grades/grader.min.js.map
mod/forum/amd/src/discussion_list.js
mod/forum/amd/src/local/grades/grader.js
mod/forum/db/upgrade.php
mod/forum/externallib.php
mod/forum/lang/en/forum.php
mod/forum/mod_form.php
mod/forum/report/summary/lang/en/forumreport_summary.php
mod/forum/templates/discussion_list.mustache
mod/forum/templates/forum_discussion_nested_v2_first_post.mustache
mod/forum/templates/forum_discussion_post.mustache
mod/forum/templates/forum_discussion_threaded_posts.mustache
mod/forum/templates/local/grades/local/grader/grading.mustache
mod/forum/templates/local/grades/local/grader/status.mustache
mod/forum/templates/local/grades/view_grade.mustache
mod/forum/tests/mail_test.php
mod/glossary/db/upgrade.php
mod/glossary/import_form.php
mod/imscp/db/upgrade.php
mod/label/db/upgrade.php
mod/lesson/classes/external.php
mod/lesson/db/upgrade.php
mod/lesson/tests/behat/date_availability.feature
mod/lesson/tests/behat/lesson_group_override.feature
mod/lesson/tests/behat/lesson_user_override.feature
mod/lesson/tests/external_test.php
mod/lti/db/upgrade.php
mod/lti/service/toolsettings/db/renamedclasses.php [deleted file]
mod/lti/upgradelib.php
mod/page/db/upgrade.php
mod/quiz/db/upgrade.php
mod/quiz/lang/en/quiz.php
mod/quiz/locallib.php
mod/quiz/report/grading/lang/en/quiz_grading.php
mod/quiz/report/overview/db/upgrade.php
mod/quiz/report/statistics/db/upgrade.php
mod/quiz/report/statistics/lang/en/quiz_statistics.php
mod/quiz/report/upgrade.txt
mod/quiz/settings.php
mod/quiz/tests/attempt_test.php
mod/resource/db/upgrade.php
mod/scorm/db/upgrade.php
mod/scorm/report/graphs/db/renamedclasses.php [deleted file]
mod/scorm/report/interactions/db/renamedclasses.php [deleted file]
mod/scorm/report/objectives/db/renamedclasses.php [deleted file]
mod/survey/db/upgrade.php
mod/survey/report.php
mod/survey/save.php
mod/survey/view.php
mod/url/db/upgrade.php
mod/url/lib.php
mod/url/locallib.php
mod/wiki/comments_form.php
mod/wiki/db/upgrade.php
mod/wiki/parser/parser.php
mod/workshop/db/upgrade.php
mod/workshop/form/accumulative/db/upgrade.php
mod/workshop/form/comments/db/upgrade.php
mod/workshop/form/numerrors/db/upgrade.php
mod/workshop/form/rubric/db/upgrade.php
mod/workshop/lang/en/workshop.php
mod/workshop/mod_form.php
mod/workshop/renderer.php
mod/workshop/tests/behat/file_type_restriction.feature
mod/workshop/tests/behat/submission_types.feature
pix/i/grading.png [new file with mode: 0644]
pix/i/grading.svg [new file with mode: 0644]
pix/i/gradingnotifications.png [new file with mode: 0644]
pix/i/gradingnotifications.svg [new file with mode: 0644]
pix/i/incorrect.png [new file with mode: 0644]
pix/i/incorrect.svg [new file with mode: 0644]
plagiarism/lib.php
plagiarism/upgrade.txt
portfolio/boxnet/db/upgrade.php
portfolio/googledocs/db/upgrade.php
portfolio/picasa/db/upgrade.php
privacy/classes/local/request/moodle_content_writer.php
question/behaviour/adaptive/renderer.php
question/behaviour/adaptive/tests/mark_display_test.php
question/behaviour/manualgraded/db/upgrade.php
question/classes/bank/view.php
question/engine/questionusage.php
question/engine/renderer.php
question/tests/behat/preview_question.feature
question/type/calculated/datasetitems_form.php
question/type/calculated/db/upgrade.php
question/type/calculatedsimple/edit_calculatedsimple_form.php
question/type/ddmarker/db/upgrade.php
question/type/essay/db/upgrade.php
question/type/match/db/upgrade.php
question/type/multianswer/db/upgrade.php
question/type/multichoice/db/upgrade.php
question/type/numerical/db/upgrade.php
question/type/numerical/edit_numerical_form.php
question/type/numerical/questiontype.php
question/type/numerical/tests/behat/edit.feature
question/type/random/db/upgrade.php
question/type/randomsamatch/db/upgrade.php
question/type/shortanswer/db/upgrade.php
report/backups/index.php
report/configlog/classes/form/search.php [new file with mode: 0644]
report/configlog/classes/output/renderer.php [new file with mode: 0644]
report/configlog/classes/output/report_table.php [new file with mode: 0644]
report/configlog/index.php
report/configlog/lang/en/report_configlog.php
report/configlog/tests/behat/view_report.feature [new file with mode: 0644]
report/configlog/version.php
report/insights/templates/insight.mustache
report/insights/templates/insight_details.mustache
report/log/classes/table_log.php
report/loglive/classes/table_log.php
report/performance/locallib.php
report/progress/index.php
report/security/index.php
repository/boxnet/db/upgrade.php
repository/dropbox/classes/dropbox.php
repository/dropbox/db/upgrade.php
repository/flickr/db/upgrade.php
repository/googledocs/db/upgrade.php
repository/onedrive/db/upgrade.php
repository/picasa/db/upgrade.php
repository/upload/tests/behat/behat_repository_upload.php
theme/boost/scss/moodle.scss
theme/boost/scss/moodle/admin.scss
theme/boost/scss/moodle/backup-restore.scss
theme/boost/scss/moodle/blocks.scss
theme/boost/scss/moodle/bs2-compat.scss
theme/boost/scss/moodle/bs4alphacompat.scss
theme/boost/scss/moodle/buttons.scss
theme/boost/scss/moodle/calendar.scss
theme/boost/scss/moodle/chat.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/course.scss
theme/boost/scss/moodle/dashboard.scss
theme/boost/scss/moodle/expendable.scss
theme/boost/scss/moodle/filemanager.scss
theme/boost/scss/moodle/forms.scss
theme/boost/scss/moodle/grade.scss
theme/boost/scss/moodle/modules.scss
theme/boost/scss/moodle/question.scss
theme/boost/scss/moodle/tables.scss
theme/boost/scss/moodle/user.scss
theme/boost/settings.php
theme/boost/style/moodle.css
theme/boost/templates/columns1.mustache
theme/boost/templates/columns2.mustache
theme/boost/templates/embedded.mustache
theme/classic/settings.php
theme/classic/style/moodle.css
theme/classic/templates/columns.mustache
theme/upgrade.txt
user/classes/output/myprofile/renderer.php
user/filters/lib.php
version.php
webservice/lib.php
webservice/rest/locallib.php
webservice/rest/server.php
webservice/soap/server.php
webservice/xmlrpc/server.php

index 90edc4f..321c133 100644 (file)
@@ -19,7 +19,7 @@ services:
 php:
     # We only run the highest and lowest supported versions to reduce the load on travis-ci.org.
     - 7.3
-    - 7.1.30 # Make this sticky because current default version (7.1.11) has a bug with redis-extension output (MDL-66062)
+    - 7.2
 
 addons:
   postgresql: "9.6"
index e6a0ce2..3502956 100644 (file)
@@ -98,8 +98,10 @@ switch ($action) {
     default:
         break;
 }
+$new = implode(',', $activeantiviruses);
+add_to_config_log('antiviruses', $CFG->antiviruses, $new, 'core');
+set_config('antiviruses', $new);
 
-set_config('antiviruses', implode(',', $activeantiviruses));
 core_plugin_manager::reset_caches();
 
-redirect ($returnurl);
\ No newline at end of file
+redirect ($returnurl);
index 39b74ab..9bfa2bb 100644 (file)
@@ -30,7 +30,7 @@ $category = required_param('category', PARAM_SAFEDIR);
 $return = optional_param('return','', PARAM_ALPHA);
 $adminediting = optional_param('adminedit', -1, PARAM_BOOL);
 
-require_admin();
+require_login(0, false);
 $PAGE->set_context(context_system::instance());
 $PAGE->set_url('/admin/category.php', array('category' => $category));
 $PAGE->set_pagetype('admin-setting-' . $category);
index 3ece44a..d7d1646 100644 (file)
@@ -35,8 +35,16 @@ require_once($CFG->libdir.'/clilib.php');      // cli only functions
 require_once($CFG->libdir.'/cronlib.php');
 
 // now get cli options
-list($options, $unrecognized) = cli_get_params(array('help'=>false),
-                                               array('h'=>'help'));
+list($options, $unrecognized) = cli_get_params(
+    array(
+        'help' => false,
+        'stop' => false,
+    ),
+    array(
+        'h' => 'help',
+        's' => 'stop',
+    )
+);
 
 if ($unrecognized) {
     $unrecognized = implode("\n  ", $unrecognized);
@@ -49,6 +57,7 @@ if ($options['help']) {
 
 Options:
 -h, --help            Print out this help
+-s, --stop            Notify all other running cron processes to stop after the current task
 
 Example:
 \$sudo -u www-data /usr/bin/php admin/cli/cron.php
@@ -58,4 +67,11 @@ Example:
     die;
 }
 
+if ($options['stop']) {
+    // By clearing the caches this signals to other running processes
+    // to exit after finishing the current task.
+    \core\task\manager::clear_static_caches();
+    die;
+}
+
 cron_run();
index 16b7402..6f6fb81 100644 (file)
       </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
+  <MOODLE version="3.9" requires="3.5">
+    <UNICODE level="required">
+      <FEEDBACK>
+        <ON_ERROR message="unicoderequired" />
+      </FEEDBACK>
+    </UNICODE>
+    <DATABASE level="required">
+      <VENDOR name="mariadb" version="10.2.29" />
+      <VENDOR name="mysql" version="5.6" />
+      <VENDOR name="postgres" version="9.5" />
+      <VENDOR name="mssql" version="11.0" />
+      <VENDOR name="oracle" version="11.2" />
+    </DATABASE>
+    <PHP version="7.2.0" level="required">
+    </PHP>
+    <PCREUNICODE level="optional">
+      <FEEDBACK>
+        <ON_CHECK message="pcreunicodewarning" />
+      </FEEDBACK>
+    </PCREUNICODE>
+    <PHP_EXTENSIONS>
+      <PHP_EXTENSION name="iconv" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="iconvrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="mbstring" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="mbstringrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="curl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="curlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="openssl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="opensslrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="tokenizer" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="tokenizerrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xmlrpc" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="xmlrpcrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="soap" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="soaprecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="ctype" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ctyperequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zip" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ziprequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zlib" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="gd" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="gdrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="simplexml" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="simplexmlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="spl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="splrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="pcre" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="dom" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xml" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xmlreader" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="intl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="intlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="json" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="hash" level="required"/>
+      <PHP_EXTENSION name="fileinfo" level="required"/>
+    </PHP_EXTENSIONS>
+    <PHP_SETTINGS>
+      <PHP_SETTING name="memory_limit" value="96M" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="settingmemorylimit" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="file_uploads" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="settingfileuploads" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="opcache.enable" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="opcacherecommended" />
+        </FEEDBACK>
+      </PHP_SETTING>
+    </PHP_SETTINGS>
+    <CUSTOM_CHECKS>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddbstorageengine" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="quizattemptsupgradedmessage" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="slashargumentswarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="unsupporteddbtablerowformat" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="unoconvwarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="libcurlwarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddbfileformat" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddbfilepertable" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddblargeprefix" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="ishttpswarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="incompleteunicodesupport" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="sixtyfourbitswarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+    </CUSTOM_CHECKS>
+  </MOODLE>
 </COMPATIBILITY_MATRIX>
index 52ef04e..d004e9e 100644 (file)
@@ -33,13 +33,17 @@ $allprocessors = get_message_processors();
 $processors = array_filter($allprocessors, function($processor) {
     return $processor->enabled;
 });
+$disabledprocessors = array_filter($allprocessors, function($processor) {
+    return !$processor->enabled;
+});
+
 // Fetch message providers.
 $providers = get_message_providers();
 // Fetch the manage message outputs interface.
 $preferences = get_message_output_default_preferences();
 
 if (($form = data_submitted()) && confirm_sesskey()) {
-    $preferences = array();
+    $newpreferences = array();
     // Prepare default message outputs settings.
     foreach ($providers as $provider) {
         $componentproviderbase = $provider->component.'_'.$provider->name;
@@ -47,9 +51,9 @@ if (($form = data_submitted()) && confirm_sesskey()) {
         $providerdisabled = false;
         if (!isset($form->$disableprovidersetting)) {
             $providerdisabled = true;
-            $preferences[$disableprovidersetting] = 1;
+            $newpreferences[$disableprovidersetting] = 1;
         } else {
-            $preferences[$disableprovidersetting] = 0;
+            $newpreferences[$disableprovidersetting] = 0;
         }
 
         foreach (array('permitted', 'loggedin', 'loggedoff') as $setting) {
@@ -75,20 +79,36 @@ if (($form = data_submitted()) && confirm_sesskey()) {
                         $form->{$componentproviderbase.'_loggedoff'}[$processor->name] = 1;
                     }
                     // Record the site preference.
-                    $preferences[$processor->name.'_provider_'.$componentprovidersetting] = $value;
+                    $newpreferences[$processor->name.'_provider_'.$componentprovidersetting] = $value;
                 }
-            } else if (property_exists($form, $componentprovidersetting)) {
-                // We must be processing loggedin or loggedoff checkboxes. Store
-                // defained comma-separated processors as setting value.
-                // Using array_filter eliminates elements set to 0 above.
-                $value = join(',', array_keys(array_filter($form->{$componentprovidersetting})));
+            } else {
+                $newsettings = array();
+                if (array_key_exists($componentprovidersetting, $form)) {
+                    // We must be processing loggedin or loggedoff checkboxes.
+                    // Store defained comma-separated processors as setting value.
+                    // Using array_filter eliminates elements set to 0 above.
+                    $newsettings = array_keys(array_filter($form->{$componentprovidersetting}));
+                }
+
+                // Let's join existing setting values for disabled processors.
+                $property = 'message_provider_'.$componentprovidersetting;
+                if (property_exists($preferences, $property)) {
+                    $existingsetting = $preferences->$property;
+                    foreach ($disabledprocessors as $disable) {
+                        if (strpos($existingsetting, $disable->name) > -1) {
+                            $newsettings[] = $disable->name;
+                        }
+                    }
+                }
+
+                $value = join(',', $newsettings);
                 if (empty($value)) {
                     $value = null;
                 }
             }
             if ($setting != 'permitted') {
                 // We have already recoded site preferences for 'permitted' type.
-                $preferences['message_provider_'.$componentprovidersetting] = $value;
+                $newpreferences['message_provider_'.$componentprovidersetting] = $value;
             }
         }
     }
@@ -102,7 +122,7 @@ if (($form = data_submitted()) && confirm_sesskey()) {
         \core_message\api::update_processor_status($processor, $enabled);
     }
 
-    foreach ($preferences as $name => $value) {
+    foreach ($newpreferences as $name => $value) {
         set_config($name, $value, 'message');
     }
     $transaction->allow_commit();
index 5769e6d..cd24ec1 100644 (file)
@@ -156,7 +156,7 @@ class mnet_review_host_form extends moodleform {
         if ($mnet_peer && !empty($mnet_peer->deleted)) {
             $radioarray = array();
             $radioarray[] = $mform->createElement('static', 'deletedinfo', '',
-                $OUTPUT->container(get_string('deletedhostinfo', 'mnet'), 'deletedhostinfo'));
+                $OUTPUT->container(get_string('deletedhostinfo', 'mnet'), 'alert alert-warning'));
             $radioarray[] = $mform->createElement('radio', 'deleted', '', get_string('yes'), 1);
             $radioarray[] = $mform->createElement('radio', 'deleted', '', get_string('no'), 0);
             $mform->addGroup($radioarray, 'radioar', get_string('deleted'), array(' ', ' '), false);
index ae01d31..9edd3f0 100644 (file)
@@ -170,7 +170,7 @@ foreach ($sortedbehaviours as $behaviour => $behaviourname) {
     if ($version) {
         $row[] = $version;
     } else {
-        $row[] = html_writer::tag('span', get_string('nodatabase', 'admin'), array('class' => 'disabled'));
+        $row[] = html_writer::tag('span', get_string('nodatabase', 'admin'), array('class' => 'text-muted'));
     }
 
     // Other question types required by this one.
index a6bc06a..4704698 100644 (file)
@@ -172,7 +172,7 @@ foreach ($sortedqtypes as $qtypename => $localname) {
     if ($version) {
         $row[] = $version;
     } else {
-        $row[] = html_writer::tag('span', get_string('nodatabase', 'admin'), array('class' => 'disabled'));
+        $row[] = html_writer::tag('span', get_string('nodatabase', 'admin'), array('class' => 'text-muted'));
     }
 
     // Other question types required by this one.
index 5abd08e..5ba612e 100644 (file)
@@ -253,7 +253,7 @@ class core_admin_renderer extends plugin_renderer_base {
             $out .= $this->output->container(get_string('cancelinstallinfodir', 'core_plugin', $pluginfo->rootdir));
             if ($repotype = $pluginman->plugin_external_source($pluginfo->component)) {
                 $out .= $this->output->container(get_string('uninstalldeleteconfirmexternal', 'core_plugin', $repotype),
-                    'uninstalldeleteconfirmexternal');
+                    'alert alert-warning mt-2');
             }
         }
 
@@ -432,7 +432,7 @@ class core_admin_renderer extends plugin_renderer_base {
 
         if ($repotype = $pluginman->plugin_external_source($pluginfo->component)) {
             $confirm .= $this->output->container(get_string('uninstalldeleteconfirmexternal', 'core_plugin', $repotype),
-                'uninstalldeleteconfirmexternal');
+                'alert alert-warning mt-2');
         }
 
         // After any uninstall we must execute full upgrade to finish the cleanup!
@@ -511,7 +511,7 @@ class core_admin_renderer extends plugin_renderer_base {
      * @return string HTML to output.
      */
     protected function warning($message, $type = 'warning') {
-        return $this->box($message, 'generalbox admin' . $type);
+        return $this->box($message, 'generalbox alert alert-' . $type);
     }
 
     /**
@@ -526,7 +526,7 @@ class core_admin_renderer extends plugin_renderer_base {
             return $this->warning(get_string('datarootsecuritywarning', 'admin', $CFG->dataroot));
 
         } else if ($insecuredataroot == INSECURE_DATAROOT_ERROR) {
-            return $this->warning(get_string('datarootsecurityerror', 'admin', $CFG->dataroot), 'error');
+            return $this->warning(get_string('datarootsecurityerror', 'admin', $CFG->dataroot), 'danger');
 
         } else {
             return '';
@@ -544,7 +544,7 @@ class core_admin_renderer extends plugin_renderer_base {
         if ($devlibdir) {
             $moreinfo = new moodle_url('/report/security/index.php');
             $warning = get_string('devlibdirpresent', 'core_admin', ['moreinfourl' => $moreinfo->out()]);
-            return $this->warning($warning, 'error');
+            return $this->warning($warning, 'danger');
 
         } else {
             return '';
@@ -721,7 +721,7 @@ class core_admin_renderer extends plugin_renderer_base {
         return $this->warning(
                     $this->container(get_string('maturitycorewarning', 'admin', $maturitylevel)) .
                     $this->container($this->doc_link('admin/versions', get_string('morehelp'))),
-                'error');
+                'danger');
     }
 
     /*
@@ -737,7 +737,7 @@ class core_admin_renderer extends plugin_renderer_base {
         }
 
         $warning = (get_string('testsiteupgradewarning', 'admin', $testsite));
-        return $this->warning($warning, 'error');
+        return $this->warning($warning, 'danger');
     }
 
     /**
@@ -772,7 +772,7 @@ class core_admin_renderer extends plugin_renderer_base {
         $level = 'warning';
 
         if ($maturity == MATURITY_ALPHA) {
-            $level = 'error';
+            $level = 'danger';
         }
 
         $maturitylevel = get_string('maturity' . $maturity, 'admin');
@@ -954,7 +954,7 @@ class core_admin_renderer extends plugin_renderer_base {
     protected function release_notes_link() {
         $releasenoteslink = get_string('releasenoteslink', 'admin', 'http://docs.moodle.org/dev/Releases');
         $releasenoteslink = str_replace('target="_blank"', 'onclick="this.target=\'_blank\'"', $releasenoteslink); // extremely ugly validation hack
-        return $this->box($releasenoteslink, 'generalbox releasenoteslink');
+        return $this->box($releasenoteslink, 'generalbox alert alert-info');
     }
 
     /**
@@ -1902,7 +1902,7 @@ class core_admin_renderer extends plugin_renderer_base {
             get_string('status'),
         );
         $servertable->colclasses = array('centeralign name', 'centeralign info', 'leftalign report', 'leftalign plugin', 'centeralign status');
-        $servertable->attributes['class'] = 'admintable environmenttable generaltable';
+        $servertable->attributes['class'] = 'admintable environmenttable generaltable table-sm';
         $servertable->id = 'serverstatus';
 
         $serverdata = array('ok'=>array(), 'warn'=>array(), 'error'=>array());
@@ -1915,7 +1915,7 @@ class core_admin_renderer extends plugin_renderer_base {
             get_string('status'),
         );
         $othertable->colclasses = array('aligncenter info', 'alignleft report', 'alignleft plugin', 'aligncenter status');
-        $othertable->attributes['class'] = 'admintable environmenttable generaltable';
+        $othertable->attributes['class'] = 'admintable environmenttable generaltable table-sm';
         $othertable->id = 'otherserverstatus';
 
         $otherdata = array('ok'=>array(), 'warn'=>array(), 'error'=>array());
index 2765659..ab45e08 100644 (file)
@@ -43,7 +43,7 @@ abstract class core_role_capability_table_base {
     protected $id;
 
     /** Added to the class="" attribute on output. */
-    protected $classes = array('rolecap');
+    protected $classes = array('rolecap table-hover');
 
     /** Default number of capabilities in the table for the search UI to be shown. */
     const NUM_CAPS_FOR_SEARCH = 12;
index b716ae5..99e8c2e 100644 (file)
@@ -50,6 +50,7 @@ class core_role_check_capability_table extends core_role_capability_table_base {
         $this->contextname = $contextname;
         $this->stryes = get_string('yes');
         $this->strno = get_string('no');
+        $this->add_classes(['table-striped']);
     }
 
     protected function add_header_cells() {
index f4ab562..c0e3d8d 100644 (file)
@@ -55,6 +55,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         foreach ($levels as $level => $classname) {
             $this->allcontextlevels[$level] = context_helper::get_level_name($level);
         }
+        $this->add_classes(['table-striped']);
     }
 
     protected function load_current_permissions() {
index 1a119d8..3440758 100644 (file)
@@ -66,9 +66,9 @@ class core_role_override_permissions_table_advanced extends core_role_capability
         $rowattributes = parent::get_row_attributes($capability);
         if ($this->permissions[$capability->name] !== 0) {
             if (empty($rowattributes['class'])) {
-                $rowattributes['class'] = "overriddenpermission";
+                $rowattributes['class'] = "overriddenpermission table-warning";
             } else {
-                $rowattributes['class'] .= " overriddenpermission";
+                $rowattributes['class'] .= " overriddenpermission table-warning";
             }
         }
         return $rowattributes;
index af78e7e..a3a3a22 100644 (file)
         <label {{#labelfor}}for="{{labelfor}}"{{/labelfor}}>
             {{{title}}}
             {{#override}}
-                <div class="form-overridden">{{override}}</div>
+                <div class="alert alert-info">{{override}}</div>
             {{/override}}
             {{#warning}}
-                <div class="form-warning">{{warning}}</div>
+                <div class="alert alert-warning">{{warning}}</div>
             {{/warning}}
         </label>
         <span class="form-shortname d-block small text-muted">{{{name}}}</span>
index 6653154..2a76659 100644 (file)
@@ -47,7 +47,7 @@
                 <h3 class="adminpagetitle"><a href="{{url}}">{{{title}}}</a></h3>
                 <ul class="adminpagepath" aria-label="{{#str}} pagepath, core {{/str}}">
                     {{#path}}
-                    <li>{{.}}</li>
+                    <li class="small text-muted">{{.}}</li>
                     {{/path}}
                 </ul>
                 <fieldset class="adminsettings">
index 34088d5..51e7960 100644 (file)
@@ -6,11 +6,11 @@ Feature: An administrator can filter user accounts by role, cohort and other pro
 
   Background:
     Given the following "users" exist:
-      | username | firstname | lastname | email | auth | confirmed | lastip |
-      | user1 | User | One | one@example.com | manual | 0 | 127.0.1.1 |
-      | user2 | User | Two | two@example.com | ldap | 1 | 0.0.0.0 |
-      | user3 | User | Three | three@example.com | manual | 1 | 0.0.0.0 |
-      | user4 | User | Four | four@example.com | ldap | 0 | 127.0.1.2 |
+      | username | firstname | lastname | email | auth | confirmed | lastip | institution | department |
+      | user1 | User | One | one@example.com | manual | 0 | 127.0.1.1       | moodle      | red        |
+      | user2 | User | Two | two@example.com | ldap | 1 | 0.0.0.0           | moodle      | blue       |
+      | user3 | User | Three | three@example.com | manual | 1 | 0.0.0.0 |                 |            |
+      | user4 | User | Four | four@example.com | ldap | 0 | 127.0.1.2 |                   |            |
     And the following "cohorts" exist:
       | name | idnumber |
       | Cohort 1 | CH1 |
@@ -104,3 +104,15 @@ Feature: An administrator can filter user accounts by role, cohort and other pro
     And I should see "User Two"
     And I should see "User Three"
     And I should see "User Four"
+
+  Scenario: Filter users by institution and department
+    When I set the field "id_institution" to "moodle"
+    And I press "Add filter"
+    Then I should see "User One"
+    And I should see "User Two"
+    And I should not see "User Three"
+    And I should not see "User Four"
+    And I set the field "id_department" to "red"
+    And I press "Add filter"
+    And I should see "User One"
+    And I should not see "User Two"
\ No newline at end of file
index cf5fa3e..1bdf548 100644 (file)
@@ -60,6 +60,11 @@ class tool_capability_settings_form extends moodleform {
         $form->addElement('select', 'roles', get_string('roleslabel', 'tool_capability'), $roles, $attributes);
         $form->setType('roles', PARAM_TEXT);
 
+        $form->addElement('checkbox', 'onlydiff',
+                get_string('filters', 'tool_capability'),
+                get_string('onlydiff', 'tool_capability'));
+        $form->setType('onlydiff', PARAM_BOOL);
+
         $form->addElement('submit', 'submitbutton', get_string('getreport', 'tool_capability'));
     }
 
index 57f0716..7f01576 100644 (file)
@@ -66,6 +66,7 @@ $capabilities = array();
 $rolestoshow = array();
 $roleids = array('0');
 $cleanedroleids = array();
+$onlydiff = false;
 if ($data = $form->get_data()) {
 
     $roleids = array();
@@ -90,6 +91,10 @@ if ($data = $form->get_data()) {
             }
         }
     }
+
+    if (isset($data->onlydiff)) {
+        $onlydiff = $data->onlydiff;
+    }
 }
 
 \tool_capability\event\report_viewed::create()->trigger();
@@ -103,7 +108,7 @@ $form->display();
 // If we have a capability, generate the report.
 if (count($capabilities) && count($rolestoshow)) {
     /* @var tool_capability_renderer $renderer */
-    echo $renderer->capability_comparison_table($capabilities, $context->id, $rolestoshow);
+    echo $renderer->capability_comparison_table($capabilities, $context->id, $rolestoshow, $onlydiff);
 }
 
 // Footer.
@@ -138,7 +143,7 @@ function print_report_tree($contextid, $contexts, $allroles) {
     // If there are any role overrides here, print them.
     if (!empty($contexts[$contextid]->rolecapabilities)) {
         $rowcounter = 0;
-        echo '<table class="generaltable rolecaps"><tbody>';
+        echo '<table class="generaltable table-striped"><tbody>';
         foreach ($allroles as $role) {
             if (isset($contexts[$contextid]->rolecapabilities[$role->id])) {
                 $permission = $contexts[$contextid]->rolecapabilities[$role->id];
index b44db22..05a4534 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['onlydiff'] = 'Show differences only';
 $string['capabilitylabel'] = 'Capability:';
 $string['capabilityreport'] = 'Capability overview';
 $string['eventreportviewed'] = 'Report viewed';
+$string['filters'] = 'Filter results';
 $string['forroles'] = 'For roles {$a}';
 $string['getreport'] = 'Get the overview';
 $string['changeoverrides'] = 'Change overrides in this context';
 $string['changeroles'] = 'Change role definitions';
 $string['intro'] = 'This report shows, for a particular capability, what permission that capability has in the definition of every role (or a selection of roles), and everywhere in the site where that capability is overridden.';
 $string['pluginname'] = 'Capability overview';
+$string['nodifferences'] = 'There are no differences to show between selected roles in this context';
 $string['reportforcapability'] = 'Report for capability \'{$a}\'';
 $string['reportsettings'] = 'Report settings';
 $string['roleslabel'] = 'Roles:';
index 30a9628..d083f28 100644 (file)
@@ -72,9 +72,10 @@ class tool_capability_renderer extends plugin_renderer_base {
      * @param array $capabilities An array of capabilities to show comparison for.
      * @param int $contextid The context we are displaying for.
      * @param array $roles An array of roles to show comparison for.
+     * @param bool $onlydiff show only different permissions
      * @return string
      */
-    public function capability_comparison_table(array $capabilities, $contextid, array $roles) {
+    public function capability_comparison_table(array $capabilities, $contextid, array $roles, $onlydiff=false) {
 
         $strpermissions = $this->get_permission_strings();
         $permissionclasses = $this->get_permission_classes();
@@ -99,18 +100,23 @@ class tool_capability_renderer extends plugin_renderer_base {
 
             $row = new html_table_row(array($captitle));
 
+            $permissiontypes = array();
             foreach ($roles as $role) {
                 if (isset($contexts[$contextid]->rolecapabilities[$role->id])) {
                     $permission = $contexts[$contextid]->rolecapabilities[$role->id];
                 } else {
                     $permission = CAP_INHERIT;
                 }
+                if (!in_array($permission, $permissiontypes)) {
+                    $permissiontypes[] = $permission;
+                }
                 $cell = new html_table_cell($strpermissions[$permission]);
                 $cell->attributes['class'] = $permissionclasses[$permission];
                 $row->cells[] = $cell;
             }
-
-            $table->data[] = $row;
+            if (!$onlydiff || count($permissiontypes) > 1) {
+                $table->data[] = $row;
+            }
         }
 
         // Start the list item, and print the context name as a link to the place to make changes.
@@ -125,11 +131,15 @@ class tool_capability_renderer extends plugin_renderer_base {
         $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 (!empty($table->data)) {
+            $html .= html_writer::table($table);
+        } else {
+            $html .= html_writer::tag('p', get_string('nodifferences', 'tool_capability'));
+        }
         // If there are any child contexts, print them recursively.
         if (!empty($contexts[$contextid]->children)) {
             foreach ($contexts[$contextid]->children as $childcontextid) {
-                $html .= $this->capability_comparison_table($capabilities, $childcontextid, $roles, true);
+                $html .= $this->capability_comparison_table($capabilities, $childcontextid, $roles, $onlydiff);
             }
         }
         return $html;
diff --git a/admin/tool/capability/tests/behat/show_capabilies.feature b/admin/tool/capability/tests/behat/show_capabilies.feature
new file mode 100644 (file)
index 0000000..d0ec7c6
--- /dev/null
@@ -0,0 +1,104 @@
+@tool @tool_capability
+Feature: show capabilities for selected roles
+  In order to check roles capabilities
+  As an admin
+  I need to be able to customize capabilities report viewing only specific roles and capabilities
+
+  Background:
+    Given the following "roles" exist:
+      | shortname     | name      | archetype |
+      | studenteq     | Studenteq | student   |
+      | studentdf     | Studentdf | student   |
+    And the following "permission overrides" exist:
+      | capability                    | permission | role        | contextlevel | reference |
+      | moodle/course:changefullname  | Allow      | studentdf   | System       |           |
+      | moodle/course:changeshortname | Prohibit   | studentdf   | System       |           |
+      | moodle/course:changeidnumber  | Prevent    | studentdf   | System       |           |
+    And I log in as "admin"
+    And I navigate to "Users > Permissions > Capability overview" in site administration
+
+  Scenario: visualize capabilities table with a limited number of capabilities
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changefullname, moodle/course:changeshortname |
+      | Roles:      | Studentdf                                                                                 |
+    And I click on "Get the overview" "button"
+    Then I should see "moodle/course:changefullname" in the "comparisontable" "table"
+    And I should see "moodle/course:changeshortname" in the "comparisontable" "table"
+    And I should not see "moodle/course:changecategory" in the "comparisontable" "table"
+
+  Scenario: visualize an allow capability
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changefullname |
+      | Roles:      | Studentdf                                                                                                     |
+    And I click on "Get the overview" "button"
+    Then I should see "Allow" in the "comparisontable" "table"
+    And I should not see "Prevent" in the "comparisontable" "table"
+    And I should not see "Prohibit" in the "comparisontable" "table"
+    And I should not see "Not set" in the "comparisontable" "table"
+
+  Scenario: visualize a prohibit capability
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changeshortname |
+      | Roles:      | Studentdf                                                                                                     |
+    And I click on "Get the overview" "button"
+    Then I should not see "Allow" in the "comparisontable" "table"
+    And I should not see "Prevent" in the "comparisontable" "table"
+    And I should see "Prohibit" in the "comparisontable" "table"
+    And I should not see "Not set" in the "comparisontable" "table"
+
+  Scenario: visualize a not set capability
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changecategory |
+      | Roles:      | Studentdf                    |
+    And I click on "Get the overview" "button"
+    Then I should not see "Allow" in the "comparisontable" "table"
+    And I should not see "Prevent" in the "comparisontable" "table"
+    And I should not see "Prohibit" in the "comparisontable" "table"
+    And I should see "Not set" in the "comparisontable" "table"
+
+  Scenario: visualize more than one role
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changecategory |
+      | Roles:      | Student, Studentdf           |
+    And I click on "Get the overview" "button"
+    Then I should see "Student" in the "comparisontable" "table"
+    And I should see "Studentdf" in the "comparisontable" "table"
+    And I should not see "Teacher" in the "comparisontable" "table"
+
+  Scenario: visualize all roles without selecting any role
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changecategory |
+    And I click on "Get the overview" "button"
+    Then I should see "Student" in the "comparisontable" "table"
+    And I should see "Studentdf" in the "comparisontable" "table"
+    And I should see "Teacher" in the "comparisontable" "table"
+
+  Scenario: visualize all roles by selecting All option
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changecategory |
+      | Roles:      | All                          |
+    And I click on "Get the overview" "button"
+    Then I should see "Student" in the "comparisontable" "table"
+    And I should see "Studentdf" in the "comparisontable" "table"
+    And I should see "Teacher" in the "comparisontable" "table"
+
+  @javascript
+  Scenario: filter capability list using javascript
+    Given I should see "moodle/site:config" in the "Capability" "field"
+    And I should see "moodle/course:change" in the "Capability" "field"
+    When I wait until the page is ready
+    And I set the field "capabilitysearch" to "moodle/course:change"
+    Then I should see "moodle/course:change" in the "Capability" "field"
+    And I should not see "moodle/site:config" in the "Capability" "field"
+
+  @javascript
+  Scenario: selecting capabilities using filters
+    Given I should see "moodle/course:change" in the "Capability" "field"
+    When I wait until the page is ready
+    And I set the field "capabilitysearch" to "moodle/course:change"
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changecategory |
+      | Roles:      | Student                      |
+    And I set the field "capabilitysearch" to ""
+    And I click on "Get the overview" "button"
+    Then I should see "moodle/course:changecategory" in the "comparisontable" "table"
diff --git a/admin/tool/capability/tests/behat/show_differences.feature b/admin/tool/capability/tests/behat/show_differences.feature
new file mode 100644 (file)
index 0000000..1c907a4
--- /dev/null
@@ -0,0 +1,67 @@
+@tool @tool_capability
+Feature: show only differences between roles for selected capabilities
+  In order to check roles capabilities
+  As an admin
+  I need to be able to filter capabilities report viewing only role differences
+
+  Background:
+    Given the following "roles" exist:
+      | shortname     | name      | archetype |
+      | studenteq     | Studenteq | student   |
+      | studentdf     | Studentdf | student   |
+    And the following "permission overrides" exist:
+      | capability                    | permission | role        | contextlevel | reference |
+      | moodle/course:changefullname  | Allow      | studentdf   | System       |           |
+      | moodle/course:changeshortname | Prohibit   | studentdf   | System       |           |
+    And I log in as "admin"
+    And I navigate to "Users > Permissions > Capability overview" in site administration
+
+  Scenario: Compare identical roles
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changefullname, moodle/course:changeshortname, moodle/course:changeidnumber, moodle/course:changesummary |
+      | Roles:      | Student, Studenteq                                                                                                     |
+    And I set the field "Show differences only" to "1"
+    And I click on "Get the overview" "button"
+    Then I should see "There are no differences to show between selected roles in this context"
+
+  Scenario: Compare different roles
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changefullname, moodle/course:changeshortname, moodle/course:changeidnumber, moodle/course:changesummary |
+      | Roles:      | Student, Studentdf                                                                                                     |
+    And I set the field "Show differences only" to "1"
+    And I click on "Get the overview" "button"
+    Then I should not see "There are no differences to show between selected roles in this context"
+    And I should see "moodle/course:changefullname" in the "comparisontable" "table"
+    And I should see "moodle/course:changeshortname" in the "comparisontable" "table"
+    And I should not see "moodle/course:changesummary" in the "comparisontable" "table"
+
+  Scenario: Compare different roles but comparing capabilities that are equals on both
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changeidnumber, moodle/course:changesummary |
+      | Roles:      | Student, Studentdf                                        |
+    And I set the field "Show differences only" to "1"
+    And I click on "Get the overview" "button"
+    Then I should see "There are no differences to show between selected roles in this context"
+
+  Scenario: Compare all roles without selecting specific role
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changefullname, moodle/site:config |
+    And I set the field "Show differences only" to "1"
+    And I click on "Get the overview" "button"
+    Then I should not see "moodle/site:config" in the "comparisontable" "table"
+    And I should see "moodle/course:changefullname" in the "comparisontable" "table"
+
+  Scenario: Compare all roles without selecting specific role on not defined capability
+    When I set the following fields to these values:
+      | Capability: | moodle/site:config |
+    And I set the field "Show differences only" to "1"
+    And I click on "Get the overview" "button"
+    Then I should see "There are no differences to show between selected roles in this context"
+
+  Scenario: Comparing only one role
+    When I set the following fields to these values:
+      | Capability: | moodle/course:changefullname, moodle/course:changeshortname, moodle/course:changeidnumber, moodle/course:changesummary |
+      | Roles:      | Student                                                                                                                |
+    And I set the field "Show differences only" to "1"
+    And I click on "Get the overview" "button"
+    Then I should see "There are no differences to show between selected roles in this context"
index 6b8b368..1c05534 100644 (file)
Binary files a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js and b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js differ
index 7e1baef..697a214 100644 (file)
Binary files a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js and b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js differ
index 6b8b368..1c05534 100644 (file)
Binary files a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js and b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js differ
index b3b1527..1e4b96c 100644 (file)
@@ -74,7 +74,7 @@ SEARCH.prototype = {
         this.button = this.form.all('input[type=submit]');
         this.lastsearch = this.form.one('input[name=search]');
 
-        var div = Y.Node.create('<div id="capabilitysearchui"></div>'),
+        var div = Y.Node.create('<div id="capabilitysearchui" data-fieldtype="text"></div>'),
             label = Y.Node.create('<label for="capabilitysearch">' + this.get('strsearch') + '</label>');
         this.input = Y.Node.create('<input type="text" id="capabilitysearch" />');
 
index e94710f..89f54ae 100644 (file)
@@ -144,6 +144,10 @@ class api {
         $rolesadded = array();
         $rolesremoved = array();
 
+        // Remove any cohort role mappings for roles which have been deleted.
+        // The role assignments are not a consideration because these will have been removed when the role was.
+        $DB->delete_records_select('tool_cohortroles', "roleid NOT IN (SELECT id FROM {role})");
+
         // Get all cohort role assignments and group them by user and role.
         $all = cohort_role_assignment::get_records(array(), 'userid, roleid');
         // We build an better structure to loop on.
index 561a56a..cf935d8 100644 (file)
@@ -29,12 +29,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_tool_customlang_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.
 
index 83e879a..2f02b45 100644 (file)
@@ -254,20 +254,19 @@ class tool_customlang_utils {
      *
      * @param string $component the name of the component
      * @param array $strings
+     * @return void
      */
     protected static function dump_strings($lang, $component, $strings) {
         global $CFG;
 
         if ($lang !== clean_param($lang, PARAM_LANG)) {
-            debugging('Unable to dump local strings for non-installed language pack .'.s($lang));
-            return false;
+            throw new moodle_exception('Unable to dump local strings for non-installed language pack .'.s($lang));
         }
         if ($component !== clean_param($component, PARAM_COMPONENT)) {
             throw new coding_exception('Incorrect component name');
         }
         if (!$filename = self::get_component_filename($component)) {
-            debugging('Unable to find the filename for the component '.s($component));
-            return false;
+            throw new moodle_exception('Unable to find the filename for the component '.s($component));
         }
         if ($filename !== clean_param($filename, PARAM_FILE)) {
             throw new coding_exception('Incorrect file name '.s($filename));
@@ -284,8 +283,7 @@ class tool_customlang_utils {
         }
 
         if (!$f = fopen($filepath, 'w')) {
-            debugging('Unable to write '.s($filepath));
-            return false;
+            throw new moodle_exception('Unable to write '.s($filepath));
         }
         fwrite($f, <<<EOF
 <?php
index 33da80e..9ae8647 100644 (file)
@@ -712,7 +712,7 @@ class api {
             'requestedby' => $requestedby->fullname,
             'requesttype' => $typetext,
             'requestdate' => userdate($requestdata->timecreated),
-            'requestorigin' => $SITE->fullname,
+            'requestorigin' => format_string($SITE->fullname, true, ['context' => context_system::instance()]),
             'requestoriginurl' => new moodle_url('/'),
             'requestcomments' => $requestdata->messagehtml,
             'datarequestsurl' => $datarequestsurl
index fe3652f..032014c 100644 (file)
@@ -26,6 +26,7 @@ namespace tool_dataprivacy\task;
 
 use action_link;
 use coding_exception;
+use context_system;
 use core\message\message;
 use core\task\adhoc_task;
 use core_user;
@@ -180,7 +181,8 @@ class process_data_request_task extends adhoc_task {
                 $message->contexturl = $datarequestsurl;
                 $message->contexturlname = get_string('datarequests', 'tool_dataprivacy');
                 // Message to the recipient.
-                $messagetextdata['message'] = get_string('resultdownloadready', 'tool_dataprivacy', $SITE->fullname);
+                $messagetextdata['message'] = get_string('resultdownloadready', 'tool_dataprivacy',
+                    format_string($SITE->fullname, true, ['context' => context_system::instance()]));
                 // Prepare download link.
                 $downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $thing->get_itemid(),
                     $thing->get_filepath(), $thing->get_filename(), true);
@@ -192,7 +194,8 @@ class process_data_request_task extends adhoc_task {
                 // No point notifying a deleted user in Moodle.
                 $message->notification = 0;
                 // Message to the recipient.
-                $messagetextdata['message'] = get_string('resultdeleted', 'tool_dataprivacy', $SITE->fullname);
+                $messagetextdata['message'] = get_string('resultdeleted', 'tool_dataprivacy',
+                    format_string($SITE->fullname, true, ['context' => context_system::instance()]));
                 // Message will be sent to the deleted user via email only.
                 $emailonly = true;
                 break;
index 9a1c6ad..8d2bf1b 100644 (file)
@@ -75,7 +75,7 @@
                 <hr />
                 <div class="p-l-3">
                     <dl class="row">
-                        <dt class="col-xs-3">
+                        <dt class="col-3">
                             {{#link}}
                                 <a href="#{{name}}"><strong style="word-wrap:break-word">{{name}}</strong></a>
                             {{/link}}
                             {{/link}}
                             <div class="small text-muted" style="word-wrap:break-word">{{type}}</div>
                         </dt>
-                        <dd class="col-xs-9">{{summary}}</dd>
+                        <dd class="col-9">{{summary}}</dd>
                     </dl>
                     <dl>
                         {{#fields}}
                         <div class="row">
-                            <dt class="col-xs-3 font-weight-normal" style="word-wrap:break-word">{{field_name}}</dt>
-                            <dd class="col-xs-9">{{field_summary}}</dd>
+                            <dt class="col-3 font-weight-normal" style="word-wrap:break-word">{{field_name}}</dt>
+                            <dd class="col-9">{{field_summary}}</dd>
                         </div>
                         {{/fields}}
                     </dl>
                 <hr />
                 <div class="p-l-3">
                     <div class="row">
-                        <div class="col-xs-12">
+                        <div class="col-12">
                             {{nullprovider}}
                         </div>
                     </div>
index 238f686..f283af1 100644 (file)
@@ -52,7 +52,7 @@ class tool_filetypes_renderer extends plugin_renderer_base {
         $out = $this->heading(get_string('pluginname', 'tool_filetypes'));
         if ($restricted) {
             $out .= html_writer::div(
-                    html_writer::div(get_string('configoverride', 'admin'), 'form-overridden'),
+                    html_writer::div(get_string('configoverride', 'admin'), 'alert alert-info'),
                     '', array('id' => 'adminsettings'));
         }
         if (count($combined) > 1) {
index ed2353a..c8375ee 100644 (file)
 .path-admin-tool-filetypes .generaltable .nonstandard {
     font-weight: bold;
 }
-
-/* Spacing around the 'Defined in config.php' stripe */
-.path-admin-tool-filetypes .form-overridden {
-    display: inline-block;
-    margin-bottom: 1em;
-    padding: 4px 6px;
-}
index c62e8de..3a8e797 100644 (file)
@@ -33,12 +33,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_tool_log_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.
 
index b68d31e..ec36ccf 100644 (file)
@@ -27,12 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_logstore_database_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.
 
index 63a312f..5a9a7e0 100644 (file)
@@ -27,12 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_logstore_standard_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.
 
diff --git a/admin/tool/lp/db/renamedclasses.php b/admin/tool/lp/db/renamedclasses.php
deleted file mode 100644 (file)
index 0530f09..0000000
+++ /dev/null
@@ -1,32 +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 contains renamed classes mappings.
- *
- * @package    tool_lp
- * @copyright  2016 Frédéric Massart - FMCorz.net
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-$renamedclasses = array(
-    'tool_lp\\external\\cohort_summary_exporter' => 'core_cohort\\external\\cohort_summary_exporter',
-    'tool_lp\\external\\course_module_summary_exporter' => 'core_course\\external\\course_module_summary_exporter',
-    'tool_lp\\external\\course_summary_exporter' => 'core_course\\external\\course_summary_exporter',
-    'tool_lp\\form\\persistent' => 'core\\form\\persistent',
-);
index 2e746d5..b8c93ac 100644 (file)
@@ -33,29 +33,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_tool_monitor_upgrade($oldversion) {
     global $CFG, $DB;
 
-    if ($oldversion < 2017021300) {
-
-        // Delete "orphaned" subscriptions.
-        $sql = "SELECT DISTINCT s.courseid
-                  FROM {tool_monitor_subscriptions} s
-       LEFT OUTER JOIN {course} c ON c.id = s.courseid
-                 WHERE s.courseid <> 0 and c.id IS NULL";
-        $deletedcourses = $DB->get_field_sql($sql);
-        if ($deletedcourses) {
-            list($sql, $params) = $DB->get_in_or_equal($deletedcourses);
-            $DB->execute("DELETE FROM {tool_monitor_subscriptions} WHERE courseid " . $sql, $params);
-        }
-
-        // Monitor savepoint reached.
-        upgrade_plugin_savepoint(true, 2017021300, 'tool', 'monitor');
-    }
-
-    // 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.
 
index f44f9d6..755026b 100644 (file)
@@ -32,10 +32,15 @@ list($options, $unrecognized) = cli_get_params(
     [
         'execute' => false,
         'help' => false,
+        'keep-alive' => 0,
         'showsql' => false,
         'showdebugging' => false,
+        'ignorelimits' => false,
     ], [
         'h' => 'help',
+        'e' => 'execute',
+        'k' => 'keep-alive',
+        'i' => 'ignorelimits',
     ]
 );
 
@@ -45,19 +50,21 @@ if ($unrecognized) {
 }
 
 if ($options['help'] or empty($options['execute'])) {
-    $help =
-"Scheduled cron tasks.
+    $help = <<<EOT
+Ad hoc cron tasks.
 
 Options:
---showsql             Show sql queries before they are executed
---showdebugging       Show developer level debugging information
---execute             Run all queued adhoc tasks
--h, --help            Print out this help
+ -h, --help                Print out this help
+     --showsql             Show sql queries before they are executed
+     --showdebugging       Show developer level debugging information
+ -e, --execute             Run all queued adhoc tasks
+ -k, --keep-alive=N        Keep this script alive for N seconds and poll for new adhoc tasks
+ -i  --ignorelimits        Ignore task_adhoc_concurrency_limit and task_adhoc_max_runtime limits
 
 Example:
 \$sudo -u www-data /usr/bin/php admin/tool/task/cli/adhoc_task.php --execute
 
-";
+EOT;
 
     echo $help;
     die;
@@ -84,6 +91,9 @@ if (moodle_needs_upgrading()) {
 if (empty($options['execute'])) {
     exit(0);
 }
+if (empty($options['keep-alive'])) {
+    $options['keep-alive'] = 0;
+}
 
 if (!empty($CFG->showcronsql)) {
     $DB->set_debug(true);
@@ -92,26 +102,18 @@ if (!empty($CFG->showcrondebugging)) {
     set_debugging(DEBUG_DEVELOPER, true);
 }
 
+$checklimits = empty($options['ignorelimits']);
+
 core_php_time_limit::raise();
-$starttime = microtime();
 
 // Increase memory limit.
 raise_memory_limit(MEMORY_EXTRA);
 
-// Emulate normal session - we use admin accoutn by default.
+// Emulate normal session - we use admin account by default.
 cron_setup_user();
 
-// Start output log.
-$timenow = time();
-$humantimenow = date('r', $timenow);
-mtrace("Server Time: {$humantimenow}\n");
+$humantimenow = date('r', time());
+$keepalive = (int)$options['keep-alive'];
 
-// Run all adhoc tasks.
-$taskcount = 0;
-while (!\core\task\manager::static_caches_cleared_since($timenow) &&
-        $task = \core\task\manager::get_next_adhoc_task($timenow)) {
-    cron_run_inner_adhoc_task($task);
-    $taskcount++;
-    unset($task);
-}
-mtrace("Ran {$taskcount} adhoc tasks found at {$humantimenow}");
+mtrace("Server Time: {$humantimenow}\n");
+cron_run_adhoc_tasks(time(), $keepalive, $checklimits);
index 1255622..76ab5da 100644 (file)
@@ -43,6 +43,7 @@ $string['pluginname'] = 'Scheduled task configuration';
 $string['resettasktodefaults'] = 'Reset task schedule to defaults';
 $string['resettasktodefaults_help'] = 'This will discard any local changes and revert the schedule for this task back to its original settings.';
 $string['runnow'] = 'Run now';
+$string['runagain'] = 'Run again';
 $string['runnow_confirm'] = 'Are you sure you want to run this task \'{$a}\' now? The task will run on the web server and may take some time to complete.';
 $string['runpattern'] = 'Run pattern';
 $string['scheduledtasks'] = 'Scheduled tasks';
index 939486d..fd7bc30 100644 (file)
@@ -92,6 +92,11 @@ $CFG->mtrace_wrapper = 'tool_task_mtrace_wrapper';
 echo html_writer::end_tag('pre');
 
 $output = $PAGE->get_renderer('tool_task');
+
+// Re-run the specified task (this will output an error if it doesn't exist).
+echo $OUTPUT->single_button(new moodle_url('/admin/tool/task/schedule_task.php',
+        array('task' => $taskname, 'confirm' => 1, 'sesskey' => sesskey())),
+        get_string('runagain', 'tool_task'));
 echo $output->link_back();
 
 echo $OUTPUT->footer();
index db95617..7f3e9d7 100644 (file)
@@ -90,7 +90,7 @@ $string['invalidcsvfile'] = 'Invalid input CSV file';
 $string['invalidencoding'] = 'Invalid encoding';
 $string['invalidmode'] = 'Invalid mode selected';
 $string['invalideupdatemode'] = 'Invalid update mode selected';
-$string['invalidvisibilitymode'] = 'Invalid visibility mode given';
+$string['invalidvisibilitymode'] = 'Invalid visible mode';
 $string['invalidroles'] = 'Invalid role names: {$a}';
 $string['invalidshortname'] = 'Invalid shortname';
 $string['missingmandatoryfields'] = 'Missing value for mandatory fields: {$a}';
index dcf72ce..72237ea 100644 (file)
Binary files a/admin/tool/usertours/amd/build/tour.min.js and b/admin/tool/usertours/amd/build/tour.min.js differ
index 9ebad30..125bbab 100644 (file)
Binary files a/admin/tool/usertours/amd/build/tour.min.js.map and b/admin/tool/usertours/amd/build/tour.min.js.map differ
index 0f9ad94..4a2fd90 100644 (file)
@@ -646,14 +646,15 @@ export default class Tour {
 
         // Is this the first step?
         if (this.isFirstStep(stepConfig.stepNumber)) {
-            template.find('[data-role="previous"]').prop('disabled', true);
+            template.find('[data-role="previous"]').hide();
         } else {
             template.find('[data-role="previous"]').prop('disabled', false);
         }
 
         // Is this the final step?
         if (this.isLastStep(stepConfig.stepNumber)) {
-            template.find('[data-role="next"]').prop('disabled', true);
+            template.find('[data-role="next"]').hide();
+            template.find('[data-role="end"]').removeClass("btn-secondary").addClass("btn-primary");
         } else {
             template.find('[data-role="next"]').prop('disabled', false);
         }
diff --git a/admin/tool/usertours/classes/local/filter/accessdate.php b/admin/tool/usertours/classes/local/filter/accessdate.php
new file mode 100644 (file)
index 0000000..26fecd4
--- /dev/null
@@ -0,0 +1,227 @@
+<?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/>.
+
+/**
+ * Access Date filter.
+ *
+ * @package    tool_usertours
+ * @copyright  2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_usertours\local\filter;
+
+defined('MOODLE_INTERNAL') || die();
+
+use context;
+use tool_usertours\tour;
+
+/**
+ * Access date filter. Used to determine if USER should see a tour based on a particular access date.
+ *
+ * @copyright  2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class accessdate extends base {
+
+    /**
+     * Access date filtering constant for setting base date as account creation date.
+     */
+    const FILTER_ACCOUNT_CREATION = 'tool_usertours_accountcreation';
+
+    /**
+     * Access date filtering constant for setting base date as account first login date.
+     */
+    const FILTER_FIRST_LOGIN = 'tool_usertours_firstlogin';
+
+    /**
+     * Access date filtering constant for setting base date as account last login date.
+     */
+    const FILTER_LAST_LOGIN = 'tool_usertours_lastlogin';
+
+    /**
+     * Default this filter to not be enabled.
+     */
+    const FILTER_ENABLED_DEFAULT = 0;
+
+    /**
+     * The name of the filter.
+     *
+     * @return  string
+     */
+    public static function get_filter_name() {
+        return 'accessdate';
+    }
+
+    /**
+     * Retrieve the list of available filter options.
+     *
+     * @return  array  An array whose keys are the valid options
+     *                 And whose values are the values to display
+     * @throws \coding_exception
+     */
+    public static function get_filter_options() {
+
+        return array(
+            self::FILTER_ACCOUNT_CREATION => get_string('filter_date_account_creation', 'tool_usertours'),
+            self::FILTER_FIRST_LOGIN => get_string('filter_date_first_login', 'tool_usertours'),
+            self::FILTER_LAST_LOGIN => get_string('filter_date_last_login', 'tool_usertours'),
+        );
+
+    }
+
+    /**
+     * Add the form elements for the filter to the supplied form.
+     *
+     * @param \MoodleQuickForm $mform The form to add filter settings to.
+     *
+     * @throws \coding_exception
+     */
+    public static function add_filter_to_form(\MoodleQuickForm &$mform) {
+
+        $filtername = static::get_filter_name();
+        $key = "filter_{$filtername}";
+        $range = "{$key}_range";
+        $enabled = "{$key}_enabled";
+
+        $mform->addElement('advcheckbox', $enabled, get_string($key, 'tool_usertours'),
+            get_string('filter_accessdate_enabled', 'tool_usertours'), null, array(0, 1));
+        $mform->addHelpButton($enabled, $enabled, 'tool_usertours');
+
+        $mform->addElement('select', $key, ' ', self::get_filter_options());
+        $mform->setDefault($key, self::FILTER_ACCOUNT_CREATION);
+        $mform->hideIf($key, $enabled, 'notchecked');
+
+        $mform->addElement('duration', $range, null, [
+            'optional' => false,
+            'defaultunit' => DAYSECS,
+        ]);
+        $mform->setDefault($range, 90 * DAYSECS);
+        $mform->hideIf($range, $enabled, 'notchecked');
+
+    }
+
+    /**
+     * Prepare the filter values for the form.
+     *
+     * @param   tour            $tour       The tour to prepare values from
+     * @param   stdClass        $data       The data value
+     * @return  stdClass
+     */
+    public static function prepare_filter_values_for_form(tour $tour, \stdClass $data) {
+        $filtername = static::get_filter_name();
+
+        $key = "filter_{$filtername}";
+        $range = "{$key}_range";
+        $enabled = "{$key}_enabled";
+
+        $values = $tour->get_filter_values($filtername);
+
+        // Prepare the advanced checkbox value and prepare filter values based on previously set values.
+        if (!empty($values)) {
+            $data->$enabled = $values->$enabled ? $values->$enabled : self::FILTER_ENABLED_DEFAULT;
+            if ($data->$enabled) {
+                if (isset($values->$key)) {
+                    $data->$key = $values->$key;
+                }
+                if (isset($values->$range)) {
+                    $data->$range = $values->$range;
+                }
+            }
+        } else {
+            $data->$enabled = self::FILTER_ENABLED_DEFAULT;
+        }
+        return $data;
+    }
+
+    /**
+     * Save the filter values from the form to the tour.
+     *
+     * @param   tour            $tour       The tour to save values to
+     * @param   \stdClass        $data       The data submitted in the form
+     */
+    public static function save_filter_values_from_form(tour $tour, \stdClass $data) {
+        $filtername = static::get_filter_name();
+        $key = "filter_{$filtername}";
+        $range = "{$key}_range";
+        $enabled = "{$key}_enabled";
+
+        $savedata = [];
+        $savedata[$key] = $data->$key;
+        $savedata[$range] = $data->$range;
+        $savedata[$enabled] = $data->$enabled;
+
+        $tour->set_filter_values($filtername, $savedata);
+    }
+
+    /**
+     * Check whether the filter matches the specified tour and/or context.
+     *
+     * @param   tour        $tour       The tour to check
+     * @param   context     $context    The context to check
+     * @return  boolean
+     */
+    public static function filter_matches(tour $tour, context $context) {
+        global $USER;
+
+        $filtername = static::get_filter_name();
+        $key = "filter_{$filtername}";
+        $range = "{$key}_range";
+        $enabled = "{$key}_enabled";
+
+        // Default behaviour is to match filter.
+        $result = true;
+        $values = (array) $tour->get_filter_values(self::get_filter_name());
+
+        // If the access date filter is not enabled, end here.
+        if (empty($values[$enabled])) {
+            return $result;
+        }
+
+        if (!empty($values[$key])) {
+            switch ($values[$key]) {
+                case (self::FILTER_ACCOUNT_CREATION):
+                    $filterbasedate = (int) $USER->timecreated;
+                    break;
+                case (self::FILTER_FIRST_LOGIN):
+                    $filterbasedate = (int) $USER->firstaccess;
+                    break;
+                case (self::FILTER_LAST_LOGIN):
+                    $filterbasedate = (int) $USER->lastlogin;
+                    break;
+                default:
+                    // Use account creation as default.
+                    $filterbasedate = (int) $USER->timecreated;
+                    break;
+            }
+            // If the base date has no value because a user hasn't accessed Moodle yet, default to account creation.
+            if (empty($filterbasedate)) {
+                $filterbasedate = (int) $USER->timecreated;
+            }
+
+            if (!empty($values[$range])) {
+                $filterrange = (int) $values[$range];
+            } else {
+                $filterrange = 90 * DAYSECS;
+            }
+
+            // If we're outside the set range from the set base date, filter out tour.
+            if ((time() > ($filterbasedate + $filterrange))) {
+                $result = false;
+            }
+        }
+        return $result;
+    }
+}
\ No newline at end of file
index 7a60194..a6855ea 100644 (file)
@@ -35,12 +35,6 @@ use tool_usertours\manager;
 function xmldb_tool_usertours_upgrade($oldversion) {
     global $CFG, $DB;
 
-    // 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.
 
index b3b22cf..2ce495b 100644 (file)
@@ -54,6 +54,9 @@ $string['event_tour_reset'] = 'Tour reset';
 $string['event_tour_ended'] = 'Tour ended';
 $string['event_step_shown'] = 'Step shown';
 $string['exporttour'] = 'Export tour';
+$string['filter_accessdate'] = 'Access date';
+$string['filter_accessdate_enabled'] = 'Enable access date filter';
+$string['filter_accessdate_enabled_help'] = 'Only show the tour to new users or users who have accessed the site recently.';
 $string['filter_category'] = 'Category';
 $string['filter_category_help'] = 'Show the tour on a page that is associated with a course in the selected category.';
 $string['filter_course'] = 'Courses';
@@ -62,6 +65,9 @@ $string['filter_courseformat'] = 'Course format';
 $string['filter_courseformat_help'] = 'Show the tour on a page that is associated with a course using the selected course format.';
 $string['filter_header'] = 'Tour filters';
 $string['filter_help'] = 'Select the conditions under which the tour will be shown. All of the filters must match for a tour to be shown to a user.';
+$string['filter_date_account_creation'] = 'User account creation date within';
+$string['filter_date_first_login'] = 'User\'s first access date within';
+$string['filter_date_last_login'] = 'User\'s last access date within';
 $string['filter_theme'] = 'Theme';
 $string['filter_theme_help'] = 'Show the tour when the user is using one of the selected themes.';
 $string['filter_role'] = 'Role';
diff --git a/admin/tool/usertours/tests/accessdate_filter_test.php b/admin/tool/usertours/tests/accessdate_filter_test.php
new file mode 100644 (file)
index 0000000..642ee0c
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Tests for time filter.
+ *
+ * @package    tool_usertours
+ * @copyright  2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use tool_usertours\tour;
+use tool_usertours\local\filter\accessdate;
+
+/**
+ * Tests for time filter.
+ *
+ * @package    tool_usertours
+ * @copyright  2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_usertours_accessdate_filter_test extends advanced_testcase {
+
+    public function setUp() {
+        $this->resetAfterTest(true);
+    }
+
+    /**
+     * Data Provider for filter_matches method.
+     *
+     * @return array
+     */
+    public function filter_matches_provider() {
+        return [
+            'No config set; Matches' => [
+                [],
+                [],
+                true,
+            ],
+            'Filter is not enabled; Match' => [
+                ['filter_accessdate' => accessdate::FILTER_ACCOUNT_CREATION, 'filter_accessdate_range' => 90 * DAYSECS,
+                    'filter_accessdate_enabled' => 0],
+                ['timecreated' => time() - (89 * DAYSECS)],
+                true,
+            ],
+            'Filter is not enabled (tour would not be displayed if it was); Match' => [
+                ['filter_accessdate' => accessdate::FILTER_ACCOUNT_CREATION, 'filter_accessdate_range' => 90 * DAYSECS,
+                    'filter_accessdate_enabled' => 0],
+                ['timecreated' => time() - (91 * DAYSECS)],
+                true,
+            ],
+            'Inside range of account creation date; Match' => [
+                ['filter_accessdate' => accessdate::FILTER_ACCOUNT_CREATION, 'filter_accessdate_range' => 90 * DAYSECS,
+                    'filter_accessdate_enabled' => 1],
+                ['timecreated' => time() - (89 * DAYSECS)],
+                true,
+            ],
+            'Outside range of account creation date; No match' => [
+                ['filter_accessdate' => accessdate::FILTER_ACCOUNT_CREATION, 'filter_accessdate_range' => 90 * DAYSECS,
+                    'filter_accessdate_enabled' => 1],
+                ['timecreated' => time() - (91 * DAYSECS)],
+                false,
+            ],
+            'Inside range of first login date; Match' => [
+                ['filter_accessdate' => accessdate::FILTER_FIRST_LOGIN, 'filter_accessdate_range' => 90 * DAYSECS,
+                    'filter_accessdate_enabled' => 1],
+                ['firstaccess' => time() - (89 * DAYSECS)],
+                true,
+            ],
+            'Outside range of first login date; No match' => [
+                ['filter_accessdate' => accessdate::FILTER_FIRST_LOGIN, 'filter_accessdate_range' => 90 * DAYSECS,
+                    'filter_accessdate_enabled' => 1],
+                ['firstaccess' => time() - (91 * DAYSECS)],
+                false,
+            ],
+            'Inside range of last login date; Match' => [
+                ['filter_accessdate' => accessdate::FILTER_LAST_LOGIN, 'filter_accessdate_range' => 90 * DAYSECS,
+                    'filter_accessdate_enabled' => 1],
+                ['lastlogin' => time() - (89 * DAYSECS)],
+                true,
+            ],
+            'Outside range of last login date; No match' => [
+                ['filter_accessdate' => accessdate::FILTER_LAST_LOGIN, 'filter_accessdate_range' => 90 * DAYSECS,
+                    'filter_accessdate_enabled' => 1],
+                ['lastlogin' => time() - (91 * DAYSECS)],
+                false,
+            ],
+            'User has never logged in, but tour should be visible; Match' => [
+                ['filter_accessdate' => accessdate::FILTER_LAST_LOGIN, 'filter_accessdate_range' => 90 * DAYSECS,
+                    'filter_accessdate_enabled' => 1],
+                ['lastlogin' => 0, 'timecreated' => time() - (89 * DAYSECS)],
+                true,
+            ],
+            'User has never logged in, and tour should not be visible; No match' => [
+                ['filter_accessdate' => accessdate::FILTER_LAST_LOGIN, 'filter_accessdate_range' => 90 * DAYSECS,
+                    'filter_accessdate_enabled' => 1],
+                ['lastlogin' => 0, 'timecreated' => time() - (91 * DAYSECS)],
+                false,
+            ],
+        ];
+    }
+
+    /**
+     * Test filter matches.
+     *
+     * @dataProvider    filter_matches_provider
+     *
+     * @param array $filtervalues the filter values set.
+     * @param array $userstate any user state required for test.
+     * @param bool $expected result expected.
+     */
+    public function test_filter_matches($filtervalues, $userstate, $expected) {
+        $course = $this->getDataGenerator()->create_course();
+        $context = \context_course::instance($course->id);
+
+        $user = $this->getDataGenerator()->create_user($userstate);
+        $this->setUser($user);
+
+        $tour = new tour();
+        $tour->set_filter_values('accessdate', $filtervalues);
+
+        $this->assertEquals($expected, accessdate::filter_matches($tour, $context));
+    }
+
+}
index 0a977db..c56c1ff 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2019111800;            // The current module version (Date: YYYYMMDDXX).
+$plugin->version   = 2019120400;            // The current module version (Date: YYYYMMDDXX).
 $plugin->requires  = 2019111200;            // Requires this Moodle version.
 $plugin->component = 'tool_usertours';      // Full name of the plugin (used for diagnostics).
index feef78a..7a37f8e 100644 (file)
@@ -120,7 +120,8 @@ class main_view extends XMLDBAction {
         $result = $this->launch('get_db_directories');
         // Display list of DB directories if everything is ok
         if ($result && !empty($XMLDB->dbdirs)) {
-            $o .= '<table id="listdirectories" border="0" cellpadding="5" cellspacing="1" class="admintable generaltable">';
+            $o .= '<table id="listdirectories" border="0" cellpadding="5" cellspacing="1"' .
+                ' class="table-striped table-sm admintable generaltable">';
             $row = 0;
             foreach ($XMLDB->dbdirs as $key => $dbdir) {
                 // Detect if this is the lastused dir
index 68f18eb..d3adb18 100644 (file)
@@ -1,5 +1,14 @@
 This files describes API changes in /admin/*.
 
+=== 3.9 ===
+
+* The following functions, previously used (exclusively) by upgrade steps are not available anymore because of the upgrade cleanup performed for this version. See MDL-65809 for more info:
+    - upgrade_fix_block_instance_configuration()
+    - upgrade_theme_is_from_family()
+    - upgrade_find_theme_location()
+    - linkcoursesectionsupgradescriptwasrun setting
+    - upgrade_block_positions()
+
 === 3.8 ===
 
 * Admin setting "Open to Google" (opentogoogle) has been renamed to the more generic "Open to search engines" (opentowebcrawlers).
index 216906c..a06b2ed 100644 (file)
         $table->head = array ();
         $table->colclasses = array();
         $table->head[] = $fullnamedisplay;
-        $table->attributes['class'] = 'admintable generaltable';
+        $table->attributes['class'] = 'admintable generaltable table-sm';
         foreach ($extracolumns as $field) {
             $table->head[] = ${$field};
         }
index c3c413c..8b2d14b 100644 (file)
@@ -32,19 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_cas_upgrade($oldversion) {
     global $CFG;
 
-    if ($oldversion < 2017020700) {
-        // Convert info in config plugins from auth/cas to auth_cas.
-        upgrade_fix_config_auth_plugin_names('cas');
-        upgrade_fix_config_auth_plugin_defaults('cas');
-        upgrade_plugin_savepoint(true, 2017020700, 'auth', 'cas');
-    }
-
-    // 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.
 
index 2a4625c..3a0691c 100644 (file)
@@ -32,19 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_db_upgrade($oldversion) {
     global $CFG, $DB;
 
-    if ($oldversion < 2017032800) {
-        // Convert info in config plugins from auth/db to auth_db
-        upgrade_fix_config_auth_plugin_names('db');
-        upgrade_fix_config_auth_plugin_defaults('db');
-        upgrade_plugin_savepoint(true, 2017032800, 'auth', 'db');
-    }
-
-    // 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.
 
index ac34c97..3d2b42d 100644 (file)
@@ -32,19 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_email_upgrade($oldversion) {
     global $CFG, $DB;
 
-    if ($oldversion < 2017020700) {
-        // Convert info in config plugins from auth/email to auth_email.
-        upgrade_fix_config_auth_plugin_names('email');
-        upgrade_fix_config_auth_plugin_defaults('email');
-        upgrade_plugin_savepoint(true, 2017020700, 'auth', 'email');
-    }
-
-    // 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.
 
index d99fdcc..abb56a9 100644 (file)
@@ -32,37 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_ldap_upgrade($oldversion) {
     global $CFG;
 
-    if ($oldversion < 2017020700) {
-        // Convert info in config plugins from auth/ldap to auth_ldap.
-        upgrade_fix_config_auth_plugin_names('ldap');
-        upgrade_fix_config_auth_plugin_defaults('ldap');
-        upgrade_plugin_savepoint(true, 2017020700, 'auth', 'ldap');
-    }
-
-    // Automatically generated Moodle v3.3.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    if ($oldversion < 2017080100) {
-        // The "auth_ldap/coursecreators" setting was replaced with "auth_ldap/coursecreatorcontext" (created
-        // dynamically from system-assignable roles) - so migrate any existing value to the first new slot.
-        if ($ldapcontext = get_config('auth_ldap', 'creators')) {
-            // Get info about the role that the old coursecreators setting would apply.
-            $creatorrole = get_archetype_roles('coursecreator');
-            $creatorrole = array_shift($creatorrole); // We can only use one, let's use the first.
-
-            // Create new setting.
-            set_config($creatorrole->shortname . 'context', $ldapcontext, 'auth_ldap');
-
-            // Delete old setting.
-            set_config('creators', null, 'auth_ldap');
-
-            upgrade_plugin_savepoint(true, 2017080100, 'auth', 'ldap');
-        }
-    }
-
-    // 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.
 
index 8afe128..0795fc2 100644 (file)
@@ -32,19 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_manual_upgrade($oldversion) {
     global $CFG;
 
-    if ($oldversion < 2017020700) {
-        // Convert info in config plugins from auth/manual to auth_manual.
-        upgrade_fix_config_auth_plugin_names('manual');
-        upgrade_fix_config_auth_plugin_defaults('manual');
-        upgrade_plugin_savepoint(true, 2017020700, 'auth', 'manual');
-    }
-
-    // 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.
 
index 905920d..fcb489d 100644 (file)
@@ -32,19 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_mnet_upgrade($oldversion) {
     global $CFG;
 
-    if ($oldversion < 2017020700) {
-        // Convert info in config plugins from auth/mnet to auth_mnet.
-        upgrade_fix_config_auth_plugin_names('mnet');
-        upgrade_fix_config_auth_plugin_defaults('mnet');
-        upgrade_plugin_savepoint(true, 2017020700, 'auth', 'mnet');
-    }
-
-    // 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.
 
index 7619cf6..ebf92f3 100644 (file)
@@ -32,19 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_none_upgrade($oldversion) {
     global $CFG, $DB;
 
-    if ($oldversion < 2017020700) {
-        // Convert info in config plugins from auth/none to auth_none.
-        upgrade_fix_config_auth_plugin_names('none');
-        upgrade_fix_config_auth_plugin_defaults('none');
-        upgrade_plugin_savepoint(true, 2017020700, 'auth', 'none');
-    }
-
-    // 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.
 
index 6dac6f2..8e8fc1a 100644 (file)
@@ -35,12 +35,6 @@ function xmldb_auth_oauth2_upgrade($oldversion) {
 
     $dbman = $DB->get_manager();
 
-    // 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.
 
index c57f98d..54d240e 100644 (file)
@@ -32,19 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_shibboleth_upgrade($oldversion) {
     global $CFG, $DB;
 
-    if ($oldversion < 2017020700) {
-        // Convert info in config plugins from auth/shibboleth to auth_shibboleth.
-        upgrade_fix_config_auth_plugin_names('shibboleth');
-        upgrade_fix_config_auth_plugin_defaults('shibboleth');
-        upgrade_plugin_savepoint(true, 2017020700, 'auth', 'shibboleth');
-    }
-
-    // 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.
 
index 9171659..79a3b80 100644 (file)
@@ -1,6 +1,12 @@
 This files describes API changes in /auth/* - plugins,
 information provided here is intended especially for developers.
 
+=== 3.9 ===
+
+* The following functions, previously used (exclusively) by upgrade steps are not available anymore because of the upgrade cleanup performed for this version. See MDL-65809 for more info:
+    - upgrade_fix_config_auth_plugin_names()
+    - upgrade_fix_config_auth_plugin_defaults()
+
 === 3.7 ===
 
 * get_password_change_info() method is added to the base class and returns an array containing the subject and body of the message
index 4cc5734..679c29a 100644 (file)
@@ -69,7 +69,10 @@ if ($importcourseid === false || $searchcourses) {
 
     // show the course selector
     echo $OUTPUT->header();
-    echo $renderer->import_course_selector($url, $search);
+    $backup = new import_ui(false, array());
+    echo $renderer->progress_bar($backup->get_progress_bar());
+    $html = $renderer->import_course_selector($url, $search);
+    echo $html;
     echo $OUTPUT->footer();
     die();
 }
@@ -131,6 +134,8 @@ if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
 
     // First execute the backup
     $backup->execute();
+    // Before destroying the backup object, we still need to generate the progress bar.
+    $progressbar = $renderer->progress_bar($backup->get_progress_bar());
     $backup->destroy();
     unset($backup);
 
@@ -209,6 +214,7 @@ if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
         echo html_writer::end_tag('ul');
         echo $OUTPUT->box_end();
     }
+    echo $progressbar;
     echo $OUTPUT->notification(get_string('importsuccess', 'backup'), 'notifysuccess');
     echo $OUTPUT->continue_button(new moodle_url('/course/view.php', array('id'=>$course->id)));
 
diff --git a/backup/tests/automated_backup_test.php b/backup/tests/automated_backup_test.php
new file mode 100644 (file)
index 0000000..9ac514f
--- /dev/null
@@ -0,0 +1,300 @@
+<?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/>.
+
+/**
+ * Automated backup tests.
+ *
+ * @package    core_backup
+ * @copyright  2019 John Yao <johnyao@catalyst-au.net>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php');
+require_once($CFG->libdir.'/cronlib.php');
+require_once($CFG->libdir . '/completionlib.php');
+
+/**
+ * Automated backup tests.
+ *
+ * @copyright  2019 John Yao <johnyao@catalyst-au.net>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_backup_automated_backup_testcase extends advanced_testcase {
+    /**
+     * @var \backup_cron_automated_helper
+     */
+    protected $backupcronautomatedhelper;
+
+    /**
+     * @var stdClass $course
+     */
+    protected $course;
+
+    protected function setUp() {
+        global $DB, $CFG;
+
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+        $CFG->enableavailability = true;
+        $CFG->enablecompletion = true;
+
+        // Getting a testable backup_cron_automated_helper class.
+        $this->backupcronautomatedhelper = new test_backup_cron_automated_helper();
+
+        $generator = $this->getDataGenerator();
+        $this->course = $generator->create_course(
+                array('format' => 'topics', 'numsections' => 3,
+                        'enablecompletion' => COMPLETION_ENABLED),
+                array('createsections' => true));
+        $forum = $generator->create_module('forum', array(
+                'course' => $this->course->id));
+        $forum2 = $generator->create_module('forum', array(
+                'course' => $this->course->id, 'completion' => COMPLETION_TRACKING_MANUAL));
+
+        // We need a grade, easiest is to add an assignment.
+        $assignrow = $generator->create_module('assign', array(
+                'course' => $this->course->id));
+        $assign = new assign(context_module::instance($assignrow->cmid), false, false);
+        $item = $assign->get_grade_item();
+
+        // Make a test grouping as well.
+        $grouping = $generator->create_grouping(array('courseid' => $this->course->id,
+                'name' => 'Grouping!'));
+
+        $availability = '{"op":"|","show":false,"c":[' .
+                '{"type":"completion","cm":' . $forum2->cmid .',"e":1},' .
+                '{"type":"grade","id":' . $item->id . ',"min":4,"max":94},' .
+                '{"type":"grouping","id":' . $grouping->id . '}' .
+                ']}';
+        $DB->set_field('course_modules', 'availability', $availability, array(
+                'id' => $forum->cmid));
+        $DB->set_field('course_sections', 'availability', $availability, array(
+                'course' => $this->course->id, 'section' => 1));
+    }
+
+    /**
+     * Tests the automated backup run when the there is course backup should be skipped.
+     */
+    public function test_automated_backup_skipped_run() {
+        global $DB;
+
+        // Enable automated back up.
+        set_config('backup_auto_active', true, 'backup');
+        set_config('backup_auto_weekdays', '1111111', 'backup');
+
+        // Start backup process.
+        $admin = get_admin();
+
+        // Backup entry should not exist.
+        $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
+        $this->assertFalse($backupcourse);
+        $this->assertInstanceOf(
+            backup_cron_automated_helper::class,
+            $this->backupcronautomatedhelper->return_this()
+        );
+
+        $classobject = $this->backupcronautomatedhelper->return_this();
+
+        $method = new ReflectionMethod('\backup_cron_automated_helper', 'get_courses');
+        $method->setAccessible(true); // Allow accessing of private method.
+        $courses = $method->invoke($classobject);
+
+        $method = new ReflectionMethod('\backup_cron_automated_helper', 'check_and_push_automated_backups');
+        $method->setAccessible(true); // Allow accessing of private method.
+        $emailpending = $method->invokeArgs($classobject, [$courses, $admin]);
+
+        $coursename = $this->course->fullname;
+        $this->expectOutputRegex("/Skipping $coursename \(Not scheduled for backup until/");
+        $this->assertFalse($emailpending);
+
+        $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
+        $this->assertNotNull($backupcourse->laststatus);
+    }
+
+    /**
+     * Tests the automated backup run when the there is course backup can be pushed to adhoc task.
+     */
+    public function test_automated_backup_push_run() {
+        global $DB;
+
+        // Enable automated back up.
+        set_config('backup_auto_active', true, 'backup');
+        set_config('backup_auto_weekdays', '1111111', 'backup');
+
+        $admin = get_admin();
+
+        $classobject = $this->backupcronautomatedhelper->return_this();
+
+        $method = new ReflectionMethod('\backup_cron_automated_helper', 'get_courses');
+        $method->setAccessible(true); // Allow accessing of private method.
+        $courses = $method->invoke($classobject);
+
+        // Create this backup course.
+        $backupcourse = new stdClass;
+        $backupcourse->courseid = $this->course->id;
+        $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN;
+        $DB->insert_record('backup_courses', $backupcourse);
+        $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
+
+        // We now manually trigger a backup pushed to adhoc task.
+        // Make sure is in the past, which means should run now.
+        $backupcourse->nextstarttime = time() - 10;
+        $DB->update_record('backup_courses', $backupcourse);
+
+        $method = new ReflectionMethod('\backup_cron_automated_helper', 'check_and_push_automated_backups');
+        $method->setAccessible(true); // Allow accessing of private method.
+        $emailpending = $method->invokeArgs($classobject, [$courses, $admin]);
+        $this->assertTrue($emailpending);
+
+        $coursename = $this->course->fullname;
+        $this->expectOutputRegex("/Putting backup of $coursename in adhoc task queue/");
+
+        $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
+        // Now this backup course status should be queued.
+        $this->assertEquals(backup_cron_automated_helper::BACKUP_STATUS_QUEUED, $backupcourse->laststatus);
+    }
+
+    /**
+     * Tests the automated backup inactive run.
+     */
+    public function test_inactive_run() {
+        backup_cron_automated_helper::run_automated_backup();
+        $this->expectOutputString("Checking automated backup status...INACTIVE\n");
+    }
+
+    /**
+     * Tests the invisible course being skipped.
+     */
+    public function test_should_skip_invisible_course() {
+        global $DB;
+
+        set_config('backup_auto_active', true, 'backup');
+        set_config('backup_auto_skip_hidden', true, 'backup');
+        set_config('backup_auto_weekdays', '1111111', 'backup');
+        // Create this backup course.
+        $backupcourse = new stdClass;
+        $backupcourse->courseid = $this->course->id;
+        // This is the status we believe last run was OK.
+        $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED;
+        $DB->insert_record('backup_courses', $backupcourse);
+        $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
+
+        $this->assertTrue(course_change_visibility($this->course->id, false));
+        $course = $DB->get_record('course', array('id' => $this->course->id));
+        $this->assertEquals('0', $course->visible);
+        $classobject = $this->backupcronautomatedhelper->return_this();
+        $nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup(null, time());
+
+        $method = new ReflectionMethod('\backup_cron_automated_helper', 'should_skip_course_backup');
+        $method->setAccessible(true); // Allow accessing of private method.
+        $skipped = $method->invokeArgs($classobject, [$backupcourse, $course, $nextstarttime]);
+
+        $this->assertTrue($skipped);
+        $this->expectOutputRegex("/Skipping $course->fullname \(Not visible\)/");
+    }
+
+    /**
+     * Tests the not modified course being skipped.
+     */
+    public function test_should_skip_not_modified_course_in_days() {
+        global $DB;
+
+        set_config('backup_auto_active', true, 'backup');
+        // Skip if not modified in two days.
+        set_config('backup_auto_skip_modif_days', 2, 'backup');
+        set_config('backup_auto_weekdays', '1111111', 'backup');
+
+        // Create this backup course.
+        $backupcourse = new stdClass;
+        $backupcourse->courseid = $this->course->id;
+        // This is the status we believe last run was OK.
+        $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED;
+        $backupcourse->laststarttime = time() - 2 * DAYSECS;
+        $backupcourse->lastendtime = time() - 1 * DAYSECS;
+        $DB->insert_record('backup_courses', $backupcourse);
+        $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
+        $course = $DB->get_record('course', array('id' => $this->course->id));
+
+        $course->timemodified = time() - 2 * DAYSECS - 1;
+
+        $classobject = $this->backupcronautomatedhelper->return_this();
+        $nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup(null, time());
+
+        $method = new ReflectionMethod('\backup_cron_automated_helper', 'should_skip_course_backup');
+        $method->setAccessible(true); // Allow accessing of private method.
+        $skipped = $method->invokeArgs($classobject, [$backupcourse, $course, $nextstarttime]);
+
+        $this->assertTrue($skipped);
+        $this->expectOutputRegex("/Skipping $course->fullname \(Not modified in the past 2 days\)/");
+    }
+
+    /**
+     * Tests the backup not modified course being skipped.
+     */
+    public function test_should_skip_not_modified_course_since_prev() {
+        global $DB;
+
+        set_config('backup_auto_active', true, 'backup');
+        // Skip if not modified in two days.
+        set_config('backup_auto_skip_modif_prev', 2, 'backup');
+        set_config('backup_auto_weekdays', '1111111', 'backup');
+
+        // Create this backup course.
+        $backupcourse = new stdClass;
+        $backupcourse->courseid = $this->course->id;
+        // This is the status we believe last run was OK.
+        $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED;
+        $backupcourse->laststarttime = time() - 2 * DAYSECS;
+        $backupcourse->lastendtime = time() - 1 * DAYSECS;
+        $DB->insert_record('backup_courses', $backupcourse);
+        $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
+        $course = $DB->get_record('course', array('id' => $this->course->id));
+
+        $course->timemodified = time() - 2 * DAYSECS - 1;
+
+        $classobject = $this->backupcronautomatedhelper->return_this();
+        $nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup(null, time());
+
+        $method = new ReflectionMethod('\backup_cron_automated_helper', 'should_skip_course_backup');
+        $method->setAccessible(true); // Allow accessing of private method.
+        $skipped = $method->invokeArgs($classobject, [$backupcourse, $course, $nextstarttime]);
+
+        $this->assertTrue($skipped);
+        $this->expectOutputRegex("/Skipping $course->fullname \(Not modified since previous backup\)/");
+    }
+}
+
+/**
+ * New backup_cron_automated_helper class for testing.
+ *
+ * This class extends the helper backup_cron_automated_helper class
+ * in order to utilise abstract class for testing.
+ *
+ * @package    core
+ * @copyright  2019 John Yao <johnyao@catalyst-au.net>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class test_backup_cron_automated_helper extends backup_cron_automated_helper {
+    /**
+     * Returning this for testing.
+     */
+    public function return_this() {
+        return $this;
+    }
+}
index 68ff822..70a2d8a 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -51,6 +50,8 @@ abstract class backup_cron_automated_helper {
     const BACKUP_STATUS_WARNING = 4;
     /** Course automated backup has yet to be run */
     const BACKUP_STATUS_NOTYETRUN = 5;
+    /** Course automated backup has been added to adhoc task queue */
+    const BACKUP_STATUS_QUEUED = 6;
 
     /** Run if required by the schedule set in config. Default. **/
     const RUN_ON_SCHEDULE = 0;
@@ -105,253 +106,290 @@ abstract class backup_cron_automated_helper {
     /**
      * Runs the automated backups if required
      *
-     * @global moodle_database $DB
+     * @param bool $rundirective
      */
     public static function run_automated_backup($rundirective = self::RUN_ON_SCHEDULE) {
-        global $CFG, $DB;
-
-        $status = true;
-        $emailpending = false;
         $now = time();
-        $config = get_config('backup');
-
-        mtrace("Checking automated backup status",'...');
-        $state = backup_cron_automated_helper::get_automated_backup_state($rundirective);
-        if ($state === backup_cron_automated_helper::STATE_DISABLED) {
-            mtrace('INACTIVE');
-            return $state;
-        } else if ($state === backup_cron_automated_helper::STATE_RUNNING) {
-            mtrace('RUNNING');
-            if ($rundirective == self::RUN_IMMEDIATELY) {
-                mtrace('Automated backups are already running. If this script is being run by cron this constitues an error. You will need to increase the time between executions within cron.');
-            } else {
-                mtrace("automated backup are already running. Execution delayed");
-            }
-            return $state;
-        } else {
-            mtrace('OK');
-        }
-        backup_cron_automated_helper::set_state_running();
 
-        mtrace("Getting admin info");
-        $admin = get_admin();
-        if (!$admin) {
-            mtrace("Error: No admin account was found");
-            $state = false;
+        $lock = self::get_automated_backup_lock($rundirective);
+        if (!$lock) {
+            return;
         }
 
-        if ($status) {
+        try {
             mtrace("Checking courses");
             mtrace("Skipping deleted courses", '...');
-            mtrace(sprintf("%d courses", backup_cron_automated_helper::remove_deleted_courses_from_schedule()));
-        }
-
-        if ($status) {
-
+            mtrace(sprintf("%d courses", self::remove_deleted_courses_from_schedule()));
             mtrace('Running required automated backups...');
             cron_trace_time_and_memory();
 
-            // This could take a while!
-            core_php_time_limit::raise();
-            raise_memory_limit(MEMORY_EXTRA);
-
-            $nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup(null, $now);
-            $showtime = "undefined";
-            if ($nextstarttime > 0) {
-                $showtime = date('r', $nextstarttime);
+            mtrace("Getting admin info");
+            $admin = get_admin();
+            if (!$admin) {
+                mtrace("Error: No admin account was found");
+                return;
             }
 
             $rs = self::get_courses($now); // Get courses to backup.
-            foreach ($rs as $course) {
-                $backupcourse = $DB->get_record('backup_courses', array('courseid' => $course->id));
-                if (!$backupcourse) {
-                    $backupcourse = new stdClass;
-                    $backupcourse->courseid = $course->id;
-                    $backupcourse->laststatus = self::BACKUP_STATUS_NOTYETRUN;
-                    $DB->insert_record('backup_courses', $backupcourse);
-                    $backupcourse = $DB->get_record('backup_courses', array('courseid' => $course->id));
-                }
+            $emailpending = self::check_and_push_automated_backups($rs, $admin);
+            $rs->close();
 
-                // The last backup is considered as successful when OK or SKIPPED.
-                $lastbackupwassuccessful =  ($backupcourse->laststatus == self::BACKUP_STATUS_SKIPPED ||
-                                            $backupcourse->laststatus == self::BACKUP_STATUS_OK) && (
-                                            $backupcourse->laststarttime > 0 && $backupcourse->lastendtime > 0);
+            // Send email to admin if necessary.
+            if ($emailpending) {
+                self::send_backup_status_to_admin($admin);
+            }
+        } finally {
+            // Everything is finished release lock.
+            $lock->release();
+            mtrace('Automated backups complete.');
+        }
+    }
 
-                // Assume that we are not skipping anything.
-                $skipped = false;
-                $skippedmessage = '';
+    /**
+     * Gets the results from the last automated backup that was run based upon
+     * the statuses of the courses that were looked at.
+     *
+     * @return array
+     */
+    public static function get_backup_status_array() {
+        global $DB;
 
-                // Check if we are going to be running the backup now.
-                $shouldrunnow = (($backupcourse->nextstarttime > 0 && $backupcourse->nextstarttime < $now)
-                    || $rundirective == self::RUN_IMMEDIATELY);
+        $result = array(
+            self::BACKUP_STATUS_ERROR => 0,
+            self::BACKUP_STATUS_OK => 0,
+            self::BACKUP_STATUS_UNFINISHED => 0,
+            self::BACKUP_STATUS_SKIPPED => 0,
+            self::BACKUP_STATUS_WARNING => 0,
+            self::BACKUP_STATUS_NOTYETRUN => 0,
+            self::BACKUP_STATUS_QUEUED => 0,
+        );
 
-                // If config backup_auto_skip_hidden is set to true, skip courses that are not visible.
-                if ($shouldrunnow && $config->backup_auto_skip_hidden) {
-                    $skipped = ($config->backup_auto_skip_hidden && !$course->visible);
-                    $skippedmessage = 'Not visible';
-                }
+        $statuses = $DB->get_records_sql('SELECT DISTINCT bc.laststatus,
+                                            COUNT(bc.courseid) AS statuscount
+                                            FROM {backup_courses} bc
+                                            GROUP BY bc.laststatus');
 
-                // If config backup_auto_skip_modif_days is set to true, skip courses
-                // that have not been modified since the number of days defined.
-                if ($shouldrunnow && !$skipped && $lastbackupwassuccessful && $config->backup_auto_skip_modif_days) {
-                    $timenotmodifsincedays = $now - ($config->backup_auto_skip_modif_days * DAYSECS);
-                    // Check log if there were any modifications to the course content.
-                    $logexists = self::is_course_modified($course->id, $timenotmodifsincedays);
-                    $skipped = ($course->timemodified <= $timenotmodifsincedays && !$logexists);
-                    $skippedmessage = 'Not modified in the past '.$config->backup_auto_skip_modif_days.' days';
-                }
+        foreach ($statuses as $status) {
+            if (empty($status->statuscount)) {
+                $status->statuscount = 0;
+            }
+            $result[(int)$status->laststatus] += $status->statuscount;
+        }
 
-                // If config backup_auto_skip_modif_prev is set to true, skip courses
-                // that have not been modified since previous backup.
-                if ($shouldrunnow && !$skipped && $lastbackupwassuccessful && $config->backup_auto_skip_modif_prev) {
-                    // Check log if there were any modifications to the course content.
-                    $logexists = self::is_course_modified($course->id, $backupcourse->laststarttime);
-                    $skipped = ($course->timemodified <= $backupcourse->laststarttime && !$logexists);
-                    $skippedmessage = 'Not modified since previous backup';
-                }
+        return $result;
+    }
 
-                // Check if the course is not scheduled to run right now.
-                if (!$shouldrunnow) {
-                    $backupcourse->nextstarttime = $nextstarttime;
-                    $DB->update_record('backup_courses', $backupcourse);