Merge branch 'wip-MDL-46921-master' of git://github.com/abgreeve/moodle
authorDamyon Wiese <damyon@moodle.com>
Thu, 9 Oct 2014 01:57:58 +0000 (09:57 +0800)
committerDamyon Wiese <damyon@moodle.com>
Thu, 9 Oct 2014 01:57:58 +0000 (09:57 +0800)
692 files changed:
admin/environment.xml
admin/roles/classes/potential_assignees_below_course.php
admin/settings/grades.php
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]
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/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
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/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/self/edit_form.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/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/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_view.feature
grade/tests/edittreelib_test.php
grade/tests/report_graderlib_test.php
grade/tests/reportlib_test.php
group/autogroup_form.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/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 [changed mode: 0644->0755]
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/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/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, mode: 0755]
lib/google/Google/Auth/AppIdentity.php [new file with mode: 0755]
lib/google/Google/Auth/AssertionCredentials.php [moved from lib/google/auth/Google_AssertionCredentials.php with 59% similarity, mode: 0755]
lib/google/Google/Auth/Exception.php [new file with mode: 0755]
lib/google/Google/Auth/LoginTicket.php [moved from lib/google/auth/Google_LoginTicket.php with 82% similarity, mode: 0755]
lib/google/Google/Auth/OAuth2.php [new file with mode: 0755]
lib/google/Google/Auth/Simple.php [new file with mode: 0755]
lib/google/Google/Cache/Abstract.php [moved from lib/google/cache/Google_Cache.php with 82% similarity, mode: 0755]
lib/google/Google/Cache/Apc.php [new file with mode: 0755]
lib/google/Google/Cache/Exception.php [new file with mode: 0755]
lib/google/Google/Cache/File.php [new file with mode: 0755]
lib/google/Google/Cache/Memcache.php [new file with mode: 0755]
lib/google/Google/Cache/Null.php [new file with mode: 0755]
lib/google/Google/Client.php [new file with mode: 0755]
lib/google/Google/Collection.php [new file with mode: 0755]
lib/google/Google/Config.php [new file with mode: 0755]
lib/google/Google/Exception.php [moved from lib/google/service/Google_Service.php with 83% similarity, mode: 0755]
lib/google/Google/Http/Batch.php [moved from lib/google/service/Google_BatchRequest.php with 52% similarity, mode: 0755]
lib/google/Google/Http/CacheParser.php [moved from lib/google/io/Google_CacheParser.php with 87% similarity, mode: 0755]
lib/google/Google/Http/MediaFileUpload.php [new file with mode: 0755]
lib/google/Google/Http/REST.php [moved from lib/google/io/Google_REST.php with 61% similarity, mode: 0755]
lib/google/Google/Http/Request.php [new file with mode: 0755]
lib/google/Google/IO/Abstract.php [new file with mode: 0755]
lib/google/Google/IO/Curl.php [new file with mode: 0755]
lib/google/Google/IO/Exception.php [new file with mode: 0755]
lib/google/Google/IO/Stream.php [new file with mode: 0755]
lib/google/Google/IO/cacerts.pem [new file with mode: 0755]
lib/google/Google/Model.php [new file with mode: 0755]
lib/google/Google/Service.php [new file with mode: 0755]
lib/google/Google/Service/AdExchangeBuyer.php [new file with mode: 0755]
lib/google/Google/Service/AdExchangeSeller.php [new file with mode: 0755]
lib/google/Google/Service/AdSense.php [new file with mode: 0755]
lib/google/Google/Service/AdSenseHost.php [new file with mode: 0755]
lib/google/Google/Service/Admin.php [new file with mode: 0755]
lib/google/Google/Service/Analytics.php [new file with mode: 0755]
lib/google/Google/Service/AndroidPublisher.php [new file with mode: 0755]
lib/google/Google/Service/AppState.php [new file with mode: 0755]
lib/google/Google/Service/Appsactivity.php [new file with mode: 0755]
lib/google/Google/Service/Audit.php [new file with mode: 0755]
lib/google/Google/Service/Autoscaler.php [new file with mode: 0755]
lib/google/Google/Service/Bigquery.php [new file with mode: 0755]
lib/google/Google/Service/Blogger.php [new file with mode: 0755]
lib/google/Google/Service/Books.php [new file with mode: 0755]
lib/google/Google/Service/Calendar.php [new file with mode: 0755]
lib/google/Google/Service/CivicInfo.php [new file with mode: 0755]
lib/google/Google/Service/CloudMonitoring.php [new file with mode: 0755]
lib/google/Google/Service/Compute.php [new file with mode: 0755]
lib/google/Google/Service/Coordinate.php [new file with mode: 0755]
lib/google/Google/Service/Customsearch.php [new file with mode: 0755]
lib/google/Google/Service/Datastore.php [new file with mode: 0755]
lib/google/Google/Service/Dfareporting.php [new file with mode: 0755]
lib/google/Google/Service/Directory.php [new file with mode: 0755]
lib/google/Google/Service/Dns.php [new file with mode: 0755]
lib/google/Google/Service/DoubleClickBidManager.php [new file with mode: 0755]
lib/google/Google/Service/Doubleclicksearch.php [new file with mode: 0755]
lib/google/Google/Service/Drive.php [new file with mode: 0755]
lib/google/Google/Service/Exception.php [new file with mode: 0755]
lib/google/Google/Service/Freebase.php [new file with mode: 0755]
lib/google/Google/Service/Fusiontables.php [new file with mode: 0755]
lib/google/Google/Service/Games.php [new file with mode: 0755]
lib/google/Google/Service/GamesManagement.php [new file with mode: 0755]
lib/google/Google/Service/Genomics.php [new file with mode: 0755]
lib/google/Google/Service/Gmail.php [new file with mode: 0755]
lib/google/Google/Service/GroupsMigration.php [new file with mode: 0755]
lib/google/Google/Service/Groupssettings.php [new file with mode: 0755]
lib/google/Google/Service/IdentityToolkit.php [new file with mode: 0755]
lib/google/Google/Service/Licensing.php [new file with mode: 0755]
lib/google/Google/Service/Manager.php [new file with mode: 0755]
lib/google/Google/Service/MapsEngine.php [new file with mode: 0755]
lib/google/Google/Service/Mirror.php [new file with mode: 0755]
lib/google/Google/Service/Oauth2.php [new file with mode: 0755]
lib/google/Google/Service/Orkut.php [new file with mode: 0755]
lib/google/Google/Service/Pagespeedonline.php [new file with mode: 0755]
lib/google/Google/Service/Plus.php [new file with mode: 0755]
lib/google/Google/Service/PlusDomains.php [new file with mode: 0755]
lib/google/Google/Service/Prediction.php [new file with mode: 0755]
lib/google/Google/Service/Pubsub.php [new file with mode: 0755]
lib/google/Google/Service/QPXExpress.php [new file with mode: 0755]
lib/google/Google/Service/Replicapool.php [new file with mode: 0755]
lib/google/Google/Service/Reports.php [new file with mode: 0755]
lib/google/Google/Service/Reseller.php [new file with mode: 0755]
lib/google/Google/Service/Resource.php [new file with mode: 0755]
lib/google/Google/Service/Resourceviews.php [new file with mode: 0755]
lib/google/Google/Service/SQLAdmin.php [new file with mode: 0755]
lib/google/Google/Service/ShoppingContent.php [new file with mode: 0755]
lib/google/Google/Service/SiteVerification.php [new file with mode: 0755]
lib/google/Google/Service/Spectrum.php [new file with mode: 0755]
lib/google/Google/Service/Storage.php [new file with mode: 0755]
lib/google/Google/Service/Taskqueue.php [new file with mode: 0755]
lib/google/Google/Service/Tasks.php [new file with mode: 0755]
lib/google/Google/Service/Translate.php [new file with mode: 0755]
lib/google/Google/Service/Urlshortener.php [new file with mode: 0755]
lib/google/Google/Service/Webfonts.php [new file with mode: 0755]
lib/google/Google/Service/YouTube.php [new file with mode: 0755]
lib/google/Google/Service/YouTubeAnalytics.php [new file with mode: 0755]
lib/google/Google/Signer/Abstract.php [moved from lib/google/auth/Google_Signer.php with 91% similarity, mode: 0755]
lib/google/Google/Signer/P12.php [new file with mode: 0755]
lib/google/Google/Utils.php [moved from lib/google/service/Google_Utils.php with 78% similarity, mode: 0755]
lib/google/Google/Utils/URITemplate.php [new file with mode: 0755]
lib/google/Google/Verifier/Abstract.php [moved from lib/google/auth/Google_Verifier.php with 91% similarity, mode: 0755]
lib/google/Google/Verifier/Pem.php [moved from lib/google/auth/Google_PemVerifier.php with 72% similarity, mode: 0755]
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/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: 0755]
lib/jquery/ui-1.11.1/images/ui-bg_diagonals-thick_20_666666_40x40.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/images/ui-bg_flat_10_000000_40x100.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/images/ui-bg_glass_100_f6f6f6_1x400.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/images/ui-bg_glass_100_fdf5ce_1x400.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/images/ui-bg_glass_65_ffffff_1x400.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/images/ui-bg_gloss-wave_35_f6a828_500x100.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/images/ui-bg_highlight-soft_100_eeeeee_1x100.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/images/ui-bg_highlight-soft_75_ffe45c_1x100.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/images/ui-icons_222222_256x240.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/images/ui-icons_228ef1_256x240.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/images/ui-icons_ef8c08_256x240.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/images/ui-icons_ffd27a_256x240.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/images/ui-icons_ffffff_256x240.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/jquery-ui.css [new file with mode: 0755]
lib/jquery/ui-1.11.1/jquery-ui.js [moved from lib/jquery/ui-1.10.4/jquery-ui.js with 82% similarity, mode: 0755]
lib/jquery/ui-1.11.1/jquery-ui.min.css [new file with mode: 0755]
lib/jquery/ui-1.11.1/jquery-ui.min.js [new file with mode: 0755]
lib/jquery/ui-1.11.1/jquery-ui.structure.css [new file with mode: 0755]
lib/jquery/ui-1.11.1/jquery-ui.structure.min.css [new file with mode: 0755]
lib/jquery/ui-1.11.1/jquery-ui.theme.css [new file with mode: 0755]
lib/jquery/ui-1.11.1/jquery-ui.theme.min.css [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_flat_75_ffffff_40x100.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_glass_65_ffffff_1x400.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_glass_75_dadada_1x400.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-icons_222222_256x240.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-icons_2e83ff_256x240.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-icons_454545_256x240.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-icons_888888_256x240.png [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/images/ui-icons_cd0a0a_256x240.png [new file with mode: 0755]
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, mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/jquery-ui.min.css [new file with mode: 0755]
lib/jquery/ui-1.11.1/theme/smoothness/theme.css [new file with mode: 0755]
lib/modinfolib.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/testing/classes/util.php
lib/testing/generator/data_generator.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_navigation.php
lib/tests/event_grade_deleted_test.php [new file with mode: 0644]
lib/tests/event_user_graded_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/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/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/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/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/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/lib.php
user/profile.php
user/tests/behat/edituserpassword.feature
version.php
webservice/lib.php

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 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..ebfa8ab
--- /dev/null
@@ -0,0 +1,953 @@
+<?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);
+
+            // 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..19c5c75
--- /dev/null
@@ -0,0 +1,111 @@
+<?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';
+$string['fixedvalidateaddress'] = 'Validate sender address';
+$string['fixedvalidateaddress_help'] = 'You cannot change the address validation for this handler. This may be because the handler requires a specific setting.';
+$string['fixedenabled_help'] = 'You cannot change the state of this handler. This may be because the handler is required by other handlers.';
+$string['incomingmailconfiguration'] = 'Incoming mail configuration';
+$string['incomingmailserversettings'] = 'Incoming mail server settings';
+$string['incomingmailserversettings_desc'] = 'Moodle is capable of connecting to appropriately configured IMAP servers. You can specify the settings used to connect to your IMAP server here.';
+$string['invalid_recipient_handler'] = 'If a valid message is received but the sender cannot be authenticated, the message is stored on the email server and the user is contacted using the email address in their user profile. The user is given the chance to reply to confirm the authenticity of the original message.
+
+This handler processes those replies.
+
+It is not possible to disable sender verification of this handler because the user may reply from an incorrect email address if their email client configuration is incorrect.';
+$string['invalid_recipient_handler_name'] = 'Invalid recipient handler';
+$string['invalidrecipientdescription'] = 'The message "{$a->subject}" could not be authenticated, since it was sent from a different email address than in your user profile. For the message to be authenticated, you need to reply to this message.';
+$string['invalidrecipientdescriptionhtml'] = 'The message "{$a->subject}" could not be authenticated, since it was sent from a different email address than in your user profile. For the message to be authenticated, you need to reply to this message.';
+$string['invalidrecipientfinal'] = 'The message you sent with subject "{$a->subject}" could not be authenticated. Please check that you are sending your message from the e-mail account listed in your Moodle profile.';
+$string['mailbox'] = 'Mailbox name';
+$string['mailboxconfiguration'] = 'Mailbox configuration';
+$string['mailboxdescription'] = '[mailbox]+subaddress@[domain]';
+$string['mailsettings'] = 'Mail settings';
+$string['message_handlers'] = 'Message handlers';
+$string['messageprocessingerror'] = 'You recently sent an e-mail to Moodle with the subject "{$a->subject}" but Moodle was unable to process it.
+
+The details of the error are shown below.
+
+{$a->error}';
+$string['messageprocessingerrorhtml'] = '<p>You recently sent an e-mail to Moodle with the subject "{$a->subject}" but Moodle was unable to process it.</p>
+<p>The details of the error are shown below.</p>
+<p>{$a->error}</p>';
+$string['messageprocessingfailed'] = 'Moodle was unable to process the e-mail you sent with subject "{$a->subject}". The following error was given: "{$a->message}".';
+$string['messageprocessingfailedunknown'] = 'Moodle was unable to process the e-mail you sent with subject "{$a->subject}". Contact your system administrator for further information.';
+$string['messageprocessingsuccess'] = '{$a->plain}
+
+If you do not wish to receive these notifications in the future, you can edit your personal messaging preferences by opening {$a->messagepreferencesurl} in your browser.';
+$string['messageprocessingsuccesshtml'] = '{$a->html}
+<p>If you do not wish to receive these notifications in the future, you can <a href="{$a->messagepreferencesurl}">edit your personal messaging preferences</a>.</p>';
+$string['messageinbound'] = 'Message Inbound';
+$string['messageinboundenabled'] = 'Enable incoming mail processing';
+$string['messageinboundenabled_desc'] = 'Incoming mail processing must be enabled in order for messages to be sent with the appropriate information.';
+$string['messageinboundgeneralconfiguration'] = 'General configuration';
+$string['messageinboundgeneralconfiguration_desc'] = 'Inbound message processing allows you to receive and process email within Moodle. This has applications such as sending email replies to forum posts or adding files to a user\'s private files.';
+$string['messageinboundhost'] = 'Incoming Mail Server';
+$string['messageinboundhostpass'] = 'Password';
+$string['messageinboundhostpass_desc'] = 'This is the password your service provider will have provided to log into your e-mail account with.';
+$string['messageinboundhostssl'] = 'Use SSL';
+$string['messageinboundhostssl_desc'] = 'Some mail servers support an additional level of security by encrypting communication between Moodle and your server. We recommend using this SSL encryption if your server supports it.';
+$string['messageinboundhosttype'] = 'Server type';
+$string['messageinboundhostuser'] = 'Username';
+$string['messageinboundhostuser_desc'] = 'This is the username your service provider will have provided to log into your e-mail account with.';
+$string['messageinboundmailboxconfiguration_desc'] = 'When messages are sent out, they fit into the format address+data@example.com. To reliably generate addresses from Moodle, please specify the address that you would normally use before the @ sign, and the domain after the @ sign separately. For example, the Mailbox name in the example would be "address", and the E-mail domain would be "example.com". You should use a dedicated e-mail account for this purpose.';
+$string['messageprovider:invalidrecipienthandler'] = 'Messages to confirm that an inbound messages came from you';
+$string['messageprovider:messageprocessingerror'] = 'Warning when an inbound message could not be processed';
+$string['messageprovider:messageprocessingsuccess'] = 'Confirmation that a message was successfully processed';
+$string['noencryption'] = 'Off - No encryption';
+$string['noexpiry'] = 'No expiry';
+$string['oldmessagenotfound'] = 'You tried to manually authenticate a message, but the message could not be found. This could be because it has already been processed, or because the message expired.';
+$string['oneday'] = 'One day';
+$string['onehour'] = 'One hour';
+$string['oneweek'] = 'One week';
+$string['oneyear'] = 'One year';
+$string['pluginname'] = 'Inbound message configuration';
+$string['replysubjectprefix'] = 'Re:';
+$string['requirevalidation'] = 'Validate sender address';
+$string['name'] = 'Name';
+$string['ssl'] = 'SSL (Auto-detect SSL version)';
+$string['sslv2'] = 'SSLv2 (Force SSL Version 2)';
+$string['sslv3'] = 'SSLv2 (Force SSL Version 3)';
+$string['taskcleanup'] = 'Cleanup of unverified incoming email';
+$string['taskpickup'] = 'Incoming email pickup';
+$string['tls'] = 'TLS (TLS; started via protocol-level negotiation over unencrypted channel; RECOMMENDED way of initiating secure connection)';
+$string['tlsv1'] = 'TLSv1 (TLS direct version 1.x connection to server)';
+$string['validateaddress'] = 'Validate sender email address';
+$string['validateaddress_help'] = 'When a message is received from a user, Moodle attempts to validate the message by comparing the email address of the sender with the email address in their user profile.
+
+If the sender does not match, then the user is sent a notification to confirm that they really did send the email.
+
+If this setting is disabled, then the email address of the sender is not checked at all.';
diff --git a/admin/tool/messageinbound/renderer.php b/admin/tool/messageinbound/renderer.php
new file mode 100644 (file)
index 0000000..d383d74
--- /dev/null
@@ -0,0 +1,109 @@
+<?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/>.
+
+/**
+ * Output rendering for the plugin.
+ *
+ * @package     tool_messageinbound
+ * @copyright   2014 Andrew Nicols
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Implements the plugin renderer
+ *
+ * @copyright 2014 Andrew Nicols
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_messageinbound_renderer extends plugin_renderer_base {
+
+    /**
+     * Render a table listing all of the Inbound Message handlers.
+     *
+     * @param array $handlers - list of all messageinbound handlers.
+     * @return string HTML to output.
+     */
+    public function messageinbound_handlers_table(array $handlers) {
+        global $CFG;
+
+        $table = new html_table();
+        $handlername = new html_table_cell(get_string('name', 'tool_messageinbound') . "\n" .
+                html_writer::tag('span', get_string('classname', 'tool_messageinbound'), array('class' => 'handler-function')));
+
+        // Prepare some of the rows with additional styling.
+        $enabled = new html_table_cell(get_string('enabled', 'tool_messageinbound'));
+        $enabled->attributes['class'] = 'state';
+        $edit = new html_table_cell(get_string('edit', 'tool_messageinbound'));
+        $edit->attributes['class'] = 'edit';
+        $table->head  = array(
+                $handlername,
+                get_string('description', 'tool_messageinbound'),
+                $enabled,
+                $edit,
+            );
+        $table->attributes['class'] = 'admintable generaltable messageinboundhandlers';
+
+        $yes = get_string('yes');
+        $no = get_string('no');
+
+        $data = array();
+
+        // Options for description formatting.
+        $descriptionoptions = new stdClass();
+        $descriptionoptions->trusted = false;
+        $descriptionoptions->noclean = false;
+        $descriptionoptions->smiley = false;
+        $descriptionoptions->filter = false;
+        $descriptionoptions->para = true;
+        $descriptionoptions->newlines = false;
+        $descriptionoptions->overflowdiv = true;
+
+        $editurlbase = new moodle_url('/admin/tool/messageinbound/index.php');
+        foreach ($handlers as $handler) {
+            $handlername = new html_table_cell($handler->name . "\n" .
+                    html_writer::tag('span', $handler->classname, array('class' => 'handler-function')));
+            $handlername->header = true;
+
+            $editurl = new moodle_url($editurlbase, array('classname' => $handler->classname));
+            $editlink = $this->action_icon($editurl, new pix_icon('t/edit',
+                    get_string('edithandler', 'tool_messageinbound', $handler->classname)));
+
+            // Prepare some of the rows with additional styling.
+            $enabled = new html_table_cell($handler->enabled ? $yes : $no);
+            $enabled->attributes['class'] = 'state';
+            $edit = new html_table_cell($editlink);
+            $edit->attributes['class'] = 'edit';
+
+            // Add the row.
+            $row = new html_table_row(array(
+                        $handlername,
+                        format_text($handler->description, FORMAT_MARKDOWN, $descriptionoptions),
+                        $enabled,
+                        $edit,
+                    ));
+
+            if (!$handler->enabled) {
+                $row->attributes['class'] = 'disabled';
+            }
+            $data[] = $row;
+        }
+        $table->data = $data;
+        return html_writer::table($table);
+    }
+
+}
diff --git a/admin/tool/messageinbound/settings.php b/admin/tool/messageinbound/settings.php
new file mode 100644 (file)
index 0000000..72d23ab
--- /dev/null
@@ -0,0 +1,87 @@
+<?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.
+ *
+ * @package    tool_messageinbound
+ * @copyright  2014 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+if ($hassiteconfig) {
+    $category = new admin_category('messageinbound', new lang_string('incomingmailconfiguration', 'tool_messageinbound'));
+
+    // Create a settings page for all of the mail server settings.
+    $settings = new admin_settingpage('messageinbound_mailsettings', new lang_string('mailsettings', 'tool_messageinbound'));
+
+    $settings->add(new admin_setting_heading('messageinbound_generalconfiguration',
+            new lang_string('messageinboundgeneralconfiguration', 'tool_messageinbound'),
+            new lang_string('messageinboundgeneralconfiguration_desc', 'tool_messageinbound'), ''));
+    $settings->add(new admin_setting_configcheckbox('messageinbound_enabled',
+            new lang_string('messageinboundenabled', 'tool_messageinbound'),
+            new lang_string('messageinboundenabled_desc', 'tool_messageinbound'), 0));
+
+    // These settings are used when generating a Inbound Message address.
+    $settings->add(new admin_setting_heading('messageinbound_mailboxconfiguration',
+            new lang_string('mailboxconfiguration', 'tool_messageinbound'),
+            new lang_string('messageinboundmailboxconfiguration_desc', 'tool_messageinbound'), ''));
+    $settings->add(new admin_setting_configtext('messageinbound_mailbox',
+            new lang_string('mailbox', 'tool_messageinbound'),
+            null, '', PARAM_RAW));
+    $settings->add(new admin_setting_configtext('messageinbound_domain',
+            new lang_string('domain', 'tool_messageinbound'),
+            null, '', PARAM_RAW));
+
+    // These settings are used when checking the incoming mailbox for mail.
+    $settings->add(new admin_setting_heading('messageinbound_serversettings',
+            new lang_string('incomingmailserversettings', 'tool_messageinbound'),
+            new lang_string('incomingmailserversettings_desc', 'tool_messageinbound'), ''));
+    $settings->add(new admin_setting_configtext('messageinbound_host',
+            new lang_string('messageinboundhost', 'tool_messageinbound'),
+            new lang_string('configmessageinboundhost', 'tool_messageinbound'), '', PARAM_RAW));
+
+    $options = array(
+        ''          => get_string('noencryption',   'tool_messageinbound'),
+        'ssl'       => get_string('ssl',            'tool_messageinbound'),
+        'sslv2'     => get_string('sslv2',          'tool_messageinbound'),
+        'sslv3'     => get_string('sslv3',          'tool_messageinbound'),
+        'tls'       => get_string('tls',            'tool_messageinbound'),
+        'tlsv1'     => get_string('tlsv1',          'tool_messageinbound'),
+    );
+    $settings->add(new admin_setting_configselect('messageinbound_hostssl',
+            new lang_string('messageinboundhostssl', 'tool_messageinbound'),
+            new lang_string('messageinboundhostssl_desc', 'tool_messageinbound'), 'ssl', $options));
+
+    $settings->add(new admin_setting_configtext('messageinbound_hostuser',
+            new lang_string('messageinboundhostuser', 'tool_messageinbound'),
+            new lang_string('messageinboundhostuser_desc', 'tool_messageinbound'), '', PARAM_NOTAGS));
+    $settings->add(new admin_setting_configpasswordunmask('messageinbound_hostpass',
+            new lang_string('messageinboundhostpass', 'tool_messageinbound'),
+            new lang_string('messageinboundhostpass_desc', 'tool_messageinbound'), ''));
+
+    $category->add('messageinbound', $settings);
+
+    // Link to the external page for Inbound Message handler configuration.
+    $category->add('messageinbound', new admin_externalpage('messageinbound_handlers',
+            new lang_string('message_handlers', 'tool_messageinbound'),
+            "$CFG->wwwroot/$CFG->admin/tool/messageinbound/index.php"));
+
+    // Add the category to the admin tree.
+    $ADMIN->add('server', $category);
+}
diff --git a/admin/tool/messageinbound/styles.css b/admin/tool/messageinbound/styles.css
new file mode 100644 (file)
index 0000000..fba5ddc
--- /dev/null
@@ -0,0 +1,11 @@
+#page-admin-tool-messageinbound-index .handler-function {
+    display: block;
+    padding: 0 0.5em;
+    color: #888;
+    font-size: 0.75em;
+}
+
+#page-admin-tool-messageinbound-index .state,
+#page-admin-tool-messageinbound-index .edit {
+    text-align: center;
+}
similarity index 77%
rename from question/format/learnwise/version.php
rename to admin/tool/messageinbound/version.php
index 05bce4a..57e55ae 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Version information for the calculated question type.
+ * Plugin version info
  *
- * @package    qformat_learnwise
- * @copyright  2011 The Open University
+ * @package    tool_messageinbound
+ * @copyright  2014 Andrew Nicols
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->component = 'qformat_learnwise';
-$plugin->version   = 2014051200;
-
+$plugin->version   = 2014091200;
 $plugin->requires  = 2014050800;
-
-$plugin->maturity  = MATURITY_STABLE;
+$plugin->component = 'tool_messageinbound';
index c5c5410..bd16aaa 100644 (file)
@@ -11,7 +11,7 @@ require_once($CFG->dirroot."/auth/shibboleth/auth.php");
 
 // Find out whether host supports https
 $protocol = 'http://';
-if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'){
+if (is_https()) {
     $protocol = 'https://';
 }
 
index e5a21ec..34ede3b 100644 (file)
@@ -91,10 +91,13 @@ class behat_auth extends behat_base {
             return $steps;
         }
 
-        // If it is needed, it expands the navigation bar with the 'Log out' link.
-        if ($clicknavbar = $this->get_expand_navbar_step()) {
-            array_unshift($steps, $clicknavbar);
-        }
+        // There is no longer any need to worry about whether the navigation
+        // bar needs to be expanded; user_menu now lives outside the
+        // hamburger.
+
+        // However, the user menu *always* needs to be expanded.
+        $xpath ="//div[@class='usermenu']//a[contains(concat(' ', @class, ' '), ' toggle-display ')]";
+        array_unshift($steps, new When('I click on "'.$xpath.'" "xpath_element"'));
 
         return $steps;
     }
index 3905060..2445cdc 100644 (file)
@@ -176,6 +176,12 @@ class condition extends \core_availability\condition {
     public function filter_user_list(array $users, $not, \core_availability\info $info,
             \core_availability\capability_checker $checker) {
         global $CFG, $DB;
+
+        // If the array is empty already, just return it.
+        if (!$users) {
+            return $users;
+        }
+
         require_once($CFG->libdir . '/grouplib.php');
         $course = $info->get_course();
 
index a538704..e0b5997 100644 (file)
@@ -209,6 +209,11 @@ class condition extends \core_availability\condition {
             \core_availability\capability_checker $checker) {
         global $CFG, $DB;
 
+        // If the array is empty already, just return it.
+        if (!$users) {
+            return $users;
+        }
+
         // List users for this course who match the condition.
         $groupingusers = $DB->get_records_sql("
                 SELECT DISTINCT gm.userid
index a156e4b..b781257 100644 (file)
@@ -449,6 +449,11 @@ class condition extends \core_availability\condition {
             \core_availability\capability_checker $checker) {
         global $CFG, $DB;
 
+        // If the array is empty already, just return it.
+        if (!$users) {
+            return $users;
+        }
+
         // Get all users from the list who match the condition.
         list ($sql, $params) = $DB->get_in_or_equal(array_keys($users));
 
index e79c667..4dd37be 100644 (file)
@@ -940,8 +940,8 @@ class backup_gradebook_structure_step extends backup_structure_step {
             'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
             'calculation', 'gradetype', 'grademax', 'grademin',
             'scaleid', 'outcomeid', 'gradepass', 'multfactor',
-            'plusfactor', 'aggregationcoef', 'sortorder', 'display',
-            'decimals', 'hidden', 'locked', 'locktime',
+            'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride',
+            'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
             'needsupdate', 'timecreated', 'timemodified'));
 
         $grade_grades = new backup_nested_element('grade_grades');
@@ -950,7 +950,8 @@ class backup_gradebook_structure_step extends backup_structure_step {
             'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
             'locked', 'locktime', 'exported', 'overridden',
             'excluded', 'feedback', 'feedbackformat', 'information',
-            'informationformat', 'timecreated', 'timemodified'));
+            'informationformat', 'timecreated', 'timemodified',
+            'aggregationstatus', 'aggregationweight'));
 
         //grade_categories
         $grade_categories = new backup_nested_element('grade_categories');
@@ -2163,8 +2164,8 @@ class backup_activity_grades_structure_step extends backup_structure_step {
             'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
             'calculation', 'gradetype', 'grademax', 'grademin',
             'scaleid', 'outcomeid', 'gradepass', 'multfactor',
-            'plusfactor', 'aggregationcoef', 'sortorder', 'display',
-            'decimals', 'hidden', 'locked', 'locktime',
+            'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride',
+            'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
             'needsupdate', 'timecreated', 'timemodified'));
 
         $grades = new backup_nested_element('grade_grades');
@@ -2174,7 +2175,8 @@ class backup_activity_grades_structure_step extends backup_structure_step {
             'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
             'locked', 'locktime', 'exported', 'overridden',
             'excluded', 'feedback', 'feedbackformat', 'information',
-            'informationformat', 'timecreated', 'timemodified'));
+            'informationformat', 'timecreated', 'timemodified',
+            'aggregationstatus', 'aggregationweight'));
 
         $letters = new backup_nested_element('grade_letters');
 
index 257a2ec..cde79ff 100644 (file)
@@ -62,6 +62,12 @@ abstract class backup_ui_stage extends base_ui_stage {
  */
 class backup_ui_stage_initial extends backup_ui_stage {
 
+    /**
+     * When set to true we skip all stages and jump to immediately processing the backup.
+     * @var bool
+     */
+    protected $oneclickbackup = false;
+
     /**
      * Initial backup stage constructor
      * @param backup_ui $ui
@@ -86,6 +92,9 @@ class backup_ui_stage_initial extends backup_ui_stage {
 
         $data = $form->get_data();
         if ($data && confirm_sesskey()) {
+            if (isset($data->oneclickbackup)) {
+                $this->oneclickbackup = true;
+            }
             $tasks = $this->ui->get_tasks();
             $changes = 0;
             foreach ($tasks as &$task) {
@@ -112,6 +121,42 @@ class backup_ui_stage_initial extends backup_ui_stage {
         }
     }
 
+    /**
+     * Gets the next stage for the backup.
+     *
+     * We override this function to implement the one click backup.
+     * When the user performs a one click backup we jump straight to the final stage.
+     *
+     * @return int
+     */
+    public function get_next_stage() {
+        if ($this->oneclickbackup) {
+            // Its a one click backup.
+            // The default filename is backup.mbz, this normally gets set to something useful in the confirmation stage.
+            // because we skipped that stage we must manually set this to a useful value.
+            $tasks = $this->ui->get_tasks();
+            foreach ($tasks as $task) {
+                if ($task instanceof backup_root_task) {
+                    // Find the filename setting.
+                    $setting = $task->get_setting('filename');
+                    if ($setting) {
+                        // Use the helper objects to get a useful name.
+                        $filename = backup_plan_dbops::get_default_backup_filename(
+                            $this->ui->get_format(),
+                            $this->ui->get_type(),
+                            $this->ui->get_controller_id(),
+                            $this->ui->get_setting_value('users'),
+                            $this->ui->get_setting_value('anonymize')
+                        );
+                        $setting->set_value($filename);
+                    }
+                }
+            }
+            return backup_ui::STAGE_FINAL;
+        }
+        return parent::get_next_stage();
+    }
+
     /**
      * Initialises the backup_moodleform instance for this stage
      *
index 262142e..d149cf0 100644 (file)
@@ -123,6 +123,9 @@ abstract class base_moodleform extends moodleform {
         $buttonarray[] = $this->_form->createElement('submit', 'submitbutton', get_string($this->uistage->get_ui()->get_name().'stage'.$this->uistage->get_stage().'action', 'backup'), array('class'=>'proceedbutton'));
         if (!$this->uistage->is_first_stage()) {
             $buttonarray[] = $this->_form->createElement('submit', 'previous', get_string('previousstage','backup'));
+        } else {
+            $buttonarray[] = $this->_form->createElement('submit', 'oneclickbackup', get_string('jumptofinalstep', 'backup'),
+                array('class' => 'oneclickbackup'));
         }
         $buttonarray[] = $this->_form->createElement('cancel', 'cancel', get_string('cancel'), array('class'=>'confirmcancel'));
         $this->_form->addGroup($buttonarray, 'buttonar', '', array(' '), false);
index 71b90dd..85f99a2 100644 (file)
@@ -84,7 +84,7 @@ abstract class base_ui_stage {
      * The next stage
      * @return int
      */
-    final public function get_next_stage() {
+    public function get_next_stage() {
         return floor($this->stage*2);
     }
     /**
index 94e02e3..de40df1 100644 (file)
@@ -60,3 +60,10 @@ Feature: Backup Moodle courses
     And I press "Next"
     And I should see "Test assign"
     And I should not see "Test data"
+
+  @javascript
+  Scenario: Backup a course using the one click backup button
+    When I perform a quick backup of course "Course 2"
+    Then I should see "Restore course"
+    And I should see "Course backup area"
+    And I should see "backup-moodle2-course-"
\ No newline at end of file
index 9a230ed..ab0a272 100644 (file)
@@ -98,6 +98,40 @@ class behat_backup extends behat_base {
         $this->find_button(get_string('backupstage16action', 'backup'))->press();
     }
 
+    /**
+     * Performs a quick (one click) backup of a course.
+     *
+     * Please note that because you can't set settings with this there is no way to know what the filename
+     * that was produced was. It contains a timestamp making it hard to find.
+     *
+     * @Given /^I perform a quick backup of course "(?P<course_fullname_string>(?:[^"]|\\")*)"$/
+     * @param string $backupcourse
+     */
+    public function i_perform_a_quick_backup_of_course($backupcourse) {
+        // We can not use other steps here as we don't know where the provided data
+        // table elements are used, and we need to catch exceptions contantly.
+
+        // Go to homepage.
+        $this->getSession()->visit($this->locate_path('/'));
+
+        // Click the course link.
+        $this->find_link($backupcourse)->click();
+
+        // Click the backup link.
+        $this->find_link(get_string('backup'))->click();
+        $this->wait();
+
+        // Initial settings.
+        $this->find_button(get_string('jumptofinalstep', 'backup'))->press();
+        $this->wait();
+
+        // Waiting for it to finish.
+        $this->wait(self::EXTENDED_TIMEOUT);
+
+        // Last backup continue button.
+        $this->find_button(get_string('backupstage16action', 'backup'))->press();
+    }
+
     /**
      * Imports the specified origin course into the other course using the provided options.
      *
index 8ac27ed..96a4359 100644 (file)
@@ -34,6 +34,7 @@ $string['defaultmaxcoursesdesc'] = 'Maximum courses which should be displayed on
 $string['expandall'] = 'Expand all course lists';
 $string['forcedefaultmaxcourses'] = 'Force maximum courses';
 $string['forcedefaultmaxcoursesdesc'] = 'If set then user will not be able to change his/her personal setting';
+$string['fullpath'] = 'Full path';
 $string['hiddencoursecount'] = 'You have {$a} hidden course';
 $string['hiddencoursecountplural'] = 'You have {$a} hidden courses';
 $string['hiddencoursecountwithshowall'] = 'You have {$a->coursecount} hidden course ({$a->showalllink})';
@@ -45,13 +46,17 @@ $string['movecoursehere'] = 'Move course here';
 $string['movetofirst'] = 'Move {$a} course to top';
 $string['moveafterhere'] = 'Move {$a->movingcoursename} course after {$a->currentcoursename}';
 $string['movingcourse'] = 'You are moving: {$a->fullname} ({$a->cancellink})';
+$string['none'] = 'None';
 $string['numtodisplay'] = 'Number of courses to display: ';
+$string['onlyparentname'] = 'Only parent name';
 $string['otherexpanded'] = 'Other courses expanded';
 $string['pluginname'] = 'Course overview';
 $string['preservestates'] = 'Preserve expanded states';
 $string['shortnameprefix'] = 'Includes {$a}';
 $string['shortnamesufixsingular'] = ' (and {$a} other)';
 $string['shortnamesufixprural'] = ' (and {$a} others)';
+$string['showcategories'] = 'Show categories';
+$string['showcategoriesdesc'] = 'Should categories be displayed for each course boxes?';
 $string['showchildren'] = 'Show children';
 $string['showchildrendesc'] = 'Should child courses be listed underneath the main course title?';
 $string['showwelcomearea'] = 'Show welcome area';
index df94445..746ccf4 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+define('BLOCKS_COURSE_OVERVIEW_SHOWCATEGORIES_NONE', '0');
+define('BLOCKS_COURSE_OVERVIEW_SHOWCATEGORIES_ONLY_PARENT_NAME', '1');
+define('BLOCKS_COURSE_OVERVIEW_SHOWCATEGORIES_FULL_PATH', '2');
+
 /**
  * Display overview for courses
  *
index 76ef098..8923322 100644 (file)
@@ -41,6 +41,10 @@ class block_course_overview_renderer extends plugin_renderer_base {
     public function course_overview($courses, $overviews) {
         $html = '';
         $config = get_config('block_course_overview');
+        if ($config->showcategories != BLOCKS_COURSE_OVERVIEW_SHOWCATEGORIES_NONE) {
+            global $CFG;
+            require_once($CFG->libdir.'/coursecatlib.php');
+        }
         $ismovingcourse = false;
         $courseordernumber = 0;
         $maxcourses = count($courses);
@@ -128,6 +132,24 @@ class block_course_overview_renderer extends plugin_renderer_base {
                 $html .= $this->activity_display($course->id, $overviews[$course->id]);
             }
 
+            if ($config->showcategories != BLOCKS_COURSE_OVERVIEW_SHOWCATEGORIES_NONE) {
+                // List category parent or categories path here.
+                $currentcategory = coursecat::get($course->category, IGNORE_MISSING);
+                if ($currentcategory !== null) {
+                    $html .= html_writer::start_tag('div', array('class' => 'categorypath'));
+                    if ($config->showcategories == BLOCKS_COURSE_OVERVIEW_SHOWCATEGORIES_FULL_PATH) {
+                        foreach ($currentcategory->get_parents() as $categoryid) {
+                            $category = coursecat::get($categoryid, IGNORE_MISSING);
+                            if ($category !== null) {
+                                $html .= $category->get_formatted_name().' / ';
+                            }
+                        }
+                    }
+      &n