Merge branch 'MDL-47594_master' of git://github.com/kordan/moodle
authorSam Hemelryk <sam@moodle.com>
Wed, 15 Oct 2014 22:04:53 +0000 (11:04 +1300)
committerSam Hemelryk <sam@moodle.com>
Wed, 15 Oct 2014 22:04:53 +0000 (11:04 +1300)
807 files changed:
admin/cli/mysql_compressed_rows.php
admin/environment.xml
admin/roles/classes/potential_assignees_below_course.php
admin/settings/grades.php
admin/tool/behat/tests/behat/data_generators.feature
admin/tool/behat/tests/behat/get_and_set_fields.feature
admin/tool/langimport/classes/controller.php [new file with mode: 0644]
admin/tool/langimport/classes/event/langpack_imported.php [new file with mode: 0644]
admin/tool/langimport/classes/event/langpack_removed.php [new file with mode: 0644]
admin/tool/langimport/classes/event/langpack_updated.php [new file with mode: 0644]
admin/tool/langimport/classes/task/update_langpacks_task.php [new file with mode: 0644]
admin/tool/langimport/db/tasks.php [new file with mode: 0644]
admin/tool/langimport/index.php
admin/tool/langimport/lang/en/tool_langimport.php
admin/tool/langimport/tests/behat/behat_tool_langimport.php [new file with mode: 0644]
admin/tool/langimport/tests/behat/manage_langpacks.feature [new file with mode: 0644]
admin/tool/langimport/tests/events_test.php [new file with mode: 0644]
admin/tool/langimport/version.php
admin/tool/messageinbound/classes/edit_handler_form.php [new file with mode: 0644]
admin/tool/messageinbound/classes/manager.php [new file with mode: 0644]
admin/tool/messageinbound/classes/message/inbound/invalid_recipient_handler.php [new file with mode: 0644]
admin/tool/messageinbound/classes/task/cleanup_task.php [new file with mode: 0644]
admin/tool/messageinbound/classes/task/pickup_task.php [new file with mode: 0644]
admin/tool/messageinbound/db/messageinbound_handlers.php [new file with mode: 0644]
admin/tool/messageinbound/db/messages.php [new file with mode: 0644]
admin/tool/messageinbound/db/tasks.php [new file with mode: 0644]
admin/tool/messageinbound/index.php [new file with mode: 0644]
admin/tool/messageinbound/lang/en/tool_messageinbound.php [new file with mode: 0644]
admin/tool/messageinbound/renderer.php [new file with mode: 0644]
admin/tool/messageinbound/settings.php [new file with mode: 0644]
admin/tool/messageinbound/styles.css [new file with mode: 0644]
admin/tool/messageinbound/version.php [moved from question/format/learnwise/version.php with 77% similarity]
admin/user.php
auth/shibboleth/logout.php
auth/tests/behat/behat_auth.php
availability/condition/group/classes/condition.php
availability/condition/grouping/classes/condition.php
availability/condition/profile/classes/condition.php
backup/moodle2/backup_stepslib.php
backup/util/ui/backup_ui_stage.class.php
backup/util/ui/base_moodleform.class.php
backup/util/ui/base_ui_stage.class.php
backup/util/ui/tests/behat/backup_courses.feature
backup/util/ui/tests/behat/behat_backup.php
blocks/badges/db/upgrade.php
blocks/calendar_month/db/upgrade.php
blocks/calendar_upcoming/db/upgrade.php
blocks/course_overview/lang/en/block_course_overview.php
blocks/course_overview/locallib.php
blocks/course_overview/renderer.php
blocks/course_overview/settings.php
blocks/course_overview/styles.css
blocks/navigation/tests/behat/expand_my_courses_setting.feature
blocks/navigation/tests/behat/view_my_courses.feature
cache/stores/memcache/lib.php
cache/stores/memcache/tests/memcache_test.php
cache/tests/fixtures/stores.php
calendar/export_execute.php
cohort/assign.php
cohort/edit.php
cohort/edit_form.php
cohort/externallib.php
cohort/index.php
cohort/lib.php
cohort/tests/behat/access_visible_cohorts.feature [new file with mode: 0644]
cohort/tests/behat/upload_cohorts.feature
cohort/tests/cohortlib_test.php
cohort/tests/externallib_test.php
cohort/tests/fixtures/uploadcohorts1.csv
cohort/upload_form.php
course/classes/management/helper.php
course/classes/management_renderer.php
course/management.php
course/renderer.php
course/reset_form.php
course/tests/behat/course_search.feature [new file with mode: 0644]
enrol/cohort/ajax.php [deleted file]
enrol/cohort/edit_form.php
enrol/cohort/lang/en/enrol_cohort.php
enrol/cohort/lib.php
enrol/cohort/locallib.php
enrol/cohort/yui/quickenrolment/assets/skins/sam/quickenrolment.css [deleted file]
enrol/cohort/yui/quickenrolment/assets/skins/sam/sprite.png [deleted file]
enrol/cohort/yui/quickenrolment/quickenrolment.js [deleted file]
enrol/locallib.php
enrol/manual/ajax.php
enrol/manual/lang/en/enrol_manual.php
enrol/manual/lib.php
enrol/manual/locallib.php
enrol/manual/yui/quickenrolment/assets/skins/sam/quickenrolment.css
enrol/manual/yui/quickenrolment/quickenrolment.js
enrol/renderer.php
enrol/self/edit_form.php
enrol/users.php
filter/mathjaxloader/filter.php
grade/edit/scale/edit.php
grade/edit/settings/index.php
grade/edit/tree/action.php
grade/edit/tree/calculation.php
grade/edit/tree/category.php
grade/edit/tree/category_form.php
grade/edit/tree/functions.js
grade/edit/tree/grade.php
grade/edit/tree/grade_form.php
grade/edit/tree/index.php
grade/edit/tree/item.php
grade/edit/tree/item_form.php
grade/edit/tree/lib.php
grade/edit/tree/outcomeitem.php
grade/edit/tree/outcomeitem_form.php
grade/import/csv/classes/load_data.php [new file with mode: 0644]
grade/import/csv/classes/output/renderer.php [new file with mode: 0644]
grade/import/csv/index.php
grade/import/csv/tests/fixtures/phpunit_gradeimport_csv_load_data.php [new file with mode: 0644]
grade/import/csv/tests/load_data_test.php [new file with mode: 0644]
grade/import/csv/version.php
grade/import/direct/classes/import_form.php [new file with mode: 0644]
grade/import/direct/classes/mapping_form.php [new file with mode: 0644]
grade/import/direct/db/access.php [new file with mode: 0644]
grade/import/direct/index.php [new file with mode: 0644]
grade/import/direct/lang/en/gradeimport_direct.php [new file with mode: 0644]
grade/import/direct/styles.css [new file with mode: 0644]
grade/import/direct/version.php [new file with mode: 0644]
grade/import/grade_import_form.php
grade/lib.php
grade/report/grader/ajax_callbacks.php
grade/report/grader/index.php
grade/report/grader/lang/en/gradereport_grader.php
grade/report/grader/lib.php
grade/report/grader/module.js
grade/report/grader/preferences.php
grade/report/grader/settings.php
grade/report/grader/styles.css
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js [new file with mode: 0644]
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js [new file with mode: 0644]
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js [new file with mode: 0644]
grade/report/grader/yui/build/moodle-gradereport_grader-scrollview/moodle-gradereport_grader-scrollview-debug.js [deleted file]
grade/report/grader/yui/build/moodle-gradereport_grader-scrollview/moodle-gradereport_grader-scrollview-min.js [deleted file]
grade/report/grader/yui/build/moodle-gradereport_grader-scrollview/moodle-gradereport_grader-scrollview.js [deleted file]
grade/report/grader/yui/src/gradereporttable/build.json [new file with mode: 0644]
grade/report/grader/yui/src/gradereporttable/js/floatingheaders.js [new file with mode: 0644]
grade/report/grader/yui/src/gradereporttable/js/gradereporttable.js [new file with mode: 0644]
grade/report/grader/yui/src/gradereporttable/meta/gradereporttable.json [new file with mode: 0644]
grade/report/grader/yui/src/scrollview/build.json [deleted file]
grade/report/grader/yui/src/scrollview/js/scrollview.js [deleted file]
grade/report/lib.php
grade/report/overview/lib.php
grade/report/user/lib.php
grade/report/user/settings.php
grade/report/user/styles.css
grade/report/user/version.php
grade/tests/behat/behat_grade.php
grade/tests/behat/grade_UI_settings.feature
grade/tests/behat/grade_aggregation.feature
grade/tests/behat/grade_calculated_weights.feature [new file with mode: 0644]
grade/tests/behat/grade_mingrade.feature [new file with mode: 0644]
grade/tests/behat/grade_natural_normalisation.feature [new file with mode: 0644]
grade/tests/behat/grade_scales.feature [new file with mode: 0644]
grade/tests/behat/grade_single_item_scales.feature [new file with mode: 0644]
grade/tests/behat/grade_view.feature
grade/tests/edittreelib_test.php
grade/tests/report_graderlib_test.php
grade/tests/reportlib_test.php
group/autogroup_form.php
install/lang/af/install.php [new file with mode: 0644]
install/lang/es_ve/langconfig.php [new file with mode: 0644]
install/lang/vi/error.php
iplookup/index.php
lang/en/backup.php
lang/en/cohort.php
lang/en/deprecated.txt
lang/en/grades.php
lang/en/moodle.php
lib/badgeslib.php
lib/blocklib.php
lib/classes/event/grade_deleted.php [new file with mode: 0644]
lib/classes/grades_external.php
lib/classes/message/inbound/handler.php
lib/classes/message/inbound/private_files_handler.php [new file with mode: 0644]
lib/classes/message/inbound/processing_failed_exception.php [new file with mode: 0644]
lib/classes/plugin_manager.php
lib/classes/session/manager.php
lib/classes/task/scheduled_task.php
lib/csvlib.class.php
lib/db/access.php
lib/db/install.xml
lib/db/messageinbound_handlers.php [moved from question/format/learnwise/lang/en/qformat_learnwise.php with 70% similarity]
lib/db/services.php
lib/db/upgrade.php
lib/deprecatedlib.php
lib/editor/atto/autosave-ajax.php
lib/editor/atto/lang/en/editor_atto.php
lib/editor/atto/lib.php
lib/editor/atto/plugins/indent/yui/build/moodle-atto_indent-button/moodle-atto_indent-button-debug.js
lib/editor/atto/plugins/indent/yui/build/moodle-atto_indent-button/moodle-atto_indent-button-min.js
lib/editor/atto/plugins/indent/yui/build/moodle-atto_indent-button/moodle-atto_indent-button.js
lib/editor/atto/plugins/indent/yui/src/button/js/button.js
lib/editor/atto/plugins/managefiles/yui/build/moodle-atto_managefiles-usedfiles/moodle-atto_managefiles-usedfiles-debug.js
lib/editor/atto/plugins/managefiles/yui/build/moodle-atto_managefiles-usedfiles/moodle-atto_managefiles-usedfiles-min.js
lib/editor/atto/plugins/managefiles/yui/build/moodle-atto_managefiles-usedfiles/moodle-atto_managefiles-usedfiles.js
lib/editor/atto/plugins/managefiles/yui/src/usedfiles/js/usedfiles.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/src/editor/js/autosave.js
lib/editor/tinymce/plugins/managefiles/tinymce/editor_plugin.js
lib/excellib.class.php
lib/filelib.php
lib/form/recaptcha.php
lib/google/Google/Auth/Abstract.php [moved from lib/google/auth/Google_Auth.php with 57% similarity]
lib/google/Google/Auth/AppIdentity.php [new file with mode: 0644]
lib/google/Google/Auth/AssertionCredentials.php [moved from lib/google/auth/Google_AssertionCredentials.php with 59% similarity]
lib/google/Google/Auth/Exception.php [new file with mode: 0644]
lib/google/Google/Auth/LoginTicket.php [moved from lib/google/auth/Google_LoginTicket.php with 82% similarity]
lib/google/Google/Auth/OAuth2.php [new file with mode: 0644]
lib/google/Google/Auth/Simple.php [new file with mode: 0644]
lib/google/Google/Cache/Abstract.php [moved from lib/google/cache/Google_Cache.php with 82% similarity]
lib/google/Google/Cache/Apc.php [new file with mode: 0644]
lib/google/Google/Cache/Exception.php [new file with mode: 0644]
lib/google/Google/Cache/File.php [new file with mode: 0644]
lib/google/Google/Cache/Memcache.php [new file with mode: 0644]
lib/google/Google/Cache/Null.php [new file with mode: 0644]
lib/google/Google/Client.php [new file with mode: 0644]
lib/google/Google/Collection.php [new file with mode: 0644]
lib/google/Google/Config.php [new file with mode: 0644]
lib/google/Google/Exception.php [moved from lib/google/service/Google_Service.php with 83% similarity]
lib/google/Google/Http/Batch.php [moved from lib/google/service/Google_BatchRequest.php with 52% similarity]
lib/google/Google/Http/CacheParser.php [moved from lib/google/io/Google_CacheParser.php with 87% similarity]
lib/google/Google/Http/MediaFileUpload.php [new file with mode: 0644]
lib/google/Google/Http/REST.php [moved from lib/google/io/Google_REST.php with 61% similarity]
lib/google/Google/Http/Request.php [new file with mode: 0644]
lib/google/Google/IO/Abstract.php [new file with mode: 0644]
lib/google/Google/IO/Curl.php [new file with mode: 0644]
lib/google/Google/IO/Exception.php [new file with mode: 0644]
lib/google/Google/IO/Stream.php [new file with mode: 0644]
lib/google/Google/IO/cacerts.pem [new file with mode: 0644]
lib/google/Google/Model.php [new file with mode: 0644]
lib/google/Google/Service.php [new file with mode: 0644]
lib/google/Google/Service/AdExchangeBuyer.php [new file with mode: 0644]
lib/google/Google/Service/AdExchangeSeller.php [new file with mode: 0644]
lib/google/Google/Service/AdSense.php [new file with mode: 0644]
lib/google/Google/Service/AdSenseHost.php [new file with mode: 0644]
lib/google/Google/Service/Admin.php [new file with mode: 0644]
lib/google/Google/Service/Analytics.php [new file with mode: 0644]
lib/google/Google/Service/AndroidPublisher.php [new file with mode: 0644]
lib/google/Google/Service/AppState.php [new file with mode: 0644]
lib/google/Google/Service/Appsactivity.php [new file with mode: 0644]
lib/google/Google/Service/Audit.php [new file with mode: 0644]
lib/google/Google/Service/Autoscaler.php [new file with mode: 0644]
lib/google/Google/Service/Bigquery.php [new file with mode: 0644]
lib/google/Google/Service/Blogger.php [new file with mode: 0644]
lib/google/Google/Service/Books.php [new file with mode: 0644]
lib/google/Google/Service/Calendar.php [new file with mode: 0644]
lib/google/Google/Service/CivicInfo.php [new file with mode: 0644]
lib/google/Google/Service/CloudMonitoring.php [new file with mode: 0644]
lib/google/Google/Service/Compute.php [new file with mode: 0644]
lib/google/Google/Service/Coordinate.php [new file with mode: 0644]
lib/google/Google/Service/Customsearch.php [new file with mode: 0644]
lib/google/Google/Service/Datastore.php [new file with mode: 0644]
lib/google/Google/Service/Dfareporting.php [new file with mode: 0644]
lib/google/Google/Service/Directory.php [new file with mode: 0644]
lib/google/Google/Service/Dns.php [new file with mode: 0644]
lib/google/Google/Service/DoubleClickBidManager.php [new file with mode: 0644]
lib/google/Google/Service/Doubleclicksearch.php [new file with mode: 0644]
lib/google/Google/Service/Drive.php [new file with mode: 0644]
lib/google/Google/Service/Exception.php [new file with mode: 0644]
lib/google/Google/Service/Freebase.php [new file with mode: 0644]
lib/google/Google/Service/Fusiontables.php [new file with mode: 0644]
lib/google/Google/Service/Games.php [new file with mode: 0644]
lib/google/Google/Service/GamesManagement.php [new file with mode: 0644]
lib/google/Google/Service/Genomics.php [new file with mode: 0644]
lib/google/Google/Service/Gmail.php [new file with mode: 0644]
lib/google/Google/Service/GroupsMigration.php [new file with mode: 0644]
lib/google/Google/Service/Groupssettings.php [new file with mode: 0644]
lib/google/Google/Service/IdentityToolkit.php [new file with mode: 0644]
lib/google/Google/Service/Licensing.php [new file with mode: 0644]
lib/google/Google/Service/Manager.php [new file with mode: 0644]
lib/google/Google/Service/MapsEngine.php [new file with mode: 0644]
lib/google/Google/Service/Mirror.php [new file with mode: 0644]
lib/google/Google/Service/Oauth2.php [new file with mode: 0644]
lib/google/Google/Service/Orkut.php [new file with mode: 0644]
lib/google/Google/Service/Pagespeedonline.php [new file with mode: 0644]
lib/google/Google/Service/Plus.php [new file with mode: 0644]
lib/google/Google/Service/PlusDomains.php [new file with mode: 0644]
lib/google/Google/Service/Prediction.php [new file with mode: 0644]
lib/google/Google/Service/Pubsub.php [new file with mode: 0644]
lib/google/Google/Service/QPXExpress.php [new file with mode: 0644]
lib/google/Google/Service/Replicapool.php [new file with mode: 0644]
lib/google/Google/Service/Reports.php [new file with mode: 0644]
lib/google/Google/Service/Reseller.php [new file with mode: 0644]
lib/google/Google/Service/Resource.php [new file with mode: 0644]
lib/google/Google/Service/Resourceviews.php [new file with mode: 0644]
lib/google/Google/Service/SQLAdmin.php [new file with mode: 0644]
lib/google/Google/Service/ShoppingContent.php [new file with mode: 0644]
lib/google/Google/Service/SiteVerification.php [new file with mode: 0644]
lib/google/Google/Service/Spectrum.php [new file with mode: 0644]
lib/google/Google/Service/Storage.php [new file with mode: 0644]
lib/google/Google/Service/Taskqueue.php [new file with mode: 0644]
lib/google/Google/Service/Tasks.php [new file with mode: 0644]
lib/google/Google/Service/Translate.php [new file with mode: 0644]
lib/google/Google/Service/Urlshortener.php [new file with mode: 0644]
lib/google/Google/Service/Webfonts.php [new file with mode: 0644]
lib/google/Google/Service/YouTube.php [new file with mode: 0644]
lib/google/Google/Service/YouTubeAnalytics.php [new file with mode: 0644]
lib/google/Google/Signer/Abstract.php [moved from lib/google/auth/Google_Signer.php with 91% similarity]
lib/google/Google/Signer/P12.php [new file with mode: 0644]
lib/google/Google/Utils.php [moved from lib/google/service/Google_Utils.php with 78% similarity]
lib/google/Google/Utils/URITemplate.php [new file with mode: 0644]
lib/google/Google/Verifier/Abstract.php [moved from lib/google/auth/Google_Verifier.php with 91% similarity]
lib/google/Google/Verifier/Pem.php [moved from lib/google/auth/Google_PemVerifier.php with 72% similarity]
lib/google/NOTICE [deleted file]
lib/google/README [deleted file]
lib/google/README.md [new file with mode: 0644]
lib/google/auth/Google_AuthNone.php [deleted file]
lib/google/auth/Google_OAuth2.php [deleted file]
lib/google/auth/Google_P12Signer.php [deleted file]
lib/google/cache/Google_ApcCache.php [deleted file]
lib/google/cache/Google_FileCache.php [deleted file]
lib/google/cache/Google_MemcacheCache.php [deleted file]
lib/google/contrib/Google_AdexchangebuyerService.php [deleted file]
lib/google/contrib/Google_AdsenseService.php [deleted file]
lib/google/contrib/Google_AdsensehostService.php [deleted file]
lib/google/contrib/Google_AnalyticsService.php [deleted file]
lib/google/contrib/Google_BigqueryService.php [deleted file]
lib/google/contrib/Google_BloggerService.php [deleted file]
lib/google/contrib/Google_BooksService.php [deleted file]
lib/google/contrib/Google_CalendarService.php [deleted file]
lib/google/contrib/Google_ComputeService.php [deleted file]
lib/google/contrib/Google_CustomsearchService.php [deleted file]
lib/google/contrib/Google_DriveService.php [deleted file]
lib/google/contrib/Google_FreebaseService.php [deleted file]
lib/google/contrib/Google_FusiontablesService.php [deleted file]
lib/google/contrib/Google_GanService.php [deleted file]
lib/google/contrib/Google_LatitudeService.php [deleted file]
lib/google/contrib/Google_LicensingService.php [deleted file]
lib/google/contrib/Google_ModeratorService.php [deleted file]
lib/google/contrib/Google_Oauth2Service.php [deleted file]
lib/google/contrib/Google_OrkutService.php [deleted file]
lib/google/contrib/Google_PagespeedonlineService.php [deleted file]
lib/google/contrib/Google_PlusMomentsService.php [deleted file]
lib/google/contrib/Google_PlusService.php [deleted file]
lib/google/contrib/Google_PredictionService.php [deleted file]
lib/google/contrib/Google_ShoppingService.php [deleted file]
lib/google/contrib/Google_SiteVerificationService.php [deleted file]
lib/google/contrib/Google_StorageService.php [deleted file]
lib/google/contrib/Google_TaskqueueService.php [deleted file]
lib/google/contrib/Google_TasksService.php [deleted file]
lib/google/contrib/Google_TranslateService.php [deleted file]
lib/google/contrib/Google_UrlshortenerService.php [deleted file]
lib/google/contrib/Google_WebfontsService.php [deleted file]
lib/google/contrib/Google_YoutubeService.php [deleted file]
lib/google/curlio.php
lib/google/external/URITemplateParser.php [deleted file]
lib/google/io/Google_CurlIO.php [deleted file]
lib/google/io/Google_HttpRequest.php [deleted file]
lib/google/io/Google_IO.php [deleted file]
lib/google/io/cacerts.pem [deleted file]
lib/google/lib.php [new file with mode: 0644]
lib/google/readme_moodle.txt
lib/google/service/Google_MediaFileUpload.php [deleted file]
lib/google/service/Google_Model.php [deleted file]
lib/google/service/Google_ServiceResource.php [deleted file]
lib/grade/constants.php
lib/grade/grade_category.php
lib/grade/grade_grade.php
lib/grade/grade_item.php
lib/grade/grade_object.php
lib/grade/tests/grade_object_test.php [new file with mode: 0644]
lib/gradelib.php
lib/jquery/jquery-1.11.0.min.js [deleted file]
lib/jquery/jquery-1.11.1.js [moved from lib/jquery/jquery-1.11.0.js with 95% similarity]
lib/jquery/jquery-1.11.1.min.js [new file with mode: 0644]
lib/jquery/plugins.php
lib/jquery/readme_moodle.txt
lib/jquery/ui-1.10.4/css/base/images/animated-overlay.gif [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-bg_flat_0_aaaaaa_40x100.png [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-bg_flat_75_ffffff_40x100.png [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-bg_glass_55_fbf9ee_1x400.png [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-bg_glass_65_ffffff_1x400.png [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-bg_glass_75_dadada_1x400.png [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-bg_glass_75_e6e6e6_1x400.png [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-bg_glass_95_fef1ec_1x400.png [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-icons_222222_256x240.png [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-icons_2e83ff_256x240.png [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-icons_454545_256x240.png [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-icons_888888_256x240.png [deleted file]
lib/jquery/ui-1.10.4/css/base/images/ui-icons_cd0a0a_256x240.png [deleted file]
lib/jquery/ui-1.10.4/css/base/jquery-ui.min.css [deleted file]
lib/jquery/ui-1.10.4/jquery-ui.min.js [deleted file]
lib/jquery/ui-1.11.1/images/ui-bg_diagonals-thick_18_b81900_40x40.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-bg_diagonals-thick_20_666666_40x40.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-bg_flat_10_000000_40x100.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-bg_glass_100_f6f6f6_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-bg_glass_100_fdf5ce_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-bg_glass_65_ffffff_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-bg_gloss-wave_35_f6a828_500x100.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-bg_highlight-soft_100_eeeeee_1x100.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-bg_highlight-soft_75_ffe45c_1x100.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-icons_222222_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-icons_228ef1_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-icons_ef8c08_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-icons_ffd27a_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/images/ui-icons_ffffff_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/jquery-ui.css [new file with mode: 0644]
lib/jquery/ui-1.11.1/jquery-ui.js [moved from lib/jquery/ui-1.10.4/jquery-ui.js with 82% similarity]
lib/jquery/ui-1.11.1/jquery-ui.min.css [new file with mode: 0644]
lib/jquery/ui-1.11.1/jquery-ui.min.js [new file with mode: 0644]
lib/jquery/ui-1.11.1/jquery-ui.structure.css [new file with mode: 0644]
lib/jquery/ui-1.11.1/jquery-ui.structure.min.css [new file with mode: 0644]
lib/jquery/ui-1.11.1/jquery-ui.theme.css [new file with mode: 0644]
lib/jquery/ui-1.11.1/jquery-ui.theme.min.css [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_flat_75_ffffff_40x100.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_glass_65_ffffff_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_glass_75_dadada_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-icons_222222_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-icons_2e83ff_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-icons_454545_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-icons_888888_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-icons_cd0a0a_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/jquery-ui.css [moved from lib/jquery/ui-1.10.4/css/base/jquery-ui.css with 77% similarity]
lib/jquery/ui-1.11.1/theme/smoothness/jquery-ui.min.css [new file with mode: 0644]
lib/jquery/ui-1.11.1/theme/smoothness/theme.css [new file with mode: 0644]
lib/modinfolib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/pagelib.php
lib/password_compat/lib/password.php
lib/password_compat/readme_moodle.txt
lib/password_compat/tests/PasswordGetInfoTest.php
lib/password_compat/tests/PasswordHashTest.php
lib/password_compat/tests/PasswordNeedsRehashTest.php
lib/password_compat/tests/PasswordVerifyTest.php
lib/pear/README.txt
lib/pear/XML/Parser.php [deleted file]
lib/pear/XML/Parser/Simple.php [deleted file]
lib/pear/XML/deprecated.txt [deleted file]
lib/phpmailer/README.md
lib/phpmailer/README_MOODLE.txt
lib/phpmailer/changelog.md
lib/phpmailer/class.phpmailer.php
lib/phpmailer/class.smtp.php
lib/phpmailer/index.html [deleted file]
lib/phpmailer/language/phpmailer.lang-ar.php
lib/phpmailer/language/phpmailer.lang-be.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-br.php
lib/phpmailer/language/phpmailer.lang-ca.php
lib/phpmailer/language/phpmailer.lang-ch.php
lib/phpmailer/language/phpmailer.lang-cz.php
lib/phpmailer/language/phpmailer.lang-de.php
lib/phpmailer/language/phpmailer.lang-dk.php
lib/phpmailer/language/phpmailer.lang-el.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-eo.php
lib/phpmailer/language/phpmailer.lang-es.php
lib/phpmailer/language/phpmailer.lang-et.php
lib/phpmailer/language/phpmailer.lang-fa.php
lib/phpmailer/language/phpmailer.lang-fi.php
lib/phpmailer/language/phpmailer.lang-fo.php
lib/phpmailer/language/phpmailer.lang-fr.php
lib/phpmailer/language/phpmailer.lang-gl.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-he.php
lib/phpmailer/language/phpmailer.lang-hr.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-hu.php
lib/phpmailer/language/phpmailer.lang-it.php
lib/phpmailer/language/phpmailer.lang-ja.php
lib/phpmailer/language/phpmailer.lang-ka.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-lt.php
lib/phpmailer/language/phpmailer.lang-lv.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-nl.php
lib/phpmailer/language/phpmailer.lang-no.php
lib/phpmailer/language/phpmailer.lang-pl.php
lib/phpmailer/language/phpmailer.lang-pt.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-ro.php
lib/phpmailer/language/phpmailer.lang-ru.php
lib/phpmailer/language/phpmailer.lang-se.php
lib/phpmailer/language/phpmailer.lang-sk.php
lib/phpmailer/language/phpmailer.lang-sr.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-tr.php
lib/phpmailer/language/phpmailer.lang-uk.php
lib/phpmailer/language/phpmailer.lang-vi.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-zh.php
lib/phpmailer/language/phpmailer.lang-zh_cn.php
lib/phpmailer/moodle_phpmailer.php
lib/phpunit/classes/util.php
lib/setup.php
lib/setuplib.php
lib/tablelib.php
lib/testing/classes/util.php
lib/testing/generator/data_generator.php
lib/tests/behat/behat_data_generators.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_navigation.php
lib/tests/cronlib_test.php
lib/tests/event_grade_deleted_test.php [new file with mode: 0644]
lib/tests/event_user_graded_test.php
lib/tests/filelib_test.php
lib/tests/moodlelib_test.php
lib/tests/scheduled_task_test.php
lib/thirdpartylibs.xml
lib/timezone.txt
lib/typo3/class.t3lib_div.php
lib/typo3/readme_moodle.txt
lib/upgrade.txt
lib/weblib.php
message/externallib.php
message/lib.php
message/tests/externallib_test.php
message/tests/fixtures/inbound_fixtures.php
mod/assign/tests/events_test.php
mod/feedback/item/captcha/lib.php
mod/forum/classes/existing_subscriber_selector.php
mod/forum/classes/message/inbound/reply_handler.php [new file with mode: 0644]
mod/forum/classes/potential_subscriber_selector.php
mod/forum/classes/subscriptions.php
mod/forum/db/messageinbound_handlers.php [new file with mode: 0644]
mod/forum/db/services.php
mod/forum/discuss.php
mod/forum/externallib.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/post.php
mod/forum/tests/behat/forum_subscriptions_availability.feature [new file with mode: 0644]
mod/forum/tests/externallib_test.php
mod/forum/version.php
mod/lesson/backup/moodle2/backup_lesson_stepslib.php
mod/lesson/backup/moodle2/restore_lesson_activity_task.class.php
mod/lesson/backup/moodle2/restore_lesson_stepslib.php
mod/lesson/db/install.xml
mod/lesson/db/upgrade.php
mod/lesson/editpage.php
mod/lesson/format.php
mod/lesson/lang/en/lesson.php
mod/lesson/lib.php
mod/lesson/locallib.php
mod/lesson/mod_form.php
mod/lesson/pagetypes/matching.php
mod/lesson/pagetypes/multichoice.php
mod/lesson/pagetypes/numerical.php
mod/lesson/pagetypes/shortanswer.php
mod/lesson/pagetypes/truefalse.php
mod/lesson/report.php
mod/lesson/settings.php
mod/lesson/tests/behat/date_availability.feature
mod/lesson/tests/behat/import_images.feature [new file with mode: 0644]
mod/lesson/tests/behat/lesson_navigation.feature
mod/lesson/tests/behat/lesson_review.feature
mod/lesson/tests/behat/password_protection.feature
mod/lesson/tests/behat/questions_images.feature [new file with mode: 0644]
mod/lesson/tests/behat/time_limit.feature
mod/lesson/tests/fixtures/moodle_logo.jpg [new file with mode: 0644]
mod/lesson/tests/fixtures/multichoice.xml [new file with mode: 0644]
mod/lesson/version.php
mod/lti/OAuth.php
mod/lti/OAuthBody.php
mod/lti/TrivialStore.php
mod/lti/ajax.php
mod/lti/backup/moodle1/lib.php
mod/lti/backup/moodle2/backup_lti_activity_task.class.php
mod/lti/backup/moodle2/backup_lti_stepslib.php
mod/lti/backup/moodle2/restore_lti_activity_task.class.php
mod/lti/backup/moodle2/restore_lti_stepslib.php
mod/lti/basiclti.js [deleted file]
mod/lti/classes/local/ltiservice/resource_base.php [new file with mode: 0644]
mod/lti/classes/local/ltiservice/response.php [new file with mode: 0644]
mod/lti/classes/local/ltiservice/service_base.php [new file with mode: 0644]
mod/lti/classes/plugininfo/ltiservice.php [new file with mode: 0644]
mod/lti/db/access.php
mod/lti/db/install.xml
mod/lti/db/log.php
mod/lti/db/subplugins.php
mod/lti/db/upgrade.php
mod/lti/db/upgradelib.php [new file with mode: 0644]
mod/lti/edit_form.php
mod/lti/grade.php
mod/lti/index.php
mod/lti/instructor_edit_tool_type.php
mod/lti/lang/en/lti.php
mod/lti/launch.php
mod/lti/lib.php
mod/lti/localadminlib.php [deleted file]
mod/lti/locallib.php
mod/lti/mod_form.js
mod/lti/mod_form.php
mod/lti/register.php [new file with mode: 0644]
mod/lti/register_form.php [new file with mode: 0644]
mod/lti/registersettings.php [new file with mode: 0644]
mod/lti/registration.php [new file with mode: 0644]
mod/lti/registrationreturn.php [new file with mode: 0644]
mod/lti/request_tool.php
mod/lti/return.php
mod/lti/service.php
mod/lti/service/profile/classes/local/resource/profile.php [new file with mode: 0644]
mod/lti/service/profile/classes/local/service/profile.php [new file with mode: 0644]
mod/lti/service/profile/lang/en/ltiservice_profile.php [new file with mode: 0644]
mod/lti/service/profile/version.php [new file with mode: 0644]
mod/lti/service/readme.txt [new file with mode: 0644]
mod/lti/service/toolproxy/classes/local/resource/toolproxy.php [new file with mode: 0644]
mod/lti/service/toolproxy/classes/local/service/toolproxy.php [new file with mode: 0644]
mod/lti/service/toolproxy/lang/en/ltiservice_toolproxy.php [new file with mode: 0644]
mod/lti/service/toolproxy/version.php [new file with mode: 0644]
mod/lti/service/toolsettings/classes/local/resource/contextsettings.php [new file with mode: 0644]
mod/lti/service/toolsettings/classes/local/resource/linksettings.php [new file with mode: 0644]
mod/lti/service/toolsettings/classes/local/resource/systemsettings.php [new file with mode: 0644]
mod/lti/service/toolsettings/classes/local/service/toolsettings.php [new file with mode: 0644]
mod/lti/service/toolsettings/lang/en/ltiservice_toolsettings.php [new file with mode: 0644]
mod/lti/service/toolsettings/version.php [new file with mode: 0644]
mod/lti/servicelib.php
mod/lti/services.php [new file with mode: 0644]
mod/lti/settings.php
mod/lti/tests/event/unknown_service_api_called_test.php
mod/lti/tests/generator_test.php
mod/lti/tests/locallib_test.php
mod/lti/tests/upgradelib_test.php [new file with mode: 0644]
mod/lti/toolproxies.php [new file with mode: 0644]
mod/lti/toolssettings.php [new file with mode: 0644]
mod/lti/typessettings.php
mod/lti/upgrade.txt
mod/lti/version.php
mod/lti/view.php
mod/quiz/addrandom.php
mod/quiz/addrandomform.php
mod/quiz/attemptlib.php
mod/quiz/classes/admin_review_setting.php
mod/quiz/classes/output/edit_renderer.php [new file with mode: 0644]
mod/quiz/classes/question/bank/add_action_column.php [new file with mode: 0644]
mod/quiz/classes/question/bank/custom_view.php [new file with mode: 0644]
mod/quiz/classes/question/bank/question_name_text_column.php [new file with mode: 0644]
mod/quiz/classes/repaginate.php [new file with mode: 0644]
mod/quiz/classes/structure.php [new file with mode: 0644]
mod/quiz/db/renamedclasses.php [new file with mode: 0644]
mod/quiz/edit.js [deleted file]
mod/quiz/edit.php
mod/quiz/edit_rest.php [new file with mode: 0644]
mod/quiz/editlib.php [deleted file]
mod/quiz/lang/en/quiz.php
mod/quiz/lib.php
mod/quiz/locallib.php
mod/quiz/mod_form.php
mod/quiz/questionbank.ajax.php [new file with mode: 0644]
mod/quiz/renderer.php
mod/quiz/repaginate.php [new file with mode: 0644]
mod/quiz/settings.php
mod/quiz/styles.css
mod/quiz/tests/attempt_walkthrough_from_csv_test.php
mod/quiz/tests/attempt_walkthrough_test.php
mod/quiz/tests/behat/behat_mod_quiz.php
mod/quiz/tests/behat/completion_condition_attempts_used.feature
mod/quiz/tests/behat/completion_condition_passing_grade.feature
mod/quiz/tests/behat/editing_add.feature [new file with mode: 0644]
mod/quiz/tests/behat/editing_click_move_icon.feature [new file with mode: 0644]
mod/quiz/tests/behat/editing_repaginate.feature [new file with mode: 0644]
mod/quiz/tests/behat/editing_set_marks.feature [new file with mode: 0644]
mod/quiz/tests/editlib_test.php [deleted file]
mod/quiz/tests/events_test.php
mod/quiz/tests/lib_test.php
mod/quiz/tests/locallib_test.php
mod/quiz/tests/repaginate_test.php [new file with mode: 0644]
mod/quiz/tests/structure_test.php [new file with mode: 0644]
mod/quiz/upgrade.txt
mod/quiz/version.php
mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-debug.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-min.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform-debug.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform-min.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser-debug.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser-min.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase-debug.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase-min.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-quizquestionbank/moodle-mod_quiz-quizquestionbank-debug.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-quizquestionbank/moodle-mod_quiz-quizquestionbank-min.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-quizquestionbank/moodle-mod_quiz-quizquestionbank.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-randomquestion/moodle-mod_quiz-randomquestion-debug.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-randomquestion/moodle-mod_quiz-randomquestion-min.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-randomquestion/moodle-mod_quiz-randomquestion.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-debug.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-min.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base-debug.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base-min.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page-debug.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page-min.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-debug.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-min.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot.js [new file with mode: 0644]
mod/quiz/yui/src/dragdrop/build.json [new file with mode: 0644]
mod/quiz/yui/src/dragdrop/js/dragdrop.js [new file with mode: 0644]
mod/quiz/yui/src/dragdrop/js/resource.js [new file with mode: 0644]
mod/quiz/yui/src/dragdrop/js/section.js [new file with mode: 0644]
mod/quiz/yui/src/dragdrop/meta/dragdrop.json [new file with mode: 0644]
mod/quiz/yui/src/modform/build.json [new file with mode: 0644]
mod/quiz/yui/src/modform/js/modform.js [new file with mode: 0644]
mod/quiz/yui/src/modform/meta/modform.json [new file with mode: 0644]
mod/quiz/yui/src/questionchooser/build.json [new file with mode: 0644]
mod/quiz/yui/src/questionchooser/js/questionchooser.js [new file with mode: 0644]
mod/quiz/yui/src/questionchooser/meta/questionchooser.json [new file with mode: 0644]
mod/quiz/yui/src/quizbase/build.json [new file with mode: 0644]
mod/quiz/yui/src/quizbase/js/quizbase.js [new file with mode: 0644]
mod/quiz/yui/src/quizbase/meta/quizbase.json [moved from grade/report/grader/yui/src/scrollview/meta/scrollview.json with 63% similarity]
mod/quiz/yui/src/quizquestionbank/build.json [new file with mode: 0644]
mod/quiz/yui/src/quizquestionbank/js/quizquestionbank.js [new file with mode: 0644]
mod/quiz/yui/src/quizquestionbank/meta/quizquestionbank.json [new file with mode: 0644]
mod/quiz/yui/src/randomquestion/build.json [new file with mode: 0644]
mod/quiz/yui/src/randomquestion/js/randomquestion.js [new file with mode: 0644]
mod/quiz/yui/src/randomquestion/meta/randomquestion.json [new file with mode: 0644]
mod/quiz/yui/src/repaginate/build.json [new file with mode: 0644]
mod/quiz/yui/src/repaginate/js/repaginate.js [new file with mode: 0644]
mod/quiz/yui/src/repaginate/meta/repaginate.json [new file with mode: 0644]
mod/quiz/yui/src/toolboxes/build.json [new file with mode: 0644]
mod/quiz/yui/src/toolboxes/js/resource.js [new file with mode: 0644]
mod/quiz/yui/src/toolboxes/js/section.js [new file with mode: 0644]
mod/quiz/yui/src/toolboxes/js/toolbox.js [new file with mode: 0644]
mod/quiz/yui/src/toolboxes/meta/toolboxes.json [new file with mode: 0644]
mod/quiz/yui/src/util/build.json [new file with mode: 0644]
mod/quiz/yui/src/util/js/base.js [new file with mode: 0644]
mod/quiz/yui/src/util/js/page.js [new file with mode: 0644]
mod/quiz/yui/src/util/js/slot.js [new file with mode: 0644]
mod/quiz/yui/src/util/meta/util.json [new file with mode: 0644]
mod/scorm/tests/events_test.php
mod/wiki/pagelib.php
mod/wiki/renderer.php
mod/wiki/search.php
mod/wiki/tests/behat/wiki_search.feature
mod/workshop/lang/en/workshop.php
mod/workshop/lib.php
mod/workshop/locallib.php
mod/workshop/tests/generator/lib.php
mod/workshop/tests/generator_test.php
mod/workshop/tests/locallib_test.php
mod/workshop/version.php
my/tests/behat/add_blocks.feature
my/tests/behat/reset_page.feature
my/tests/behat/restrict_available_blocks.feature
phpunit.xml.dist
pix/e/insert_page_break.png [new file with mode: 0644]
pix/e/insert_page_break.svg [new file with mode: 0644]
pix/e/remove_page_break.png [new file with mode: 0644]
pix/e/remove_page_break.svg [new file with mode: 0644]
pix/t/reset.png [new file with mode: 0644]
pix/t/reset.svg [new file with mode: 0644]
portfolio/boxnet/lib.php
question/addquestion.php
question/classes/bank/checkbox_column.php
question/classes/bank/view.php
question/editlib.php
question/format/gift/tests/behat/import_export.feature [moved from question/format/gift/tests/behat/import.feature with 82% similarity]
question/format/learnwise/format.php [deleted file]
question/format/learnwise/learnwise-example.xml [deleted file]
question/format/xml/tests/behat/import_export.feature [moved from question/format/xml/tests/behat/import.feature with 92% similarity]
question/question.php
question/tests/behat/delete_questions.feature
question/type/multianswer/module.js
question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-debug.js
question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-min.js
question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager.js
question/yui/src/qbankmanager/js/qbankmanager.js
report/log/classes/table_log.php
report/participation/index.php
report/security/locallib.php
repository/boxnet/lib.php
repository/googledocs/lib.php
theme/base/layout/frontpage.php
theme/base/layout/general.php
theme/base/style/core.css
theme/base/style/grade.css
theme/bootstrapbase/layout/columns1.php
theme/bootstrapbase/layout/columns2.php
theme/bootstrapbase/layout/columns3.php
theme/bootstrapbase/layout/popup.php
theme/bootstrapbase/less/moodle/backup-restore.less
theme/bootstrapbase/less/moodle/buttons.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/course.less
theme/bootstrapbase/less/moodle/expendable.less
theme/bootstrapbase/less/moodle/grade.less
theme/bootstrapbase/less/moodle/modules.less
theme/bootstrapbase/less/moodle/responsive.less
theme/bootstrapbase/style/moodle.css
theme/clean/layout/columns1.php
theme/clean/layout/columns2.php
theme/clean/layout/columns3.php
theme/upgrade.txt
user/files.php
user/files_form.php
user/index.php
user/lib.php
user/profile.php
user/tests/behat/edituserpassword.feature
user/tests/behat/table_sorting.feature [new file with mode: 0644]
version.php
webservice/lib.php

index 92aa9db..95af47d 100644 (file)
@@ -52,7 +52,7 @@ $help =
 
 By default InnoDB storage table is using legacy Antelope file format
 which has major restriction on database row size.
-Use this script to detect and fix database tables with potentail data
+Use this script to detect and fix database tables with potential data
 overflow problems.
 
 Options:
index b1cfc26..38ec017 100644 (file)
       </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
+  <MOODLE version="2.8" requires="2.2">
+    <UNICODE level="required">
+      <FEEDBACK>
+        <ON_ERROR message="unicoderequired" />
+      </FEEDBACK>
+    </UNICODE>
+    <DATABASE level="required">
+      <VENDOR name="mariadb" version="5.5.31" />
+      <VENDOR name="mysql" version="5.5.31" />
+      <VENDOR name="postgres" version="9.1" />
+      <VENDOR name="mssql" version="10.0" />
+      <VENDOR name="oracle" version="10.2" />
+    </DATABASE>
+    <PHP version="5.4.4" 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="optional">
+        <FEEDBACK>
+          <ON_CHECK message="mbstringrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="curl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="curlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="openssl" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="opensslrecommended" />
+        </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="intl" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="intlrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="json" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="hash" 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="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="quizattemptsupgradedmessage" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+    </CUSTOM_CHECKS>
+  </MOODLE>
 </COMPATIBILITY_MATRIX>
index 71664a1..ce60366 100644 (file)
@@ -48,11 +48,12 @@ class core_role_potential_assignees_below_course extends core_role_assign_user_s
         $fields      = 'SELECT ' . $this->required_fields_sql('u');
         $countfields = 'SELECT COUNT(u.id)';
 
-        $sql   = " FROM {user} u
-              LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.roleid = :roleid AND ra.contextid = :contextid)
-                  WHERE u.id IN ($enrolsql)
-                        $wherecondition
-                        AND ra.id IS NULL";
+        $sql   = " FROM ($enrolsql) enrolled_users_view
+                   JOIN {user} u ON u.id = enrolled_users_view.id
+              LEFT JOIN {role_assignments} ra ON (ra.userid = enrolled_users_view.id AND
+                                            ra.roleid = :roleid AND ra.contextid = :contextid)
+                  WHERE ra.id IS NULL
+                        $wherecondition";
         $params['contextid'] = $this->context->id;
         $params['roleid'] = $this->roleid;
 
index 2cb6122..84a5296 100644 (file)
@@ -96,11 +96,9 @@ if (has_capability('moodle/grade:manage', $systemcontext)
                          GRADE_AGGREGATE_MODE            =>new lang_string('aggregatemode', 'grades'),
                          GRADE_AGGREGATE_SUM             =>new lang_string('aggregatesum', 'grades'));
 
-        $defaultvisible = array(GRADE_AGGREGATE_MEAN, GRADE_AGGREGATE_WEIGHTED_MEAN, GRADE_AGGREGATE_WEIGHTED_MEAN2,
-                                GRADE_AGGREGATE_EXTRACREDIT_MEAN, GRADE_AGGREGATE_MEDIAN, GRADE_AGGREGATE_MIN,
-                                GRADE_AGGREGATE_MAX, GRADE_AGGREGATE_MODE, GRADE_AGGREGATE_SUM);
+        $defaultvisible = array(GRADE_AGGREGATE_SUM);
 
-        $defaults = array('value'=>GRADE_AGGREGATE_WEIGHTED_MEAN2, 'forced'=>false, 'adv'=>false);
+        $defaults = array('value' => GRADE_AGGREGATE_SUM, 'forced' => false, 'adv' => false);
         $temp->add(new admin_setting_gradecat_combo('grade_aggregation', new lang_string('aggregation', 'grades'), new lang_string('aggregation_help', 'grades'), $defaults, $options));
 
         $temp->add(new admin_setting_configmultiselect('grade_aggregations_visible', new lang_string('aggregationsvisible', 'grades'),
index c9758f2..2b32cb0 100644 (file)
@@ -185,6 +185,12 @@ Feature: Set up contextual data for tests
       | url        | Test url name          | Test url description          | C1     | url1        |
       | wiki       | Test wiki name         | Test wiki description         | C1     | wiki1       |
       | workshop   | Test workshop name     | Test workshop description     | C1     | workshop1   |
+    And the following "scales" exist:
+      | name | scale |
+      | Test Scale 1 | Disappointing, Good, Very good, Excellent |
+    And the following "activities" exist:
+      | activity   | name                            | intro                         | course | idnumber    | grade |
+      | assign     | Test assignment name with scale | Test assignment description   | C1     | assign1     | Test Scale 1 |
     When I log in as "admin"
     And I follow "Course 1"
     Then I should see "Test assignment name"
@@ -214,6 +220,10 @@ Feature: Set up contextual data for tests
     And I should see "Test workshop name"
     And I follow "Test assignment name"
     And I should see "Test assignment description"
+    And I follow "C1"
+    And I follow "Test assignment name with scale"
+    And I follow "Edit settings"
+    And the field "Type" matches value "Scale"
 
   @javascript
   Scenario: Add relations between users and groups
@@ -297,13 +307,124 @@ Feature: Set up contextual data for tests
       | Course 1 | C1 |
     And the following "grade categories" exist:
       | fullname | course |
-      | Grade category 1 | C1|
+      | Grade category 1 | C1 |
     And the following "grade categories" exist:
       | fullname | course | gradecategory |
-      | Grade sub category 2 | C1 | Grade category 1|
+      | Grade sub category 2 | C1 | Grade category 1 |
     When I log in as "admin"
     And I follow "Courses"
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     Then I should see "Grade category 1"
     And I should see "Grade sub category 2"
+
+  Scenario: Add a bunch of grade items
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1 |
+    And the following "grade categories" exist:
+      | fullname | course |
+      | Grade category 1 | C1 |
+    And the following "grade categories" exist:
+      | fullname | course | gradecategory |
+      | Grade sub category 2 | C1 | Grade category 1 |
+    And the following "grade items" exist:
+      | itemname    | course |
+      | Test Grade Item 1 | C1 |
+    And the following "grade items" exist:
+      | itemname    | course | gradecategory |
+      | Test Grade Item 2 | C1 | Grade category 1 |
+      | Test Grade Item 3 | C1 | Grade sub category 2 |
+    When I log in as "admin"
+    And I follow "Course 1"
+    And I follow "Grades"
+    And I expand "Setup" node
+    And I follow "Categories and items"
+    Then I should see "Test Grade Item 1"
+    And I follow "Edit   Test Grade Item 1"
+    And I expand all fieldsets
+    And I should see "Course 1"
+    And I press "Cancel"
+    And I should see "Grade category 1"
+    And I should see "Test Grade Item 2"
+    And I follow "Edit   Test Grade Item 2"
+    And I expand all fieldsets
+    And I should see "Grade category 1"
+    And I press "Cancel"
+    And I should see "Grade sub category 2"
+    And I should see "Test Grade Item 3"
+    And I follow "Edit   Test Grade Item 3"
+    And I expand all fieldsets
+    And I should see "Grade sub category 2"
+    And I press "Cancel"
+
+  Scenario: Add a bunch of scales
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1 |
+    And the following "scales" exist:
+      | name | scale |
+      | Test Scale 1 | Disappointing, Good, Very good, Excellent |
+    When I log in as "admin"
+    And I follow "Course 1"
+    And I follow "Grades"
+    And I follow "Scales"
+    Then I should see "Test Scale 1"
+    And I should see "Disappointing,  Good,  Very good,  Excellent"
+
+  Scenario: Add a bunch of outcomes
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "scales" exist:
+      | name | scale |
+      | Test Scale 1 | Disappointing, Good, Very good, Excellent |
+    And the following "grade outcomes" exist:
+      | fullname        | shortname | scale        |
+      | Grade outcome 1 | OT1       | Test Scale 1 |
+    And the following "grade outcomes" exist:
+      | fullname        | shortname | course | scale        |
+      | Grade outcome 2 | OT2       | C1     | Test Scale 1 |
+    When I log in as "admin"
+    And I set the following administration settings values:
+      | Enable outcomes | 1 |
+    And I follow "Home"
+    And I follow "Course 1"
+    And I follow "Outcomes"
+    Then I should see "Grade outcome 1" in the "#addoutcomes" "css_element"
+    And I should see "Grade outcome 2" in the "#removeoutcomes" "css_element"
+    And I follow "Edit outcomes"
+    And the following should exist in the "generaltable" table:
+      | Full name       | Short name | Scale        |
+      | Grade outcome 2 | OT2        | Test Scale 1 |
+
+  Scenario: Add a bunch of outcome grade items
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "scales" exist:
+      | name         | scale                                     |
+      | Test Scale 1 | Disappointing, Good, Very good, Excellent |
+    And the following "grade outcomes" exist:
+      | fullname        | shortname | course | scale        |
+      | Grade outcome 1 | OT1       | C1     | Test Scale 1 |
+    And the following "grade categories" exist:
+      | fullname         | course |
+      | Grade category 1 | C1     |
+     And the following "grade items" exist:
+       | itemname                  | course | outcome | gradecategory    |
+       | Test Outcome Grade Item 1 | C1     | OT1     | Grade category 1 |
+    When I log in as "admin"
+    And I set the following administration settings values:
+      | Enable outcomes | 1 |
+    And I follow "Home"
+    And I follow "Course 1"
+    And I follow "Grades"
+    And I expand "Setup" node
+    And I follow "Categories and items"
+    Then I should see "Test Outcome Grade Item 1"
+    And I follow "Edit   Test Outcome Grade Item 1"
+    And the field "Outcome" matches value "Grade outcome 1"
+    And I expand all fieldsets
+    And "//div[contains(@class, 'fitem')]/div[contains(@class, 'fitemtitle')]/div[contains(@class, fstaticlabel) and contains(., 'Grade category')]/../../div[contains(@class, 'felement') and contains(., 'Grade category 1')]" "xpath_element" should exist
+    And I press "Cancel"
index 780a315..5e281be 100644 (file)
@@ -159,6 +159,7 @@ Feature: Verify that all form fields values can be get and set
     And I add a "Lesson" to section "1"
     And I set the following fields to these values:
       | Name | Test lesson |
+      | Description | Test lesson description |
       | available[enabled] | 1 |
     And I set the field "deadline[enabled]" to "1"
     # Checkbox (AJAX) - Checking "the field matches value" before saving.
diff --git a/admin/tool/langimport/classes/controller.php b/admin/tool/langimport/classes/controller.php
new file mode 100644 (file)
index 0000000..06f815e
--- /dev/null
@@ -0,0 +1,237 @@
+<?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/>.
+
+/**
+ * Lang import controller
+ *
+ * @package    tool_langimport
+ * @copyright  2014 Dan Poltawski <dan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_langimport;
+
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->libdir.'/filelib.php');
+require_once($CFG->libdir.'/componentlib.class.php');
+
+/**
+ * Lang import controller
+ *
+ * @package    tool_langimport
+ * @copyright  2014 Dan Poltawski <dan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class controller {
+    /** @var array list of informational messages */
+    public $info;
+    /** @var array  list of error messages */
+    public $errors;
+    /** @var \lang_installer */
+    private $installer;
+    /** @var array languages available on the remote server */
+    public $availablelangs;
+
+    /**
+     * Constructor.
+     */
+    public function __construct() {
+        make_temp_directory('');
+        make_upload_directory('lang');
+
+        $this->info = array();
+        $this->errors = array();
+        $this->installer = new \lang_installer();
+
+        $this->availablelangs = $this->installer->get_remote_list_of_languages();
+    }
+
+    /**
+     * Install language packs provided
+     *
+     * @param string|array $langs array of langcodes or individual langcodes
+     * @param bool $updating true if updating the langpacks
+     * @return int false if an error encountered or
+     * @throws \moodle_exception when error is encountered installing langpack
+     */
+    public function install_languagepacks($langs, $updating = false) {
+        global $CFG;
+
+        $this->installer->set_queue($langs);
+        $results = $this->installer->run();
+
+        $updatedpacks = 0;
+
+        foreach ($results as $langcode => $langstatus) {
+            switch ($langstatus) {
+                case \lang_installer::RESULT_DOWNLOADERROR:
+                    $a       = new \stdClass();
+                    $a->url  = $this->installer->lang_pack_url($langcode);
+                    $a->dest = $CFG->dataroot.'/lang';
+                    $this->errors[] = get_string('remotedownloaderror', 'error', $a);
+                    throw new \moodle_exception('remotedownloaderror', 'error', $a);
+                    break;
+                case \lang_installer::RESULT_INSTALLED:
+                    $updatedpacks++;
+                    if ($updating) {
+                        event\langpack_updated::event_with_langcode($langcode)->trigger();
+                        $this->info[] = get_string('langpackupdated', 'tool_langimport', $langcode);
+                    } else {
+                        $this->info[] = get_string('langpackinstalled', 'tool_langimport', $langcode);
+                        event\langpack_imported::event_with_langcode($langcode)->trigger();
+                    }
+                    break;
+                case \lang_installer::RESULT_UPTODATE:
+                    $this->info[] = get_string('langpackuptodate', 'tool_langimport', $langcode);
+                    break;
+            }
+        }
+
+        return $updatedpacks;
+    }
+
+    /**
+     * Uninstall language pack
+     *
+     * @param string $lang language code
+     * @return bool true if language succesfull installed
+     */
+    public function uninstall_language($lang) {
+        global $CFG;
+
+        $dest1 = $CFG->dataroot.'/lang/'.$lang;
+        $dest2 = $CFG->dirroot.'/lang/'.$lang;
+        $rm1 = false;
+        $rm2 = false;
+        if (file_exists($dest1)) {
+            $rm1 = remove_dir($dest1);
+        }
+        if (file_exists($dest2)) {
+            $rm2 = remove_dir($dest2);
+        }
+
+        if ($rm1 or $rm2) {
+            $this->info[] = get_string('langpackremoved', 'tool_langimport', $lang);
+            event\langpack_removed::event_with_langcode($lang)->trigger();
+            return true;
+        } else {    // Nothing deleted, possibly due to permission error.
+            $this->errors[] = 'An error has occurred, language pack is not completely uninstalled, please check file permissions';
+            return false;
+        }
+    }
+
+    /**
+     * Updated all install language packs with the latest found on servre
+     *
+     * @return bool true if languages succesfully updated.
+     */
+    public function update_all_installed_languages() {
+        global $CFG;
+
+        if (!$availablelangs = $this->installer->get_remote_list_of_languages()) {
+            $this->errors[] = get_string('cannotdownloadlanguageupdatelist', 'error');
+            return false;
+        }
+
+        $md5array = array();    // Convert to (string)langcode => (string)md5.
+        foreach ($availablelangs as $alang) {
+            $md5array[$alang[0]] = $alang[1];
+        }
+
+        // Filter out unofficial packs.
+        $currentlangs = array_keys(get_string_manager()->get_list_of_translations(true));
+        $updateablelangs = array();
+        foreach ($currentlangs as $clang) {
+            if (!array_key_exists($clang, $md5array)) {
+                $noticeok[] = get_string('langpackupdateskipped', 'tool_langimport', $clang);
+                continue;
+            }
+            $dest1 = $CFG->dataroot.'/lang/'.$clang;
+            $dest2 = $CFG->dirroot.'/lang/'.$clang;
+
+            if (file_exists($dest1.'/langconfig.php') || file_exists($dest2.'/langconfig.php')) {
+                $updateablelangs[] = $clang;
+            }
+        }
+
+        // Filter out packs that have the same md5 key.
+        $neededlangs = array();
+        foreach ($updateablelangs as $ulang) {
+            if (!$this->is_installed_lang($ulang, $md5array[$ulang])) {
+                $neededlangs[] = $ulang;
+            }
+        }
+
+        // Clean-up currently installed versions of the packs.
+        foreach ($neededlangs as $packindex => $pack) {
+            if ($pack == 'en') {
+                continue;
+            }
+
+            // Delete old directories.
+            $dest1 = $CFG->dataroot.'/lang/'.$pack;
+            $dest2 = $CFG->dirroot.'/lang/'.$pack;
+            if (file_exists($dest1)) {
+                if (!remove_dir($dest1)) {
+                    $noticeerror[] = 'Could not delete old directory '.$dest1.', update of '.$pack
+                        .' failed, please check permissions.';
+                    unset($neededlangs[$packindex]);
+                    continue;
+                }
+            }
+            if (file_exists($dest2)) {
+                if (!remove_dir($dest2)) {
+                    $noticeerror[] = 'Could not delete old directory '.$dest2.', update of '.$pack
+                        .' failed, please check permissions.';
+                    unset($neededlangs[$packindex]);
+                    continue;
+                }
+            }
+        }
+
+        try {
+            $updated = $this->install_languagepacks($neededlangs, true);
+        } catch (\moodle_exception $e) {
+            return false;
+        }
+
+        if ($updated) {
+            $this->info[] = get_string('langupdatecomplete', 'tool_langimport');
+        } else {
+            $this->info[] = get_string('nolangupdateneeded', 'tool_langimport');
+        }
+
+        return true;
+    }
+
+    /**
+     * checks the md5 of the zip file, grabbed from download.moodle.org,
+     * against the md5 of the local language file from last update
+     * @param string $lang language code
+     * @param string $md5check md5 to check
+     * @return bool true if installed
+     */
+    public function is_installed_lang($lang, $md5check) {
+        global $CFG;
+        $md5file = $CFG->dataroot.'/lang/'.$lang.'/'.$lang.'.md5';
+        if (file_exists($md5file)) {
+            return (file_get_contents($md5file) == $md5check);
+        }
+        return false;
+    }
+}
+
+
diff --git a/admin/tool/langimport/classes/event/langpack_imported.php b/admin/tool/langimport/classes/event/langpack_imported.php
new file mode 100644 (file)
index 0000000..c8bd149
--- /dev/null
@@ -0,0 +1,114 @@
+<?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/>.
+
+/**
+ * The langimport langpack imported event.
+ *
+ * @package    tool_langimport
+ * @copyright  2014 Dan Poltawski <dan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_langimport\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_langimport langpack imported event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - string langcode: the langpage pack code.
+ * }
+ *
+ * @package    tool_langimport
+ * @copyright  2014 Dan Poltawski <dan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class langpack_imported extends \core\event\base {
+    /**
+     * Create instance of event.
+     *
+     * @param string $langcode
+     * @return langpack_updated
+     */
+    public static function event_with_langcode($langcode) {
+        $data = array(
+            'context' => \context_system::instance(),
+            'other' => array(
+                'langcode' => $langcode,
+            )
+        );
+
+        return self::create($data);
+    }
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The language pack '{$this->other['langcode']}' was installed.";
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('langpackinstalledevent', 'tool_langimport');
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/admin/tool/langimport/');
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['langcode'])) {
+            throw new \coding_exception('The \'langcode\' value must be set');
+        }
+        // We can't use PARAM_LANG here as the string manager might not be aware of langpack yet.
+        $cleanedlang = clean_param($this->other['langcode'], PARAM_SAFEDIR);
+        if ($cleanedlang !== $this->other['langcode']) {
+            throw new \coding_exception('The \'langcode\' value must be set to a valid language code');
+        }
+    }
+}
diff --git a/admin/tool/langimport/classes/event/langpack_removed.php b/admin/tool/langimport/classes/event/langpack_removed.php
new file mode 100644 (file)
index 0000000..82e5270
--- /dev/null
@@ -0,0 +1,115 @@
+<?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/>.
+
+/**
+ * The langimport langpack removed event.
+ *
+ * @package    tool_langimport
+ * @copyright  2014 Dan Poltawski <dan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_langimport\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_langimport langpack removed event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - string langcode: the langpage pack code.
+ * }
+ *
+ * @package    tool_langimport
+ * @copyright  2014 Dan Poltawski <dan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class langpack_removed extends \core\event\base {
+    /**
+     * Create instance of event.
+     *
+     * @param string $langcode
+     * @return langpack_updated
+     */
+    public static function event_with_langcode($langcode) {
+        $data = array(
+            'context' => \context_system::instance(),
+            'other' => array(
+                'langcode' => $langcode,
+            )
+        );
+
+        return self::create($data);
+    }
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'd';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The language pack '{$this->other['langcode']}' was removed.";
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/admin/tool/langimport/');
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('langpackremovedevent', 'tool_langimport');
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['langcode'])) {
+            throw new \coding_exception('The \'langcode\' value must be set');
+        }
+
+        // We can't use PARAM_LANG here as it queries installed strings.
+        $cleanedlang = clean_param($this->other['langcode'], PARAM_SAFEDIR);
+        if ($cleanedlang !== $this->other['langcode']) {
+            throw new \coding_exception('The \'langcode\' value must be set to a valid language code');
+        }
+    }
+}
diff --git a/admin/tool/langimport/classes/event/langpack_updated.php b/admin/tool/langimport/classes/event/langpack_updated.php
new file mode 100644 (file)
index 0000000..4f0528f
--- /dev/null
@@ -0,0 +1,114 @@
+<?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/>.
+
+/**
+ * The langimport langpack updated event.
+ *
+ * @package    tool_langimport
+ * @copyright  2014 Dan Poltawski <dan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_langimport\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_langimport langpack updated event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - string langcode: the langpage pack code.
+ * }
+ *
+ * @package    tool_langimport
+ * @copyright  2014 Dan Poltawski <dan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class langpack_updated extends \core\event\base {
+    /**
+     * Create instance of event.
+     *
+     * @param string $langcode
+     * @return langpack_updated
+     */
+    public static function event_with_langcode($langcode) {
+        $data = array(
+            'context' => \context_system::instance(),
+            'other' => array(
+                'langcode' => $langcode,
+            )
+        );
+
+        return self::create($data);
+    }
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The language pack '{$this->other['langcode']}' was updated.";
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('langpackupdatedevent', 'tool_langimport');
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/admin/tool/langimport/');
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['langcode'])) {
+            throw new \coding_exception('The \'langcode\' value must be set');
+        }
+
+        $cleanedlang = clean_param($this->other['langcode'], PARAM_LANG);
+        if ($cleanedlang !== $this->other['langcode']) {
+            throw new \coding_exception('The \'langcode\' value must be set to a valid language code');
+        }
+    }
+}
diff --git a/admin/tool/langimport/classes/task/update_langpacks_task.php b/admin/tool/langimport/classes/task/update_langpacks_task.php
new file mode 100644 (file)
index 0000000..68b9e32
--- /dev/null
@@ -0,0 +1,63 @@
+<?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/>.
+
+/**
+ * A scheduled task for updating langpacks.
+ *
+ * @package    tool_langimport
+ * @copyright  2014 Dan Poltawski <dan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_langimport\task;
+
+/**
+ * A scheduled task for updating langpacks.
+ *
+ * @package    tool_langimport
+ * @copyright  2014 Dan Poltawski <dan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class update_langpacks_task extends \core\task\scheduled_task {
+
+    /**
+     * Get a descriptive name for this task (shown to admins).
+     *
+     * @return string
+     */
+    public function get_name() {
+        return get_string('updatelangs', 'tool_langimport');
+    }
+
+    /**
+     * Run langpack update
+     */
+    public function execute() {
+        $controller = new \tool_langimport\controller();
+        if ($controller->update_all_installed_languages()) {
+            foreach ($controller->info as $message) {
+                mtrace($message);
+            }
+            return true;
+        } else {
+            foreach ($controller->errors as $message) {
+                mtrace($message);
+            }
+            return false;
+        }
+
+    }
+
+}
diff --git a/admin/tool/langimport/db/tasks.php b/admin/tool/langimport/db/tasks.php
new file mode 100644 (file)
index 0000000..bd79f1b
--- /dev/null
@@ -0,0 +1,38 @@
+<?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/>.
+
+/**
+ * Definition of langimport tasks
+ *
+ * @package   tool_langimport
+ * @category  task
+ * @copyright 2014 Dan Poltawski <dan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$tasks = array(
+    array(
+        'classname' => 'tool_langimport\task\update_langpacks_task',
+        'blocking' => 0,
+        'minute' => 'R',
+        'hour' => '4',
+        'day' => '*',
+        'month' => '*',
+        'dayofweek' => '*'
+    )
+);
index 01d7a5d..b49dbd3 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-require(dirname(dirname(dirname(dirname(__FILE__)))) . '/config.php');
+require(__DIR__.'/../../../config.php');
 require_once($CFG->libdir.'/adminlib.php');
-require_once($CFG->libdir.'/filelib.php');
-require_once($CFG->libdir.'/componentlib.class.php');
 
 admin_externalpage_setup('toollangimport');
 
@@ -65,37 +63,17 @@ define('UPDATE_ALL_LANG', 5);
 
 get_string_manager()->reset_caches();
 
-$notice_ok    = array();
-$notice_error = array();
+$controller = new tool_langimport\controller();
 
 if (($mode == INSTALLATION_OF_SELECTED_LANG) and confirm_sesskey() and !empty($pack)) {
     core_php_time_limit::raise();
-    make_temp_directory('');
-    make_upload_directory('lang');
-
-    $installer = new lang_installer($pack);
-    $results = $installer->run();
-    foreach ($results as $langcode => $langstatus) {
-        switch ($langstatus) {
-        case lang_installer::RESULT_DOWNLOADERROR:
-            $a       = new stdClass();
-            $a->url  = $installer->lang_pack_url($langcode);
-            $a->dest = $CFG->dataroot.'/lang';
-            print_error('remotedownloaderror', 'error', 'index.php', $a);
-            break;
-        case lang_installer::RESULT_INSTALLED:
-            $notice_ok[] = get_string('langpackinstalled', 'tool_langimport', $langcode);
-            break;
-        case lang_installer::RESULT_UPTODATE:
-            $notice_ok[] = get_string('langpackuptodate', 'tool_langimport', $langcode);
-            break;
-        }
-    }
+    $controller->install_languagepacks($pack);
 }
 
 if ($mode == DELETION_OF_SELECTED_LANG and !empty($uninstalllang)) {
     if ($uninstalllang == 'en') {
-        $notice_error[] = 'English language pack can not be uninstalled';
+        // TODO.
+        $controller->errors[] = 'English language pack can not be uninstalled';
 
     } else if (!$confirm and confirm_sesskey()) {
         echo $OUTPUT->header();
@@ -106,120 +84,13 @@ if ($mode == DELETION_OF_SELECTED_LANG and !empty($uninstalllang)) {
         die;
 
     } else if (confirm_sesskey()) {
-        $dest1 = $CFG->dataroot.'/lang/'.$uninstalllang;
-        $dest2 = $CFG->dirroot.'/lang/'.$uninstalllang;
-        $rm1 = false;
-        $rm2 = false;
-        if (file_exists($dest1)){
-            $rm1 = remove_dir($dest1);
-        }
-        if (file_exists($dest2)){
-            $rm2 = remove_dir($dest2);
-        }
-        if ($rm1 or $rm2) {
-            $notice_ok[] = get_string('langpackremoved', 'tool_langimport');
-        } else {    //nothing deleted, possibly due to permission error
-            $notice_error[] = 'An error has occurred, language pack is not completely uninstalled, please check file permissions';
-        }
+        $controller->uninstall_language($uninstalllang);
     }
 }
 
 if ($mode == UPDATE_ALL_LANG) {
     core_php_time_limit::raise();
-
-    $installer = new lang_installer();
-
-    if (!$availablelangs = $installer->get_remote_list_of_languages()) {
-        print_error('cannotdownloadlanguageupdatelist', 'error');
-    }
-    $md5array = array();    // (string)langcode => (string)md5
-    foreach ($availablelangs as $alang) {
-        $md5array[$alang[0]] = $alang[1];
-    }
-
-    // filter out unofficial packs
-    $currentlangs = array_keys(get_string_manager()->get_list_of_translations(true));
-    $updateablelangs = array();
-    foreach ($currentlangs as $clang) {
-        if (!array_key_exists($clang, $md5array)) {
-            $notice_ok[] = get_string('langpackupdateskipped', 'tool_langimport', $clang);
-            continue;
-        }
-        $dest1 = $CFG->dataroot.'/lang/'.$clang;
-        $dest2 = $CFG->dirroot.'/lang/'.$clang;
-
-        if (file_exists($dest1.'/langconfig.php') || file_exists($dest2.'/langconfig.php')){
-            $updateablelangs[] = $clang;
-        }
-    }
-
-    // then filter out packs that have the same md5 key
-    $neededlangs = array();   // all the packs that needs updating
-    foreach ($updateablelangs as $ulang) {
-        if (!is_installed_lang($ulang, $md5array[$ulang])) {
-            $neededlangs[] = $ulang;
-        }
-    }
-
-    make_temp_directory('');
-    make_upload_directory('lang');
-
-    // clean-up currently installed versions of the packs
-    foreach ($neededlangs as $packindex => $pack) {
-        if ($pack == 'en') {
-            continue;
-        }
-
-        // delete old directories
-        $dest1 = $CFG->dataroot.'/lang/'.$pack;
-        $dest2 = $CFG->dirroot.'/lang/'.$pack;
-        $rm1 = false;
-        $rm2 = false;
-        if (file_exists($dest1)) {
-            if (!remove_dir($dest1)) {
-                $notice_error[] = 'Could not delete old directory '.$dest1.', update of '.$pack.' failed, please check permissions.';
-                unset($neededlangs[$packindex]);
-                continue;
-            }
-        }
-        if (file_exists($dest2)) {
-            if (!remove_dir($dest2)) {
-                $notice_error[] = 'Could not delete old directory '.$dest2.', update of '.$pack.' failed, please check permissions.';
-                unset($neededlangs[$packindex]);
-                continue;
-            }
-        }
-    }
-
-    // install all needed language packs
-    $installer->set_queue($neededlangs);
-    $results = $installer->run();
-    $updated = false;    // any packs updated?
-    foreach ($results as $langcode => $langstatus) {
-        switch ($langstatus) {
-        case lang_installer::RESULT_DOWNLOADERROR:
-            $a       = new stdClass();
-            $a->url  = $installer->lang_pack_url($langcode);
-            $a->dest = $CFG->dataroot.'/lang';
-            print_error('remotedownloaderror', 'error', 'index.php', $a);
-            break;
-        case lang_installer::RESULT_INSTALLED:
-            $updated = true;
-            $notice_ok[] = get_string('langpackinstalled', 'tool_langimport', $langcode);
-            break;
-        case lang_installer::RESULT_UPTODATE:
-            $notice_ok[] = get_string('langpackuptodate', 'tool_langimport', $langcode);
-            break;
-        }
-    }
-
-    if ($updated) {
-        $notice_ok[] = get_string('langupdatecomplete', 'tool_langimport');
-    } else {
-        $notice_ok[] = get_string('nolangupdateneeded', 'tool_langimport');
-    }
-
-    unset($installer);
+    $controller->update_all_installed_languages();
 }
 get_string_manager()->reset_caches();
 
@@ -239,9 +110,7 @@ foreach ($installedlangs as $installedlang => $unused) {
     }
 }
 
-$installer = new lang_installer();
-
-if ($availablelangs = $installer->get_remote_list_of_languages()) {
+if ($availablelangs = $controller->availablelangs) {
     $remote = true;
 } else {
     $remote = false;
@@ -251,18 +120,18 @@ if ($availablelangs = $installer->get_remote_list_of_languages()) {
     echo $OUTPUT->box_end();
 }
 
-if ($notice_ok) {
-    $info = implode('<br />', $notice_ok);
+if ($controller->info) {
+    $info = implode('<br />', $controller->info);
     echo $OUTPUT->notification($info, 'notifysuccess');
 }
 
-if ($notice_error) {
-    $info = implode('<br />', $notice_error);
+if ($controller->errors) {
+    $info = implode('<br />', $controller->errors);
     echo $OUTPUT->notification($info, 'notifyproblem');
 }
 
 if ($missingparents) {
-    foreach ($missingparents as $l=>$parent) {
+    foreach ($missingparents as $l => $parent) {
         $a = new stdClass();
         $a->lang   = $installedlangs[$l];
         $a->parent = $parent;
@@ -306,7 +175,7 @@ echo html_writer::end_tag('td');
 // list of available languages
 $options = array();
 foreach ($availablelangs as $alang) {
-    if (!empty($alang[0]) and trim($alang[0]) !== 'en' and !is_installed_lang($alang[0], $alang[1])) {
+    if (!empty($alang[0]) and trim($alang[0]) !== 'en' and !$controller->is_installed_lang($alang[0], $alang[1])) {
         $options[$alang[0]] = $alang[2].' ('.$alang[0].')';
     }
 }
@@ -331,23 +200,3 @@ echo html_writer::end_tag('table');
 echo $OUTPUT->box_end();
 echo $OUTPUT->footer();
 die();
-
-////////////////////////////////////////////////////////////////////////////////
-// Local functions /////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////////////
-
-/**
- * checks the md5 of the zip file, grabbed from download.moodle.org,
- * against the md5 of the local language file from last update
- * @param string $lang
- * @param string $md5check
- * @return bool
- */
-function is_installed_lang($lang, $md5check) {
-    global $CFG;
-    $md5file = $CFG->dataroot.'/lang/'.$lang.'/'.$lang.'.md5';
-    if (file_exists($md5file)){
-        return (file_get_contents($md5file) == $md5check);
-    }
-    return false;
-}
index d7534fb..fba12f9 100644 (file)
@@ -27,10 +27,14 @@ $string['install'] = 'Install selected language pack(s)';
 $string['installedlangs'] = 'Installed language packs';
 $string['langimport'] = 'Language import utility';
 $string['langimportdisabled'] = 'Language import feature has been disabled. You have to update your language packs manually at the file-system level. Do not forget to purge string caches after you do so.';
-$string['langpackinstalled'] = 'Language pack {$a} was successfully installed';
-$string['langpackremoved'] = 'Language pack was uninstalled';
-$string['langpackupdateskipped'] = 'Update of {$a} language pack skipped';
-$string['langpackuptodate'] = 'Language pack {$a} is up-to-date';
+$string['langpackinstalled'] = 'Language pack \'{$a}\' was successfully installed';
+$string['langpackinstalledevent'] = 'Language pack installed';
+$string['langpackremoved'] = 'Language pack \'{$a}\' was uninstalled';
+$string['langpackremovedevent'] = 'Language pack uninstalled';
+$string['langpackupdateskipped'] = 'Update of \'{$a}\' language pack skipped';
+$string['langpackuptodate'] = 'Language pack \'{$a}\' is up-to-date';
+$string['langpackupdated'] = 'Language pack \'{$a}\' was successfully updated';
+$string['langpackupdatedevent'] = 'Language pack updated';
 $string['langupdatecomplete'] = 'Language pack update completed';
 $string['missingcfglangotherroot'] = 'Missing configuration value $CFG->langotherroot';
 $string['missinglangparent'] = 'Missing parent language <em>{$a->parent}</em> of <em>{$a->lang}</em>.';
diff --git a/admin/tool/langimport/tests/behat/behat_tool_langimport.php b/admin/tool/langimport/tests/behat/behat_tool_langimport.php
new file mode 100644 (file)
index 0000000..f2b5729
--- /dev/null
@@ -0,0 +1,81 @@
+<?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/>.
+
+/**
+ * Behat steps definitions for Language import tool
+ *
+ * @package   tool_langimport
+ * @category  test
+ * @copyright 2014 Dan Poltawski <dan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
+
+require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
+
+use Moodle\BehatExtension\Exception\SkippedException;
+
+/**
+ * Steps definitions related with the Language import tool
+ *
+ * @package   tool_langimport
+ * @category  test
+ * @copyright 2014 Dan Poltawski <dan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_tool_langimport extends behat_base {
+
+    /**
+     * This step looks to see if the remote language import tests should be run (indicated by
+     * setting TOOL_LANGIMPORT_REMOTE_TESTS in config.php.
+     *
+     * @Given /^remote langimport tests are enabled$/
+     */
+    public function remote_langimport_tests_are_enabled() {
+        if (!defined('TOOL_LANGIMPORT_REMOTE_TESTS')) {
+            throw new SkippedException('To run the remote langimport tests you must '.
+                'define TOOL_LANGIMPORT_REMOTE_TESTS in config.php');
+        }
+    }
+
+    /**
+     * Downloads a langpack and fakes it being outdated
+     *
+     * @param string $langcode The language code (e.g. en)
+     * @Given /^outdated langpack \'([^\']*)\' is installed$/
+     */
+    public function outdated_langpack_is_installed($langcode) {
+        global $CFG;
+        require_once($CFG->libdir.'/componentlib.class.php');
+
+        // Download the langpack.
+        $dir = make_upload_directory('lang');
+        $installer = new lang_installer($langcode);
+        $result = $installer->run();
+
+        if ($result[$langcode] !== lang_installer::RESULT_INSTALLED) {
+            throw new coding_exception("Failed to install langpack '$langcode'");
+        }
+
+        $path = "$dir/$langcode/$langcode.md5";
+
+        if (!file_exists($path)) {
+            throw new coding_exception("Failed to find '$langcode' checksum");
+        }
+        file_put_contents($path, '000000');
+    }
+}
diff --git a/admin/tool/langimport/tests/behat/manage_langpacks.feature b/admin/tool/langimport/tests/behat/manage_langpacks.feature
new file mode 100644 (file)
index 0000000..ff21c8e
--- /dev/null
@@ -0,0 +1,59 @@
+@tool @tool_langimport
+Feature: Manage language packs
+  In order to support different languages
+  As an administrator
+  I need to be able to add, update and remove language packs
+
+
+  Background:
+    Given remote langimport tests are enabled
+
+  # The pirate language pack is used for testing because its small to download.
+
+  Scenario: Install language pack
+    Given I log in as "admin"
+    And I navigate to "Language packs" node in "Site administration > Language"
+    When I set the field "Available language packs" to "English - Pirate (en_ar)"
+    And I press "Install selected language pack(s)"
+    Then I should see "Language pack 'en_ar' was successfully installed"
+    And the "Installed language packs" select box should contain "English - Pirate (en_ar)"
+    And I navigate to "Live logs" node in "Site administration > Reports"
+    And I should see "The language pack 'en_ar' was installed."
+    And I log out
+
+  Scenario: Update language pack
+    Given outdated langpack 'en_ar' is installed
+    And I log in as "admin"
+    And I navigate to "Language packs" node in "Site administration > Language"
+    When I press "Update all installed language packs"
+    Then I should see "Language pack 'en_ar' was successfully updated"
+    And I should see "Language pack update completed"
+    And I navigate to "Live logs" node in "Site administration > Reports"
+    And I should see "The language pack 'en_ar' was updated."
+    And I log out
+
+  Scenario: Try to uninstall language pack
+    Given I log in as "admin"
+    And I navigate to "Language packs" node in "Site administration > Language"
+    And I set the field "Available language packs" to "English - Pirate (en_ar)"
+    And I press "Install selected language pack(s)"
+    When I set the field "Installed language packs" to "English - Pirate (en_ar)"
+    And I press "Uninstall selected language pack"
+    And I press "Continue"
+    Then I should see "Language pack 'en_ar' was uninstalled"
+    And the "Installed language packs" select box should not contain "English - Pirate (en_ar)"
+    And the "Available language packs" select box should contain "English - Pirate (en_ar)"
+    And I navigate to "Live logs" node in "Site administration > Reports"
+    And I should see "The language pack 'en_ar' was removed."
+    And I should see "Language pack uninstalled"
+    And I log out
+
+  Scenario: Try to uninstall English language pack
+    Given I log in as "admin"
+    And I navigate to "Language packs" node in "Site administration > Language"
+    When I set the field "Installed language packs" to "English (en)"
+    And I press "Uninstall selected language pack"
+    Then I should see "English language pack can not be uninstalled"
+    And I navigate to "Live logs" node in "Site administration > Reports"
+    And I should not see "Language pack uninstalled"
+    And I log out
diff --git a/admin/tool/langimport/tests/events_test.php b/admin/tool/langimport/tests/events_test.php
new file mode 100644 (file)
index 0000000..0a75b34
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Tests for langimport events.
+ *
+ * @package    tool_langimport
+ * @copyright  2014 Dan Poltawski
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Test class for langimport events.
+ *
+ * @package    tool_langimport
+ * @copyright  2014 Dan Poltawski
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
+ */
+class tool_langimport_events_testcase extends advanced_testcase {
+
+    /**
+     * Setup testcase.
+     */
+    public function setUp() {
+        $this->setAdminUser();
+        $this->resetAfterTest();
+    }
+
+    public function test_langpack_updated() {
+        global $CFG;
+
+        $event = \tool_langimport\event\langpack_updated::event_with_langcode($CFG->lang);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        $this->assertInstanceOf('\tool_langimport\event\langpack_updated', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+    }
+
+    public function test_langpack_updated_validation() {
+        $this->setExpectedException('coding_exception', 'The \'langcode\' value must be set to a valid language code');
+
+        \tool_langimport\event\langpack_updated::event_with_langcode('broken langcode');
+    }
+
+    public function test_langpack_installed() {
+        $event = \tool_langimport\event\langpack_imported::event_with_langcode('fr');
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        $this->assertInstanceOf('\tool_langimport\event\langpack_imported', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+    }
+
+    public function test_langpack_installed_validation() {
+        $this->setExpectedException('coding_exception', 'The \'langcode\' value must be set to a valid language code');
+
+        \tool_langimport\event\langpack_imported::event_with_langcode('broken langcode');
+    }
+
+    public function test_langpack_removed() {
+        $event = \tool_langimport\event\langpack_removed::event_with_langcode('fr');
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        $this->assertInstanceOf('\tool_langimport\event\langpack_removed', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+    }
+
+    public function test_langpack_removed_validation() {
+        $this->setExpectedException('coding_exception', 'The \'langcode\' value must be set to a valid language code');
+
+        \tool_langimport\event\langpack_removed::event_with_langcode('broken langcode');
+    }
+}
index 398cfc3..7050367 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2014051200; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2014092801; // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2014050800; // Requires this Moodle version
 $plugin->component = 'tool_langimport'; // Full name of the plugin (used for diagnostics)
diff --git a/admin/tool/messageinbound/classes/edit_handler_form.php b/admin/tool/messageinbound/classes/edit_handler_form.php
new file mode 100644 (file)
index 0000000..bcb6af5
--- /dev/null
@@ -0,0 +1,106 @@
+<?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/>.
+
+/**
+ * Form to edit handlers.
+ *
+ * @package    tool_messageinbound
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/formslib.php');
+
+/**
+ * Form to edit handlers.
+ *
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_messageinbound_edit_handler_form extends moodleform {
+    public function definition() {
+        $mform = $this->_form;
+
+        $handler = $this->_customdata['handler'];
+
+        // Set up the options for formatting text for descriptions, etc.
+        $formatoptions = new stdClass();
+        $formatoptions->trusted = false;
+        $formatoptions->noclean = false;
+        $formatoptions->smiley = false;
+        $formatoptions->filter = false;
+        $formatoptions->para = true;
+        $formatoptions->newlines = false;
+        $formatoptions->overflowdiv = true;
+
+        // General information about the handler.
+        $mform->addElement('header', 'general', get_string('general'));
+        $mform->addElement('static', 'name', get_string('name', 'tool_messageinbound'),
+            $handler->name);
+        $mform->addElement('static', 'classname', get_string('classname', 'tool_messageinbound'));
+
+        $description = format_text($handler->description, FORMAT_MARKDOWN, $formatoptions);
+
+        $mform->addElement('static', 'description', get_string('description', 'tool_messageinbound'),
+            $description);
+
+        // Items which can be configured.
+        $mform->addElement('header', 'configuration', get_string('configuration'));
+
+        $options = array(
+            HOURSECS => get_string('onehour', 'tool_messageinbound'),
+            DAYSECS => get_string('oneday', 'tool_messageinbound'),
+            WEEKSECS => get_string('oneweek', 'tool_messageinbound'),
+            YEARSECS => get_string('oneyear', 'tool_messageinbound'),
+            '' => get_string('noexpiry', 'tool_messageinbound'),
+        );
+        $mform->addElement('select', 'defaultexpiration', get_string('defaultexpiration', 'tool_messageinbound'), $options);
+        $mform->addHelpButton('defaultexpiration', 'defaultexpiration', 'tool_messageinbound');
+
+        if ($handler->can_change_validateaddress()) {
+            $mform->addElement('checkbox', 'validateaddress', get_string('requirevalidation', 'tool_messageinbound'));
+            $mform->addHelpButton('validateaddress', 'validateaddress', 'tool_messageinbound');
+        } else {
+            if ($handler->validateaddress) {
+                $text = get_string('yes');
+            } else {
+                $text = get_string('no');
+            }
+            $mform->addElement('static', 'validateaddress_fake', get_string('requirevalidation', 'tool_messageinbound'), $text);
+            $mform->addElement('hidden', 'validateaddress');
+            $mform->addHelpButton('validateaddress_fake', 'fixedvalidateaddress', 'tool_messageinbound');
+            $mform->setType('validateaddress', PARAM_INT);
+        }
+
+        if ($handler->can_change_enabled()) {
+            $mform->addElement('checkbox', 'enabled', get_string('enabled', 'tool_messageinbound'));
+        } else {
+            if ($handler->enabled) {
+                $text = get_string('yes');
+            } else {
+                $text = get_string('no');
+            }
+            $mform->addElement('static', 'enabled_fake', get_string('enabled', 'tool_messageinbound'), $text);
+            $mform->addHelpButton('enabled', 'fixedenabled', 'tool_messageinbound');
+            $mform->addElement('hidden', 'enabled');
+            $mform->setType('enabled', PARAM_INT);
+        }
+
+        $this->add_action_buttons(true, get_string('savechanges'));
+    }
+}
diff --git a/admin/tool/messageinbound/classes/manager.php b/admin/tool/messageinbound/classes/manager.php
new file mode 100644 (file)
index 0000000..e9f0108
--- /dev/null
@@ -0,0 +1,962 @@
+<?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/>.
+
+/**
+ * The Mail Pickup Manager.
+ *
+ * @package    tool_messageinbound
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_messageinbound;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Mail Pickup Manager.
+ *
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class manager {
+
+    /**
+     * @var string The main mailbox to check.
+     */
+    const MAILBOX = 'INBOX';
+
+    /**
+     * @var string The mailbox to store messages in when they are awaiting confirmation.
+     */
+    const CONFIRMATIONFOLDER = 'tobeconfirmed';
+
+    /**
+     * @var string The flag for seen/read messages.
+     */
+    const MESSAGE_SEEN = '\seen';
+
+    /**
+     * @var string The flag for flagged messages.
+     */
+    const MESSAGE_FLAGGED = '\flagged';
+
+    /**
+     * @var string The flag for deleted messages.
+     */
+    const MESSAGE_DELETED = '\deleted';
+
+    /**
+     * @var Horde_Imap_Client_Socket A reference to the IMAP client.
+     */
+    protected $client = null;
+
+    /**
+     * @var \core\message\inbound\address_manager A reference to the Inbound Message Address Manager instance.
+     */
+    protected $addressmanager = null;
+
+    /**
+     * @var stdClass The data for the current message being processed.
+     */
+    protected $currentmessagedata = null;
+
+    /**
+     * Retrieve the connection to the IMAP client.
+     *
+     * @return bool Whether a connection was successfully established.
+     */
+    protected function get_imap_client() {
+        global $CFG;
+
+        if (!\core\message\inbound\manager::is_enabled()) {
+            // E-mail processing not set up.
+            mtrace("Inbound Message not fully configured - exiting early.");
+            return false;
+        }
+
+        mtrace("Connecting to {$CFG->messageinbound_host} as {$CFG->messageinbound_hostuser}...");
+
+        $configuration = array(
+            'username' => $CFG->messageinbound_hostuser,
+            'password' => $CFG->messageinbound_hostpass,
+            'hostspec' => $CFG->messageinbound_host,
+            'secure'   => $CFG->messageinbound_hostssl,
+        );
+
+        $this->client = new \Horde_Imap_Client_Socket($configuration);
+
+        try {
+            $this->client->login();
+            mtrace("Connection established.");
+            return true;
+
+        } catch (\Horde_Imap_Client_Exception $e) {
+            $message = $e->getMessage();
+            mtrace("Unable to connect to IMAP server. Failed with '{$message}'");
+
+            return false;
+        }
+    }
+
+    /**
+     * Shutdown and close the connection to the IMAP client.
+     */
+    protected function close_connection() {
+        if ($this->client) {
+            $this->client->close();
+        }
+        $this->client = null;
+    }
+
+    /**
+     * Get the current mailbox information.
+     *
+     * @return \Horde_Imap_Client_Mailbox
+     */
+    protected function get_mailbox() {
+        // Get the current mailbox.
+        $mailbox = $this->client->currentMailbox();
+
+        if (isset($mailbox['mailbox'])) {
+            return $mailbox['mailbox'];
+        } else {
+            throw new \core\message\inbound\processing_failed_exception('couldnotopenmailbox', 'tool_messageinbound');
+        }
+    }
+
+    /**
+     * Execute the main Inbound Message pickup task.
+     */
+    public function pickup_messages() {
+        if (!$this->get_imap_client()) {
+            return false;
+        }
+
+        // Restrict results to messages which are unseen, and have not been flagged.
+        $search = new \Horde_Imap_Client_Search_Query();
+        $search->flag(self::MESSAGE_SEEN, false);
+        $search->flag(self::MESSAGE_FLAGGED, false);
+        mtrace("Searching for Unseen, Unflagged email in the folder '" . self::MAILBOX . "'");
+        $results = $this->client->search(self::MAILBOX, $search);
+
+        // We require the envelope data and structure of each message.
+        $query = new \Horde_Imap_Client_Fetch_Query();
+        $query->envelope();
+        $query->structure();
+
+        // Retrieve the message id.
+        $messages = $this->client->fetch(self::MAILBOX, $query, array('ids' => $results['match']));
+
+        mtrace("Found " . $messages->count() . " messages to parse. Parsing...");
+        $this->addressmanager = new \core\message\inbound\address_manager();
+        foreach ($messages as $message) {
+            $this->process_message($message);
+        }
+
+        // Close the client connection.
+        $this->close_connection();
+
+        return true;
+    }
+
+    /**
+     * Process a message received and validated by the Inbound Message processor.
+     *
+     * @param stdClass $maildata The data retrieved from the database for the current record.
+     * @return bool Whether the message was successfully processed.
+     */
+    public function process_existing_message(\stdClass $maildata) {
+        // Grab the new IMAP client.
+        if (!$this->get_imap_client()) {
+            return false;
+        }
+
+        // Build the search.
+        $search = new \Horde_Imap_Client_Search_Query();
+        // When dealing with Inbound Message messages, we mark them as flagged and seen. Restrict the search to those criterion.
+        $search->flag(self::MESSAGE_SEEN, true);
+        $search->flag(self::MESSAGE_FLAGGED, true);
+        mtrace("Searching for a Seen, Flagged message in the folder '" . self::CONFIRMATIONFOLDER . "'");
+
+        // Match the message ID.
+        $search->headerText('message-id', $maildata->messageid);
+        $search->headerText('to', $maildata->address);
+
+        $results = $this->client->search(self::CONFIRMATIONFOLDER, $search);
+
+        // Build the base query.
+        $query = new \Horde_Imap_Client_Fetch_Query();
+        $query->envelope();
+        $query->structure();
+
+
+        // Fetch the first message from the client.
+        $messages = $this->client->fetch(self::CONFIRMATIONFOLDER, $query, array('ids' => $results['match']));
+        $this->addressmanager = new \core\message\inbound\address_manager();
+        if ($message = $messages->first()) {
+            mtrace("--> Found the message. Passing back to the pickup system.");
+
+            // Process the message.
+            $this->process_message($message, true, true);
+
+            // Close the client connection.
+            $this->close_connection();
+
+            mtrace("============================================================================");
+            return true;
+        } else {
+            // Close the client connection.
+            $this->close_connection();
+
+            mtrace("============================================================================");
+            throw new \core\message\inbound\processing_failed_exception('oldmessagenotfound', 'tool_messageinbound');
+        }
+    }
+
+    /**
+     * Tidy up old messages in the confirmation folder.
+     *
+     * @return bool Whether tidying occurred successfully.
+     */
+    public function tidy_old_messages() {
+        // Grab the new IMAP client.
+        if (!$this->get_imap_client()) {
+            return false;
+        }
+
+        // Open the mailbox.
+        mtrace("Searching for messages older than 24 hours in the '" .
+                self::CONFIRMATIONFOLDER . "' folder.");
+        $this->client->openMailbox(self::CONFIRMATIONFOLDER);
+
+        $mailbox = $this->get_mailbox();
+
+        // Build the search.
+        $search = new \Horde_Imap_Client_Search_Query();
+
+        // Delete messages older than 24 hours old.
+        $search->intervalSearch(DAYSECS, \Horde_Imap_Client_Search_Query::INTERVAL_OLDER);
+
+        $results = $this->client->search($mailbox, $search);
+
+        // Build the base query.
+        $query = new \Horde_Imap_Client_Fetch_Query();
+        $query->envelope();
+
+        // Retrieve the messages and mark them for removal.
+        $messages = $this->client->fetch($mailbox, $query, array('ids' => $results['match']));
+        mtrace("Found " . $messages->count() . " messages for removal.");
+        foreach ($messages as $message) {
+            $this->add_flag_to_message($message->getUid(), self::MESSAGE_DELETED);
+        }
+
+        mtrace("Finished removing messages.");
+        $this->close_connection();
+
+        return true;
+    }
+
+    /**
+     * Process a message and pass it through the Inbound Message handling systems.
+     *
+     * @param Horde_Imap_Client_Data_Fetch $message The message to process
+     * @param bool $viewreadmessages Whether to also look at messages which have been marked as read
+     * @param bool $skipsenderverification Whether to skip the sender verificiation stage
+     */
+    public function process_message(
+            \Horde_Imap_Client_Data_Fetch $message,
+            $viewreadmessages = false,
+            $skipsenderverification = false) {
+        global $USER;
+
+        // We use the Client IDs several times - store them here.
+        $messageid = new \Horde_Imap_Client_Ids($message->getUid());
+
+        mtrace("- Parsing message " . $messageid);
+
+        // First flag this message to prevent another running hitting this message while we look at the headers.
+        $this->add_flag_to_message($messageid, self::MESSAGE_FLAGGED);
+
+        // Record the user that this script is currently being run as.  This is important when re-processing existing
+        // messages, as cron_setup_user is called multiple times.
+        $originaluser = $USER;
+
+        $envelope = $message->getEnvelope();
+        $recipients = $envelope->to->bare_addresses;
+        foreach ($recipients as $recipient) {
+            if (!\core\message\inbound\address_manager::is_correct_format($recipient)) {
+                // Message did not contain a subaddress.
+                mtrace("- Recipient '{$recipient}' did not match Inbound Message headers.");
+                continue;
+            }
+
+            // Message contained a match.
+            $senders = $message->getEnvelope()->from->bare_addresses;
+            if (count($senders) !== 1) {
+                mtrace("- Received multiple senders. Only the first sender will be used.");
+            }
+            $sender = array_shift($senders);
+
+            mtrace("-- Subject:\t"      . $envelope->subject);
+            mtrace("-- From:\t"         . $sender);
+            mtrace("-- Recipient:\t"    . $recipient);
+
+            // Grab messagedata including flags.
+            $query = new \Horde_Imap_Client_Fetch_Query();
+            $query->structure();
+            $messagedata = $this->client->fetch($this->get_mailbox(), $query, array(
+                'ids' => $messageid,
+            ))->first();
+
+            if (!$viewreadmessages && $this->message_has_flag($messageid, self::MESSAGE_SEEN)) {
+                // Something else has already seen this message. Skip it now.
+                mtrace("-- Skipping the message - it has been marked as seen - perhaps by another process.");
+                continue;
+            }
+
+            // Mark it as read to lock the message.
+            $this->add_flag_to_message($messageid, self::MESSAGE_SEEN);
+
+            // Now pass it through the Inbound Message processor.
+            $status = $this->addressmanager->process_envelope($recipient, $sender);
+
+            if (($status & ~ \core\message\inbound\address_manager::VALIDATION_DISABLED_HANDLER) !== $status) {
+                // The handler is disabled.
+                mtrace("-- Skipped message - Handler is disabled. Fail code {$status}");
+                // In order to handle the user error, we need more information about the message being failed.
+                $this->process_message_data($envelope, $messagedata, $messageid);
+                $this->inform_user_of_error(get_string('handlerdisabled', 'tool_messageinbound', $this->currentmessagedata));
+                return;
+            }
+
+            // Check the validation status early. No point processing garbage messages, but we do need to process it
+            // for some validation failure types.
+            if (!$this->passes_key_validation($status, $messageid)) {
+                // None of the above validation failures were found. Skip this message.
+                mtrace("-- Skipped message - it does not appear to relate to a Inbound Message pickup. Fail code {$status}");
+
+                // Remove the seen flag from the message as there may be multiple recipients.
+                $this->remove_flag_from_message($messageid, self::MESSAGE_SEEN);
+
+                // Skip further processing for this recipient.
+                continue;
+            }
+
+            // Process the message as the user.
+            $user = $this->addressmanager->get_data()->user;
+            mtrace("-- Processing the message as user {$user->id} ({$user->username}).");
+            cron_setup_user($user);
+
+            // Process and retrieve the message data for this message.
+            // This includes fetching the full content, as well as all headers, and attachments.
+            $this->process_message_data($envelope, $messagedata, $messageid);
+
+            // When processing validation replies, we need to skip the sender verification phase as this has been
+            // manually completed.
+            if (!$skipsenderverification && $status !== 0) {
+                // Check the validation status for failure types which require confirmation.
+                // The validation result is tested in a bitwise operation.
+                mtrace("-- Message did not meet validation but is possibly recoverable. Fail code {$status}");
+                // This is a recoverable error, but requires user input.
+
+                if ($this->handle_verification_failure($messageid, $recipient)) {
+                    mtrace("--- Original message retained on mail server and confirmation message sent to user.");
+                } else {
+                    mtrace("--- Invalid Recipient Handler - unable to save. Informing the user of the failure.");
+                    $this->inform_user_of_error(get_string('invalidrecipientfinal', 'tool_messageinbound', $this->currentmessagedata));
+                }
+
+                // Returning to normal cron user.
+                mtrace("-- Returning to the original user.");
+                cron_setup_user($originaluser);
+                return;
+            }
+
+            // Add the content and attachment data.
+            mtrace("-- Validation completed. Fetching rest of message content.");
+            $this->process_message_data_body($messagedata, $messageid);
+
+            // The message processor throws exceptions upon failure. These must be caught and notifications sent to
+            // the user here.
+            try {
+                $result = $this->send_to_handler();
+            } catch (\core\message\inbound\processing_failed_exception $e) {
+                // We know about these kinds of errors and they should result in the user being notified of the
+                // failure. Send the user a notification here.
+                $this->inform_user_of_error($e->getMessage());
+
+                // Returning to normal cron user.
+                mtrace("-- Returning to the original user.");
+                cron_setup_user($originaluser);
+                return;
+            } catch (Exception $e) {
+                // An unknown error occurred. The user is not informed, but the administrator is.
+                mtrace("-- Message processing failed. An unexpected exception was thrown. Details follow.");
+                mtrace($e->getMessage());
+
+                // Returning to normal cron user.
+                mtrace("-- Returning to the original user.");
+                cron_setup_user($originaluser);
+                return;
+            }
+
+            if ($result) {
+                // Handle message cleanup. Messages are deleted once fully processed.
+                mtrace("-- Marking the message for removal.");
+                $this->add_flag_to_message($messageid, self::MESSAGE_DELETED);
+            } else {
+                mtrace("-- The Inbound Message processor did not return a success status. Skipping message removal.");
+            }
+
+            // Returning to normal cron user.
+            mtrace("-- Returning to the original user.");
+            cron_setup_user($originaluser);
+
+            mtrace("-- Finished processing " . $message->getUid());
+
+            // Skip the outer loop too. The message has already been processed and it could be possible for there to
+            // be two recipients in the envelope which match somehow.
+            return;
+        }
+    }
+
+    /**
+     * Process a message to retrieve it's header data without body and attachemnts.
+     *
+     * @param Horde_Imap_Client_Data_Envelope $envelope The Envelope of the message
+     * @param Horde_Imap_Client_Data_Fetch $messagedata The structure and part of the message body
+     * @param string|Horde_Imap_Client_Ids $messageid The Hore message Uid
+     * @return \stdClass The current value of the messagedata
+     */
+    private function process_message_data(
+            \Horde_Imap_Client_Data_Envelope $envelope,
+            \Horde_Imap_Client_Data_Fetch $basemessagedata,
+            $messageid) {
+
+        // Get the current mailbox.
+        $mailbox = $this->get_mailbox();
+
+        // We need the structure at various points below.
+        $structure = $basemessagedata->getStructure();
+
+        // Now fetch the rest of the message content.
+        $query = new \Horde_Imap_Client_Fetch_Query();
+        $query->imapDate();
+
+        // Fetch all of the message parts too.
+        $typemap = $structure->contentTypeMap();
+        foreach ($typemap as $part => $type) {
+            // The header.
+            $query->headerText(array(
+                'id' => $part,
+            ));
+        }
+
+        $messagedata = $this->client->fetch($mailbox, $query, array('ids' => $messageid))->first();
+
+        // Store the data for this message.
+        $headers = '';
+
+        foreach ($typemap as $part => $type) {
+            // Grab all of the header data into a string.
+            $headers .= $messagedata->getHeaderText($part);
+
+            // We don't handle any of the other MIME content at this stage.
+        }
+
+        $data = new \stdClass();
+
+        // The message ID should always be in the first part.
+        $data->messageid = $messagedata->getHeaderText(0, \Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->getValue('Message-ID');
+        $data->subject = $envelope->subject;
+        $data->timestamp = $messagedata->getImapDate()->__toString();
+        $data->envelope = $envelope;
+        $data->data = $this->addressmanager->get_data();
+        $data->headers = $headers;
+
+        $this->currentmessagedata = $data;
+
+        return $this->currentmessagedata;
+    }
+
+    /**
+     * Process a message again to add body and attachment data.
+     *
+     * @param Horde_Imap_Client_Data_Envelope $envelope The Envelope of the message
+     * @param Horde_Imap_Client_Data_Fetch $basemessagedata The structure and part of the message body
+     * @param string|Horde_Imap_Client_Ids $messageid The Hore message Uid
+     * @return \stdClass The current value of the messagedata
+     */
+    private function process_message_data_body(
+            \Horde_Imap_Client_Data_Fetch $basemessagedata,
+            $messageid) {
+        global $CFG;
+
+        // Get the current mailbox.
+        $mailbox = $this->get_mailbox();
+
+        // We need the structure at various points below.
+        $structure = $basemessagedata->getStructure();
+
+        // Now fetch the rest of the message content.
+        $query = new \Horde_Imap_Client_Fetch_Query();
+        $query->fullText();
+
+        // Fetch all of the message parts too.
+        $typemap = $structure->contentTypeMap();
+        foreach ($typemap as $part => $type) {
+            // The body of the part - attempt to decode it on the server.
+            $query->bodyPart($part, array(
+                'decode' => true,
+                'peek' => true,
+            ));
+            $query->bodyPartSize($part);
+        }
+
+        $messagedata = $this->client->fetch($mailbox, $query, array('ids' => $messageid))->first();
+
+        // Store the data for this message.
+        $contentplain = '';
+        $contenthtml = '';
+        $attachments = array(
+            'inline' => array(),
+            'attachment' => array(),
+        );
+
+        $plainpartid = $structure->findBody('plain');
+        $htmlpartid = $structure->findBody('html');
+
+        foreach ($typemap as $part => $type) {
+            // Get the message data from the body part, and combine it with the structure to give a fully-formed output.
+            $stream = $messagedata->getBodyPart($part, true);
+            $partdata = $structure->getPart($part);
+            $partdata->setContents($stream, array(
+                'usestream' => true,
+            ));
+
+            if ($part === $plainpartid) {
+                $contentplain = $this->process_message_part_body($messagedata, $partdata, $part);
+
+            } else if ($part === $htmlpartid) {
+                $contenthtml = $this->process_message_part_body($messagedata, $partdata, $part);
+
+            } else if ($filename = $partdata->getName($part)) {
+                if ($attachment = $this->process_message_part_attachment($messagedata, $partdata, $part, $filename)) {
+                    // The disposition should be one of 'attachment', 'inline'.
+                    // If an empty string is provided, default to 'attachment'.
+                    $disposition = $partdata->getDisposition();
+                    $disposition = $disposition == 'inline' ? 'inline' : 'attachment';
+                    $attachments[$disposition][] = $attachment;
+                }
+            }
+
+            // We don't handle any of the other MIME content at this stage.
+        }
+
+        // The message ID should always be in the first part.
+        $this->currentmessagedata->plain = $contentplain;
+        $this->currentmessagedata->html = $contenthtml;
+        $this->currentmessagedata->attachments = $attachments;
+
+        return $this->currentmessagedata;
+    }
+
+    /**
+     * Process the messagedata and part data to extract the content of this part.
+     *
+     * @param $messagedata The structure and part of the message body
+     * @param $partdata The part data
+     * @param $part The part ID
+     * @return string
+     */
+    private function process_message_part_body($messagedata, $partdata, $part) {
+        // This is a content section for the main body.
+
+        // Get the string version of it.
+        $content = $messagedata->getBodyPart($part);
+        if (!$messagedata->getBodyPartDecode($part)) {
+            // Decode the content.
+            $partdata->setContents($content);
+            $content = $partdata->getContents();
+        }
+
+        // Convert the text from the current encoding to UTF8.
+        $content = \core_text::convert($content, $partdata->getCharset());
+
+        // Fix any invalid UTF8 characters.
+        // Note: XSS cleaning is not the responsibility of this code. It occurs immediately before display when
+        // format_text is called.
+        $content = clean_param($content, PARAM_RAW);
+
+        return $content;
+    }
+
+    /**
+     * Process a message again to add body and attachment data.
+     *
+     * @param $messagedata The structure and part of the message body
+     * @param $partdata The part data
+     * @param $filename The filename of the attachment
+     * @return \stdClass
+     */
+    private function process_message_part_attachment($messagedata, $partdata, $part, $filename) {
+        global $CFG;
+
+        // If a filename is present, assume that this part is an attachment.
+        $attachment = new \stdClass();
+        $attachment->filename       = $filename;
+        $attachment->type           = $partdata->getType();
+        $attachment->content        = $partdata->getContents();
+        $attachment->charset        = $partdata->getCharset();
+        $attachment->description    = $partdata->getDescription();
+        $attachment->contentid      = $partdata->getContentId();
+        $attachment->filesize       = $messagedata->getBodyPartSize($part);
+
+        if (empty($CFG->runclamonupload) or empty($CFG->pathtoclam)) {
+            mtrace("--> Attempting virus scan of '{$attachment->filename}'");
+
+            // Store the file on disk - it will need to be virus scanned first.
+            $itemid = rand(1, 999999999);;
+            $directory = make_temp_directory("/messageinbound/{$itemid}", false);
+            $filepath = $directory . "/" . $attachment->filename;
+            if (!$fp = fopen($filepath, "w")) {
+                // Unable to open the temporary file to write this to disk.
+                mtrace("--> Unable to save the file to disk for virus scanning. Check file permissions.");
+
+                throw new \core\message\inbound\processing_failed_exception('attachmentfilepermissionsfailed',
+                        'tool_messageinbound');
+            }
+
+            fwrite($fp, $attachment->content);
+            fclose($fp);
+
+            // Perform a virus scan now.
+            try {
+                \repository::antivir_scan_file($filepath, $attachment->filename, true);
+            } catch (moodle_exception $e) {
+                mtrace("--> A virus was found in the attachment '{$attachment->filename}'.");
+                $this->inform_attachment_virus();
+                return;
+            }
+        }
+
+        return $attachment;
+    }
+
+    /**
+     * Check whether the key provided is valid.
+     *
+     * @param $status The Message to process
+     * @param $messageid The Hore message Uid
+     * @return bool
+     */
+    private function passes_key_validation($status, $messageid) {
+        // The validation result is tested in a bitwise operation.
+        if ((
+            $status & ~ \core\message\inbound\address_manager::VALIDATION_SUCCESS
+                    & ~ \core\message\inbound\address_manager::VALIDATION_UNKNOWN_DATAKEY
+                    & ~ \core\message\inbound\address_manager::VALIDATION_EXPIRED_DATAKEY
+                    & ~ \core\message\inbound\address_manager::VALIDATION_INVALID_HASH
+                    & ~ \core\message\inbound\address_manager::VALIDATION_ADDRESS_MISMATCH) !== 0) {
+
+            // One of the above bits was found in the status - fail the validation.
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Add the specified flag to the message.
+     *
+     * @param $messageid
+     * @param string $flag The flag to add
+     */
+    private function add_flag_to_message($messageid, $flag) {
+        // Get the current mailbox.
+        $mailbox = $this->get_mailbox();
+
+        // Mark it as read to lock the message.
+        $this->client->store($mailbox, array(
+            'ids' => new \Horde_Imap_Client_Ids($messageid),
+            'add' => $flag,
+        ));
+    }
+
+    /**
+     * Remove the specified flag from the message.
+     *
+     * @param $messageid
+     * @param string $flag The flag to remove
+     */
+    private function remove_flag_from_message($messageid, $flag) {
+        // Get the current mailbox.
+        $mailbox = $this->get_mailbox();
+
+        // Mark it as read to lock the message.
+        $this->client->store($mailbox, array(
+            'ids' => $messageid,
+            'delete' => $flag,
+        ));
+    }
+
+    /**
+     * Check whether the message has the specified flag
+     *
+     * @param $messageid
+     * @param string $flag The flag to check
+     * @return bool
+     */
+    private function message_has_flag($messageid, $flag) {
+        // Get the current mailbox.
+        $mailbox = $this->get_mailbox();
+
+        // Grab messagedata including flags.
+        $query = new \Horde_Imap_Client_Fetch_Query();
+        $query->flags();
+        $query->structure();
+        $messagedata = $this->client->fetch($mailbox, $query, array(
+            'ids' => $messageid,
+        ))->first();
+        $flags = $messagedata->getFlags();
+
+        return in_array($flag, $flags);
+    }
+
+    /**
+     * Send the message to the appropriate handler.
+     *
+     */
+    private function send_to_handler() {
+        try {
+            mtrace("--> Passing to Inbound Message handler {$this->addressmanager->get_handler()->classname}");
+            if ($result = $this->addressmanager->handle_message($this->currentmessagedata)) {
+                $this->inform_user_of_success($this->currentmessagedata, $result);
+                // Request that this message be marked for deletion.
+                return true;
+            }
+
+        } catch (\core\message\inbound\processing_failed_exception $e) {
+            mtrace("-> The Inbound Message handler threw an exception. Unable to process this message. The user has been informed.");
+            mtrace("--> " . $e->getMessage());
+            // Throw the exception again, with additional data.
+            $error = new \stdClass();
+            $error->subject     = $this->currentmessagedata->envelope->subject;
+            $error->message     = $e->getMessage();
+            throw new \core\message\inbound\processing_failed_exception('messageprocessingfailed', 'tool_messageinbound', $error);
+
+        } catch (Exception $e) {
+            mtrace("-> The Inbound Message handler threw an exception. Unable to process this message. User informed.");
+            mtrace("--> " . $e->getMessage());
+            // An unknown error occurred. Still inform the user but, this time do not include the specific
+            // message information.
+            $error = new \stdClass();
+            $error->subject     = $this->currentmessagedata->envelope->subject;
+            throw new \core\message\inbound\processing_failed_exception('messageprocessingfailedunknown',
+                    'tool_messageinbound', $error);
+
+        }
+
+        // Something went wrong and the message was not handled well in the Inbound Message handler.
+        mtrace("-> The Inbound Message handler reported an error. The message may not have been processed.");
+
+        // It is the responsiblity of the handler to throw an appropriate exception if the message was not processed.
+        // Do not inform the user at this point.
+        return false;
+    }
+
+    /**
+     * Handle failure of sender verification.
+     *
+     * This will send a notification to the user identified in the Inbound Message address informing them that a message has been
+     * stored. The message includes a verification link and reply-to address which is handled by the
+     * invalid_recipient_handler.
+     *
+     * @param $recipient The message recipient
+     */
+    private function handle_verification_failure(
+            \Horde_Imap_Client_Ids $messageids,
+            $recipient) {
+        global $DB, $USER;
+
+        if (!$messageid = $this->currentmessagedata->messageid) {
+            mtrace("---> Warning: Unable to determine the Message-ID of the message.");
+            return false;
+        }
+
+        // Move the message into a new mailbox.
+        $this->client->copy(self::MAILBOX, self::CONFIRMATIONFOLDER, array(
+                'create'    => true,
+                'ids'       => $messageids,
+                'move'      => true,
+            ));
+
+        // Store the data from the failed message in the associated table.
+        $record = new \stdClass();
+        $record->messageid = $messageid;
+        $record->userid = $USER->id;
+        $record->address = $recipient;
+        $record->timecreated = time();
+        $record->id = $DB->insert_record('messageinbound_messagelist', $record);
+
+        // Setup the Inbound Message generator for the invalid recipient handler.
+        $addressmanager = new \core\message\inbound\address_manager();
+        $addressmanager->set_handler('\tool_messageinbound\message\inbound\invalid_recipient_handler');
+        $addressmanager->set_data($record->id);
+
+        $eventdata = new \stdClass();
+        $eventdata->component           = 'tool_messageinbound';
+        $eventdata->name                = 'invalidrecipienthandler';
+
+        $userfrom = clone $USER;
+        $userfrom->customheaders = array();
+        // Adding the In-Reply-To header ensures that it is seen as a reply.
+        $userfrom->customheaders[] = 'In-Reply-To: ' . $messageid;
+
+        // The message will be sent from the intended user.
+        $eventdata->userfrom            = \core_user::get_noreply_user();
+        $eventdata->userto              = $USER;
+        $eventdata->subject             = $this->get_reply_subject($this->currentmessagedata->envelope->subject);
+        $eventdata->fullmessage         = get_string('invalidrecipientdescription', 'tool_messageinbound', $this->currentmessagedata);
+        $eventdata->fullmessageformat   = FORMAT_PLAIN;
+        $eventdata->fullmessagehtml     = get_string('invalidrecipientdescriptionhtml', 'tool_messageinbound', $this->currentmessagedata);
+        $eventdata->smallmessage        = $eventdata->fullmessage;
+        $eventdata->notification        = 1;
+        $eventdata->replyto             = $addressmanager->generate($USER->id);
+
+        mtrace("--> Sending a message to the user to report an verification failure.");
+        if (!message_send($eventdata)) {
+            mtrace("---> Warning: Message could not be sent.");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Inform the identified sender of a processing error.
+     *
+     * @param string $error The error message
+     */
+    private function inform_user_of_error($error) {
+        global $USER;
+
+        // The message will be sent from the intended user.
+        $userfrom = clone $USER;
+        $userfrom->customheaders = array();
+
+        if ($messageid = $this->currentmessagedata->messageid) {
+            // Adding the In-Reply-To header ensures that it is seen as a reply and threading is maintained.
+            $userfrom->customheaders[] = 'In-Reply-To: ' . $messageid;
+        }
+
+        $messagedata = new \stdClass();
+        $messagedata->subject = $this->currentmessagedata->envelope->subject;
+        $messagedata->error = $error;
+
+        $eventdata = new \stdClass();
+        $eventdata->component           = 'tool_messageinbound';
+        $eventdata->name                = 'messageprocessingerror';
+        $eventdata->userfrom            = $userfrom;
+        $eventdata->userto              = $USER;
+        $eventdata->subject             = self::get_reply_subject($this->currentmessagedata->envelope->subject);
+        $eventdata->fullmessage         = get_string('messageprocessingerror', 'tool_messageinbound', $messagedata);
+        $eventdata->fullmessageformat   = FORMAT_PLAIN;
+        $eventdata->fullmessagehtml     = get_string('messageprocessingerrorhtml', 'tool_messageinbound', $messagedata);
+        $eventdata->smallmessage        = $eventdata->fullmessage;
+        $eventdata->notification        = 1;
+
+        if (message_send($eventdata)) {
+            mtrace("---> Notification sent to {$USER->email}.");
+        } else {
+            mtrace("---> Unable to send notification.");
+        }
+    }
+
+    /**
+     * Inform the identified sender that message processing was successful.
+     *
+     * @param stdClass $messagedata The data for the current message being processed.
+     * @param mixed $handlerresult The result returned by the handler.
+     */
+    private function inform_user_of_success(\stdClass $messagedata, $handlerresult) {
+        global $USER;
+
+        // Check whether the handler has a success notification.
+        $handler = $this->addressmanager->get_handler();
+        $message = $handler->get_success_message($messagedata, $handlerresult);
+
+        if (!$message) {
+            mtrace("---> Handler has not defined a success notification e-mail.");
+            return false;
+        }
+
+        // Wrap the message in the notification wrapper.
+        $messageparams = new \stdClass();
+        $messageparams->html    = $message->html;
+        $messageparams->plain   = $message->plain;
+        $messagepreferencesurl = new \moodle_url("/message/edit.php", array('id' => $USER->id));
+        $messageparams->messagepreferencesurl = $messagepreferencesurl->out();
+        $htmlmessage = get_string('messageprocessingsuccesshtml', 'tool_messageinbound', $messageparams);
+        $plainmessage = get_string('messageprocessingsuccess', 'tool_messageinbound', $messageparams);
+
+        // The message will be sent from the intended user.
+        $userfrom = clone $USER;
+        $userfrom->customheaders = array();
+
+        if ($messageid = $this->currentmessagedata->messageid) {
+            // Adding the In-Reply-To header ensures that it is seen as a reply and threading is maintained.
+            $userfrom->customheaders[] = 'In-Reply-To: ' . $messageid;
+        }
+
+        $messagedata = new \stdClass();
+        $messagedata->subject = $this->currentmessagedata->envelope->subject;
+
+        $eventdata = new \stdClass();
+        $eventdata->component           = 'tool_messageinbound';
+        $eventdata->name                = 'messageprocessingsuccess';
+        $eventdata->userfrom            = $userfrom;
+        $eventdata->userto              = $USER;
+        $eventdata->subject             = self::get_reply_subject($this->currentmessagedata->envelope->subject);
+        $eventdata->fullmessage         = $plainmessage;
+        $eventdata->fullmessageformat   = FORMAT_PLAIN;
+        $eventdata->fullmessagehtml     = $htmlmessage;
+        $eventdata->smallmessage        = $eventdata->fullmessage;
+        $eventdata->notification        = 1;
+
+        if (message_send($eventdata)) {
+            mtrace("---> Success notification sent to {$USER->email}.");
+        } else {
+            mtrace("---> Unable to send success notification.");
+        }
+        return true;
+    }
+
+    /**
+     * Return a formatted subject line for replies.
+     *
+     * @param $subject string The subject string
+     * @return string The formatted reply subject
+     */
+    private function get_reply_subject($subject) {
+        $prefix = get_string('replysubjectprefix', 'tool_messageinbound');
+        if (!(substr($subject, 0, strlen($prefix)) == $prefix)) {
+            $subject = $prefix . ' ' . $subject;
+        }
+
+        return $subject;
+    }
+}
diff --git a/admin/tool/messageinbound/classes/message/inbound/invalid_recipient_handler.php b/admin/tool/messageinbound/classes/message/inbound/invalid_recipient_handler.php
new file mode 100644 (file)
index 0000000..7ff3a69
--- /dev/null
@@ -0,0 +1,95 @@
+<?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/>.
+
+/**
+ * A Handler to re-process messages which previously failed sender
+ * verification.
+ *
+ * @package    task_messageinbound
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_messageinbound\message\inbound;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/repository/lib.php');
+
+/**
+ * A Handler to re-process messages which previously failed sender
+ * verification.
+ *
+ * This may happen if the user did not use their registerd e-mail address,
+ * the verification hash used had expired, or if some erroneous content was
+ * introduced into the content hash.
+ *
+ * @package    task
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class invalid_recipient_handler extends \core\message\inbound\handler {
+
+    /**
+     * Do not allow changes to the address validation setting.
+     */
+    public function allow_validateaddress_change() {
+        return false;
+    }
+
+    /**
+     * Return a description for the current handler.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return get_string('invalid_recipient_handler', 'tool_messageinbound');
+    }
+
+    /**
+     * Return a name for the current handler.
+     * This appears in the admin pages as a human-readable name.
+     *
+     * @return string
+     */
+    public function get_name() {
+        return get_string('invalid_recipient_handler_name', 'tool_messageinbound');
+    }
+
+    /**
+     * Process a message received and validated by the Inbound Message processor.
+     *
+     * @param $messagedata The Inbound Message record
+     * @param $messagedata The message data packet.
+     * @return bool Whether the message was successfully processed.
+     */
+    public function process_message(\stdClass $record, \stdClass $data) {
+        global $DB;
+
+        if (!$maildata = $DB->get_record('messageinbound_messagelist', array('id' => $record->datavalue))) {
+            // The message requested couldn't be found. Failing here will alert the user that we failed.
+            throw new \core\message\inbound\processing_failed_exception('oldmessagenotfound', 'tool_messageinbound');
+        }
+
+        mtrace("=== Request to re-process message {$record->datavalue} from server.");
+        mtrace("=== Message-Id:\t{$maildata->messageid}");
+        mtrace("=== Recipient:\t{$maildata->address}");
+
+        $manager = new \tool_messageinbound\manager();
+        return $manager->process_existing_message($maildata);
+    }
+
+}
diff --git a/admin/tool/messageinbound/classes/task/cleanup_task.php b/admin/tool/messageinbound/classes/task/cleanup_task.php
new file mode 100644 (file)
index 0000000..e507597
--- /dev/null
@@ -0,0 +1,53 @@
+<?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/>.
+
+/**
+ * A scheduled task to handle cleanup of old, unconfirmed e-mails.
+ *
+ * @package    tool_messageinbound
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_messageinbound\task;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * A scheduled task to handle cleanup of old, unconfirmed e-mails.
+ *
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cleanup_task extends \core\task\scheduled_task {
+
+    /**
+     * Get a descriptive name for this task (shown to admins).
+     *
+     * @return string
+     */
+    public function get_name() {
+        return get_string('taskcleanup', 'tool_messageinbound');
+    }
+
+    /**
+     * Execute the main Inbound Message pickup task.
+     */
+    public function execute() {
+        $manager = new \tool_messageinbound\manager();
+        return $manager->tidy_old_messages();
+    }
+}
diff --git a/admin/tool/messageinbound/classes/task/pickup_task.php b/admin/tool/messageinbound/classes/task/pickup_task.php
new file mode 100644 (file)
index 0000000..d10fb8c
--- /dev/null
@@ -0,0 +1,53 @@
+<?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/>.
+
+/**
+ * A scheduled task to handle Inbound Message e-mail pickup.
+ *
+ * @package    tool_messageinbound
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_messageinbound\task;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * A scheduled task to handle Inbound Message e-mail pickup.
+ *
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class pickup_task extends \core\task\scheduled_task {
+
+    /**
+     * Get a descriptive name for this task (shown to admins).
+     *
+     * @return string
+     */
+    public function get_name() {
+        return get_string('taskpickup', 'tool_messageinbound');
+    }
+
+    /**
+     * Execute the main Inbound Message pickup task.
+     */
+    public function execute() {
+        $manager = new \tool_messageinbound\manager();
+        return $manager->pickup_messages();
+    }
+}
diff --git a/admin/tool/messageinbound/db/messageinbound_handlers.php b/admin/tool/messageinbound/db/messageinbound_handlers.php
new file mode 100644 (file)
index 0000000..50cb34a
--- /dev/null
@@ -0,0 +1,34 @@
+<?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/>.
+
+/**
+ * Handlers for tool_messageinbound.
+ *
+ * @package    task
+ * @category   tool_messageinbound
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$handlers = array(
+    array(
+        'classname'         => '\tool_messageinbound\message\inbound\invalid_recipient_handler',
+        'enabled'           => true,
+        'validateaddress'   => false,
+    ),
+);
diff --git a/admin/tool/messageinbound/db/messages.php b/admin/tool/messageinbound/db/messages.php
new file mode 100644 (file)
index 0000000..c425c48
--- /dev/null
@@ -0,0 +1,35 @@
+<?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/>.
+
+/**
+ * Message Providers for task_messageinbound.
+ *
+ * @package    task
+ * @category   messageinbound
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$messageproviders = array (
+    // Invalid recipient handler.
+    'invalidrecipienthandler'   => array(),
+
+    // A generic message processing error.
+    'messageprocessingerror'    => array(),
+
+    // A generic message processing success message.
+    'messageprocessingsuccess'    => array(),
+);
diff --git a/admin/tool/messageinbound/db/tasks.php b/admin/tool/messageinbound/db/tasks.php
new file mode 100644 (file)
index 0000000..6e4c659
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The Main Manager tasks.
+ *
+ * @package    tool_messageinbound
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$tasks = array(
+    array(
+        'classname' => '\tool_messageinbound\task\pickup_task',
+        'blocking' => 0,
+        'minute' => '*',
+        'hour' => '*',
+        'day' => '*',
+        'dayofweek' => '*',
+        'month' => '*'
+    ),
+
+    array(
+        'classname' => '\tool_messageinbound\task\cleanup_task',
+        'blocking' => 0,
+        'minute' => '55',
+        'hour' => '1',
+        'day' => '*',
+        'dayofweek' => '*',
+        'month' => '*'
+    ),
+);
diff --git a/admin/tool/messageinbound/index.php b/admin/tool/messageinbound/index.php
new file mode 100644 (file)
index 0000000..45d8f7f
--- /dev/null
@@ -0,0 +1,84 @@
+<?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/>.
+
+/**
+ * Inbound Message Settings pages.
+ *
+ * @package    tool_messageinbound
+ * @copyright  2014 Andrew NIcols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(dirname(__FILE__) . '/../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/tablelib.php');
+
+admin_externalpage_setup('messageinbound_handlers');
+
+$classname = optional_param('classname', '', PARAM_RAW);
+
+$pageurl = new moodle_url('/admin/tool/messageinbound/index.php');
+
+if (empty($classname)) {
+    $renderer = $PAGE->get_renderer('tool_messageinbound');
+
+    $records = $DB->get_recordset('messageinbound_handlers', null, 'enabled desc', 'classname');
+    $instances = array();
+    foreach ($records as $record) {
+        $instances[] = \core\message\inbound\manager::get_handler($record->classname);
+    }
+
+    echo $OUTPUT->header();
+    echo $renderer->messageinbound_handlers_table($instances);
+    echo $OUTPUT->footer();
+
+} else {
+    // Retrieve the handler and its record.
+    $handler = \core\message\inbound\manager::get_handler($classname);
+    $record = \core\message\inbound\manager::record_from_handler($handler);
+
+    $formurl = new moodle_url($PAGE->url, array('classname' => $classname));
+    $mform = new tool_messageinbound_edit_handler_form($formurl, array(
+            'handler' => $handler,
+    ));
+
+    if ($mform->is_cancelled()) {
+        redirect($PAGE->url);
+    } else if ($data = $mform->get_data()) {
+        // Update the record from the form.
+        $record->defaultexpiration = (int) $data->defaultexpiration;
+
+        if ($handler->can_change_validateaddress()) {
+            $record->validateaddress = !empty($data->validateaddress);
+        }
+
+        if ($handler->can_change_enabled()) {
+            $record->enabled = !empty($data->enabled);
+        }
+        $DB->update_record('messageinbound_handlers', $record);
+        redirect($PAGE->url);
+    }
+
+    // Add the breadcrumb.
+    $pageurl->param('classname', $handler->classname);
+    $PAGE->navbar->add($handler->name, $pageurl);
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(get_string('editinghandler', 'tool_messageinbound', $handler->name));
+    $mform->set_data($record);
+    $mform->display();
+    echo $OUTPUT->footer();
+
+}
diff --git a/admin/tool/messageinbound/lang/en/tool_messageinbound.php b/admin/tool/messageinbound/lang/en/tool_messageinbound.php
new file mode 100644 (file)
index 0000000..329c956
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Strings for component 'tool_messageinbound', language 'en'
+ *
+ * @package    tool_messageinbound
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['classname'] = 'Class name';
+$string['component'] = 'Component';
+$string['configmessageinboundhost'] = 'The address of the server that Moodle should check mail against. To specify a non-default port, you can use the [server]:[port], for example mail.example.com:587 format. If you leave this field blank, Moodle will use the default port for the type of mail server you specify.';
+$string['defaultexpiration'] = 'Default address expiry period';
+$string['defaultexpiration_help'] = 'When an email address is generated by the handler, it can be set to automatically expire after a period of time, so that it can no longer be used. It is advisable to set an expiry period.';
+$string['description'] = 'Description';
+$string['domain'] = 'Email Domain';
+$string['edit'] = 'Edit';
+$string['edithandler'] = 'Edit settings for the {$a} handler';
+$string['editinghandler'] = 'Editing {$a}';
+$string['enabled'] = 'Enabled';