Merge branch 'MDL-56069' of git://github.com/timhunt/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 4 Oct 2016 14:34:50 +0000 (16:34 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 4 Oct 2016 14:34:50 +0000 (16:34 +0200)
450 files changed:
.stylelintrc
Gruntfile.js
admin/settings/development.php
admin/settings/plugins.php
admin/tool/assignmentupgrade/styles.css
admin/tool/behat/styles.css
admin/tool/capability/styles.css
admin/tool/customlang/styles.css
admin/tool/health/styles.css
admin/tool/langimport/styles.css
admin/tool/lp/styles.css
admin/tool/lpimportcsv/classes/form/export.php [new file with mode: 0644]
admin/tool/lpimportcsv/classes/form/import.php [new file with mode: 0644]
admin/tool/lpimportcsv/classes/form/import_confirm.php [new file with mode: 0644]
admin/tool/lpimportcsv/classes/framework_exporter.php [new file with mode: 0644]
admin/tool/lpimportcsv/classes/framework_importer.php [new file with mode: 0644]
admin/tool/lpimportcsv/continue.php [new file with mode: 0644]
admin/tool/lpimportcsv/export.php [new file with mode: 0644]
admin/tool/lpimportcsv/index.php [new file with mode: 0644]
admin/tool/lpimportcsv/lang/en/tool_lpimportcsv.php [new file with mode: 0644]
admin/tool/lpimportcsv/settings.php [new file with mode: 0644]
admin/tool/lpimportcsv/tests/fixtures/example.csv [new file with mode: 0644]
admin/tool/lpimportcsv/tests/import_test.php [new file with mode: 0644]
admin/tool/lpimportcsv/version.php [new file with mode: 0644]
admin/tool/mobile/classes/api.php
admin/tool/mobile/classes/external.php
admin/tool/mobile/lang/en/tool_mobile.php
admin/tool/mobile/launch.php [new file with mode: 0644]
admin/tool/mobile/settings.php [new file with mode: 0644]
admin/tool/mobile/tests/externallib_test.php
admin/tool/mobile/version.php
admin/tool/profiling/styles.css
blocks/activity_results/styles.css
blocks/blog_tags/styles.css
blocks/community/styles.css
blocks/course_list/styles.css
blocks/course_overview/styles.css
blocks/course_summary/styles.css
blocks/globalsearch/styles.css
blocks/lp/styles.css
blocks/messages/styles.css
blocks/myprofile/styles.css
blocks/online_users/styles.css
blocks/recent_activity/styles.css
blocks/rss_client/edit_form.php
blocks/rss_client/styles.css
blocks/search_forums/styles.css
blocks/settings/styles.css
blocks/site_main_menu/styles.css
blocks/social_activities/styles.css
blocks/tag_flickr/styles.css
blocks/tag_youtube/styles.css
blog/external_blog_edit.php
blog/external_blogs.php
blog/locallib.php
blog/tests/events_test.php [new file with mode: 0644]
blog/tests/lib_test.php
cache/stores/apcu/addinstanceform.php [new file with mode: 0644]
cache/stores/apcu/lang/en/cachestore_apcu.php [new file with mode: 0644]
cache/stores/apcu/lib.php [new file with mode: 0644]
cache/stores/apcu/settings.php [new file with mode: 0644]
cache/stores/apcu/tests/apcu_test.php [new file with mode: 0644]
cache/stores/apcu/version.php [new file with mode: 0644]
calendar/yui/build/moodle-calendar-info/assets/skins/sam/moodle-calendar-info.css
calendar/yui/src/info/assets/skins/sam/moodle-calendar-info.css
competency/classes/invalid_persistent_exception.php
composer.json
composer.lock
config-dist.php
course/format/singleactivity/styles.css
course/format/topics/styles.css
course/format/weeks/styles.css
dataformat/csv/classes/writer.php
dataformat/excel/classes/writer.php
dataformat/ods/classes/writer.php
enrol/manual/yui/quickenrolment/assets/skins/sam/quickenrolment.css
enrol/yui/otherusersmanager/assets/skins/sam/otherusersmanager.css
enrol/yui/rolemanager/assets/skins/sam/rolemanager.css
filter/glossary/styles.css
filter/mediaplugin/styles.css
grade/grading/form/guide/styles.css
grade/grading/form/rubric/styles.css
grade/import/direct/styles.css
grade/report/grader/styles.css
grade/report/history/styles.css
grade/report/singleview/styles.css
grade/report/upgrade.txt
grade/report/user/db/services.php
grade/report/user/externallib.php
grade/report/user/lib.php
grade/report/user/styles.css
grade/report/user/tests/externallib_test.php
grade/report/user/version.php
lang/en/admin.php
lang/en/blog.php
lang/en/competency.php
lang/en/countries.php
lang/en/deprecated.txt
lib/accesslib.php
lib/amd/build/loglevel.min.js
lib/amd/src/loglevel.js
lib/classes/component.php
lib/classes/dml/sql_join.php [new file with mode: 0644]
lib/classes/event/blog_association_deleted.php [new file with mode: 0644]
lib/classes/event/blog_external_added.php [new file with mode: 0644]
lib/classes/event/blog_external_removed.php [new file with mode: 0644]
lib/classes/event/blog_external_updated.php [new file with mode: 0644]
lib/classes/event/blog_external_viewed.php [new file with mode: 0644]
lib/classes/grades_external.php
lib/classes/plugin_manager.php
lib/classes/plugininfo/tool.php
lib/classes/useragent.php
lib/csslib.php
lib/db/services.php
lib/db/upgrade.php
lib/deprecatedlib.php
lib/editor/atto/plugins/emoticon/styles.css
lib/editor/atto/plugins/equation/styles.css
lib/editor/atto/plugins/image/styles.css
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js
lib/editor/atto/plugins/image/yui/src/button/js/button.js
lib/editor/atto/plugins/managefiles/styles.css
lib/editor/atto/plugins/table/styles.css
lib/editor/atto/styles.css
lib/editor/tinymce/plugins/managefiles/styles.css
lib/editor/tinymce/plugins/moodleimage/tinymce/css/image.css
lib/editor/tinymce/plugins/moodlemedia/tinymce/css/media.css
lib/editor/tinymce/plugins/spellchecker/tinymce/css/content.css
lib/editor/tinymce/styles.css
lib/enrollib.php
lib/externallib.php
lib/form/form.js
lib/grouplib.php
lib/jquery/plugins.php
lib/jquery/readme_moodle.txt
lib/jquery/ui-1.11.4/external/jquery/jquery.js [deleted file]
lib/jquery/ui-1.11.4/images/ui-bg_flat_0_aaaaaa_40x100.png [deleted file]
lib/jquery/ui-1.11.4/images/ui-bg_flat_75_ffffff_40x100.png [deleted file]
lib/jquery/ui-1.11.4/images/ui-bg_glass_55_fbf9ee_1x400.png [deleted file]
lib/jquery/ui-1.11.4/images/ui-bg_glass_65_ffffff_1x400.png [deleted file]
lib/jquery/ui-1.11.4/images/ui-bg_glass_75_dadada_1x400.png [deleted file]
lib/jquery/ui-1.11.4/images/ui-bg_glass_75_e6e6e6_1x400.png [deleted file]
lib/jquery/ui-1.11.4/images/ui-bg_glass_95_fef1ec_1x400.png [deleted file]
lib/jquery/ui-1.11.4/images/ui-bg_highlight-soft_75_cccccc_1x100.png [deleted file]
lib/jquery/ui-1.11.4/images/ui-icons_222222_256x240.png [deleted file]
lib/jquery/ui-1.11.4/images/ui-icons_2e83ff_256x240.png [deleted file]
lib/jquery/ui-1.11.4/images/ui-icons_454545_256x240.png [deleted file]
lib/jquery/ui-1.11.4/images/ui-icons_888888_256x240.png [deleted file]
lib/jquery/ui-1.11.4/images/ui-icons_cd0a0a_256x240.png [deleted file]
lib/jquery/ui-1.11.4/index.html [deleted file]
lib/jquery/ui-1.11.4/jquery-ui.min.css [deleted file]
lib/jquery/ui-1.11.4/jquery-ui.min.js [deleted file]
lib/jquery/ui-1.11.4/jquery-ui.structure.min.css [deleted file]
lib/jquery/ui-1.11.4/jquery-ui.theme.min.css [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-bg_flat_75_ffffff_40x100.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-bg_glass_65_ffffff_1x400.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-bg_glass_75_dadada_1x400.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-icons_222222_256x240.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-icons_2e83ff_256x240.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-icons_454545_256x240.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-icons_888888_256x240.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/images/ui-icons_cd0a0a_256x240.png [deleted file]
lib/jquery/ui-1.11.4/theme/smoothness/jquery-ui.min.css [deleted file]
lib/jquery/ui-1.12.1/LICENSE.txt [new file with mode: 0644]
lib/jquery/ui-1.12.1/images/ui-icons_444444_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/images/ui-icons_555555_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/images/ui-icons_777620_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/images/ui-icons_777777_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/images/ui-icons_cc0000_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/images/ui-icons_ffffff_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/jquery-ui.css [moved from lib/jquery/ui-1.11.4/theme/smoothness/jquery-ui.css with 74% similarity]
lib/jquery/ui-1.12.1/jquery-ui.js [moved from lib/jquery/ui-1.11.4/jquery-ui.js with 52% similarity]
lib/jquery/ui-1.12.1/jquery-ui.min.css [new file with mode: 0644]
lib/jquery/ui-1.12.1/jquery-ui.min.js [new file with mode: 0644]
lib/jquery/ui-1.12.1/jquery-ui.structure.css [moved from lib/jquery/ui-1.11.4/jquery-ui.structure.css with 81% similarity]
lib/jquery/ui-1.12.1/jquery-ui.structure.min.css [new file with mode: 0644]
lib/jquery/ui-1.12.1/jquery-ui.theme.css [moved from lib/jquery/ui-1.11.4/jquery-ui.theme.css with 69% similarity]
lib/jquery/ui-1.12.1/jquery-ui.theme.min.css [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/images/ui-bg_glass_65_ffffff_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/images/ui-bg_glass_75_dadada_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/images/ui-icons_222222_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/images/ui-icons_2e83ff_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/images/ui-icons_454545_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/images/ui-icons_888888_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/images/ui-icons_cd0a0a_256x240.png [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/jquery-ui.css [moved from lib/jquery/ui-1.11.4/jquery-ui.css with 84% similarity]
lib/jquery/ui-1.12.1/theme/smoothness/jquery-ui.min.css [new file with mode: 0644]
lib/jquery/ui-1.12.1/theme/smoothness/theme.css [moved from lib/jquery/ui-1.11.4/theme/smoothness/theme.css with 88% similarity]
lib/outputlib.php
lib/outputrenderers.php
lib/phpmailer/README_MOODLE.txt
lib/phpmailer/VERSION
lib/phpmailer/class.phpmailer.php
lib/phpmailer/class.smtp.php
lib/phpmailer/language/phpmailer.lang-ka.php
lib/phpmailer/language/phpmailer.lang-pl.php
lib/phpmailer/language/phpmailer.lang-ru.php
lib/requirejs/require.js
lib/requirejs/require.min.js
lib/setup.php
lib/simplepie/library/SimplePie.php [changed mode: 0644->0755]
lib/simplepie/library/SimplePie/Author.php
lib/simplepie/library/SimplePie/Cache.php
lib/simplepie/library/SimplePie/Cache/Base.php
lib/simplepie/library/SimplePie/Cache/DB.php
lib/simplepie/library/SimplePie/Cache/File.php
lib/simplepie/library/SimplePie/Cache/Memcache.php
lib/simplepie/library/SimplePie/Cache/Memcached.php [new file with mode: 0755]
lib/simplepie/library/SimplePie/Cache/MySQL.php
lib/simplepie/library/SimplePie/Cache/Redis.php [new file with mode: 0644]
lib/simplepie/library/SimplePie/Caption.php
lib/simplepie/library/SimplePie/Category.php
lib/simplepie/library/SimplePie/Content/Type/Sniffer.php
lib/simplepie/library/SimplePie/Copyright.php
lib/simplepie/library/SimplePie/Core.php
lib/simplepie/library/SimplePie/Credit.php
lib/simplepie/library/SimplePie/Decode/HTML/Entities.php
lib/simplepie/library/SimplePie/Enclosure.php
lib/simplepie/library/SimplePie/Exception.php
lib/simplepie/library/SimplePie/File.php
lib/simplepie/library/SimplePie/HTTP/Parser.php
lib/simplepie/library/SimplePie/IRI.php
lib/simplepie/library/SimplePie/Item.php
lib/simplepie/library/SimplePie/Locator.php
lib/simplepie/library/SimplePie/Misc.php
lib/simplepie/library/SimplePie/Net/IPv6.php
lib/simplepie/library/SimplePie/Parse/Date.php
lib/simplepie/library/SimplePie/Parser.php
lib/simplepie/library/SimplePie/Rating.php
lib/simplepie/library/SimplePie/Registry.php [changed mode: 0644->0755]
lib/simplepie/library/SimplePie/Restriction.php
lib/simplepie/library/SimplePie/Sanitize.php
lib/simplepie/library/SimplePie/Source.php
lib/simplepie/library/SimplePie/XML/Declaration/Parser.php
lib/simplepie/library/SimplePie/gzdecode.php
lib/simplepie/readme_moodle.txt
lib/spout/readme_moodle.txt
lib/spout/src/Spout/Common/Escaper/ODS.php
lib/spout/src/Spout/Common/Escaper/XLSX.php
lib/spout/src/Spout/Common/Helper/EncodingHelper.php
lib/spout/src/Spout/Common/Helper/GlobalFunctionsHelper.php
lib/spout/src/Spout/Common/Singleton.php [new file with mode: 0644]
lib/spout/src/Spout/Reader/AbstractReader.php
lib/spout/src/Spout/Reader/CSV/RowIterator.php
lib/spout/src/Spout/Reader/ODS/Helper/CellValueFormatter.php
lib/spout/src/Spout/Reader/ODS/Reader.php
lib/spout/src/Spout/Reader/ODS/RowIterator.php
lib/spout/src/Spout/Reader/ODS/Sheet.php
lib/spout/src/Spout/Reader/ODS/SheetIterator.php
lib/spout/src/Spout/Reader/Wrapper/SimpleXMLElement.php
lib/spout/src/Spout/Reader/Wrapper/XMLReader.php
lib/spout/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php
lib/spout/src/Spout/Reader/XLSX/Helper/DateFormatHelper.php [new file with mode: 0644]
lib/spout/src/Spout/Reader/XLSX/Helper/SharedStringsCaching/CachingStrategyFactory.php
lib/spout/src/Spout/Reader/XLSX/Helper/SharedStringsHelper.php
lib/spout/src/Spout/Reader/XLSX/Helper/SheetHelper.php
lib/spout/src/Spout/Reader/XLSX/Helper/StyleHelper.php
lib/spout/src/Spout/Reader/XLSX/Reader.php
lib/spout/src/Spout/Reader/XLSX/RowIterator.php
lib/spout/src/Spout/Reader/XLSX/Sheet.php
lib/spout/src/Spout/Reader/XLSX/SheetIterator.php
lib/spout/src/Spout/Writer/AbstractWriter.php
lib/spout/src/Spout/Writer/CSV/Writer.php
lib/spout/src/Spout/Writer/Exception/Border/InvalidNameException.php [new file with mode: 0644]
lib/spout/src/Spout/Writer/Exception/Border/InvalidStyleException.php [new file with mode: 0644]
lib/spout/src/Spout/Writer/Exception/Border/InvalidWidthException.php [new file with mode: 0644]
lib/spout/src/Spout/Writer/ODS/Helper/BorderHelper.php [new file with mode: 0644]
lib/spout/src/Spout/Writer/ODS/Helper/StyleHelper.php
lib/spout/src/Spout/Writer/ODS/Internal/Worksheet.php
lib/spout/src/Spout/Writer/Style/Border.php [new file with mode: 0644]
lib/spout/src/Spout/Writer/Style/BorderBuilder.php [new file with mode: 0644]
lib/spout/src/Spout/Writer/Style/BorderPart.php [new file with mode: 0644]
lib/spout/src/Spout/Writer/Style/Style.php
lib/spout/src/Spout/Writer/Style/StyleBuilder.php
lib/spout/src/Spout/Writer/XLSX/Helper/BorderHelper.php [new file with mode: 0644]
lib/spout/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php
lib/spout/src/Spout/Writer/XLSX/Helper/SharedStringsHelper.php
lib/spout/src/Spout/Writer/XLSX/Helper/StyleHelper.php
lib/spout/src/Spout/Writer/XLSX/Internal/Workbook.php
lib/spout/src/Spout/Writer/XLSX/Internal/Worksheet.php
lib/tests/accesslib_test.php
lib/tests/csslib_test.php
lib/tests/grouplib_test.php
lib/tests/useragent_test.php
lib/thirdpartylibs.xml
login/token.php
message/output/airnotifier/style.css
mnet/service/enrol/styles.css
mod/assign/db/services.php
mod/assign/feedback/editpdf/styles.css
mod/assign/styles.css
mod/assign/templates/grading_navigation.mustache
mod/assign/version.php
mod/book/tool/exportimscp/imscp.css
mod/book/tool/print/print.css
mod/chat/gui_ajax/theme/bubble/chat.css
mod/chat/gui_ajax/theme/compact/chat.css
mod/chat/styles.css
mod/choice/styles.css
mod/data/preset/imagegallery/csstemplate.css
mod/data/styles.css
mod/feedback/styles.css
mod/forum/externallib.php
mod/forum/styles.css
mod/forum/tests/externallib_test.php
mod/glossary/classes/external.php
mod/glossary/styles.css
mod/glossary/tests/external_test.php
mod/imscp/styles.css
mod/lti/styles.css
mod/lti/tests/behat/addtool.feature
mod/quiz/classes/external.php
mod/quiz/db/upgrade.php
mod/quiz/lang/en/quiz.php
mod/quiz/mod_form.php
mod/quiz/report/grading/styles.css
mod/quiz/styles.css
mod/quiz/tests/external_test.php
mod/quiz/version.php
mod/resource/styles.css
mod/scorm/styles.css
mod/survey/styles.css
mod/url/styles.css
mod/wiki/styles.css
mod/workshop/allocation/manual/styles.css
mod/workshop/allocation/random/styles.css
mod/workshop/styles.css
question/behaviour/deferredcbm/styles.css
question/format/xhtml/xhtml.css
question/type/calculated/styles.css
question/type/calculatedmulti/styles.css
question/type/calculatedsimple/styles.css
question/type/ddimageortext/lang/en/qtype_ddimageortext.php
question/type/ddimageortext/rendererbase.php
question/type/ddimageortext/styles.css
question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd-debug.js
question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd-min.js
question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd.js
question/type/ddimageortext/yui/src/ddimageortext/js/ddimageortext.js
question/type/ddmarker/styles.css
question/type/ddwtos/lang/en/qtype_ddwtos.php
question/type/ddwtos/renderer.php
question/type/ddwtos/styles.css
question/type/essay/styles.css
question/type/gapselect/styles.css
question/type/match/styles.css
question/type/multianswer/styles.css
question/type/multichoice/styles.css
question/type/numerical/styles.css
question/type/shortanswer/styles.css
rating/classes/external.php
rating/lib.php
rating/rate_ajax.php
rating/tests/externallib_test.php
report/competency/index.php
report/competency/styles.css [deleted file]
report/eventlist/styles.css
report/log/styles.css
report/loglive/styles.css
report/outline/styles.css
report/participation/styles.css
report/progress/styles.css
report/stats/styles.css
theme/boost/amd/build/alert.min.js
theme/boost/amd/build/button.min.js
theme/boost/amd/build/carousel.min.js
theme/boost/amd/build/collapse.min.js
theme/boost/amd/build/dropdown.min.js
theme/boost/amd/build/modal.min.js
theme/boost/amd/build/popover.min.js
theme/boost/amd/build/scrollspy.min.js
theme/boost/amd/build/tab.min.js
theme/boost/amd/build/tooltip.min.js
theme/boost/amd/src/alert.js
theme/boost/amd/src/button.js
theme/boost/amd/src/carousel.js
theme/boost/amd/src/collapse.js
theme/boost/amd/src/dropdown.js
theme/boost/amd/src/modal.js
theme/boost/amd/src/popover.js
theme/boost/amd/src/scrollspy.js
theme/boost/amd/src/tab.js
theme/boost/amd/src/tooltip.js
theme/boost/amd/src/util.js
theme/boost/config.php
theme/boost/lang/en/theme_boost.php
theme/boost/readme_moodle.txt
theme/boost/scss/bootstrap/_alert.scss
theme/boost/scss/bootstrap/_card.scss
theme/boost/scss/bootstrap/_input-group.scss
theme/boost/scss/bootstrap/_media.scss
theme/boost/scss/bootstrap/_navbar.scss
theme/boost/scss/bootstrap/_print.scss
theme/boost/scss/bootstrap/_progress.scss
theme/boost/scss/bootstrap/_reboot.scss
theme/boost/scss/bootstrap/_tables.scss
theme/boost/scss/bootstrap/_variables.scss
theme/boost/scss/bootstrap/bootstrap.scss
theme/boost/scss/bootstrap/mixins/_forms.scss
theme/boost/scss/bootstrap/mixins/_grid-framework.scss
theme/boost/scss/bootstrap/mixins/_grid.scss
theme/boost/scss/bootstrap/mixins/_text-emphasis.scss
theme/boost/scss/bootstrap/utilities/_background.scss
theme/boost/scss/bootstrap/utilities/_visibility.scss
theme/boost/scss/moodle/course.scss
theme/boost/scss/preset-default.scss
theme/boost/settings.php
theme/boost/style/editor.css [deleted file]
theme/boost/templates/core/dataformat_selector.mustache
theme/boost/templates/mod_assign/grading_navigation.mustache
theme/boost/thirdpartylibs.xml
theme/bootstrapbase/config.php
theme/bootstrapbase/less/moodle/admin.less
theme/bootstrapbase/less/moodle/blocks.less
theme/bootstrapbase/less/moodle/calendar.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/course.less
theme/bootstrapbase/less/moodle/debug.less
theme/bootstrapbase/less/moodle/filemanager.less
theme/bootstrapbase/less/moodle/forms.less
theme/bootstrapbase/less/moodle/grade.less
theme/bootstrapbase/less/moodle/modules.less
theme/bootstrapbase/less/moodle/question.less
theme/bootstrapbase/less/moodle/reports.less
theme/bootstrapbase/less/moodle/responsive.less
theme/bootstrapbase/less/moodle/undo.less
theme/bootstrapbase/style/moodle.css
theme/clean/config.php
theme/clean/style/custom.css
theme/index.php
theme/more/config.php
theme/more/style/custom.css
theme/upgrade.txt
user/externallib.php
user/tests/externallib_test.php
version.php
webservice/externallib.php
webservice/tests/externallib_test.php
webservice/upgrade.txt

index 61d9bf6..409123a 100644 (file)
@@ -81,7 +81,6 @@
         "selector-root-no-composition": true,
         "selector-type-case": "lower",
         "selector-type-no-unknown": true,
-        "shorthand-property-no-redundant-values": [null, { "severity": "warning" }],
         "string-no-newline": true,
         "time-no-imperceptible": true,
         "unit-blacklist": ["pt"],
index 08b28b6..8fc47df 100644 (file)
@@ -184,15 +184,6 @@ module.exports = function(grunt) {
                     syntax: 'less',
                     configOverrides: {
                         rules: {
-                            // TODO: MDL-55165 -Enable these rules once we make output-changing changes to less.
-                            "declaration-block-no-ignored-properties": null,
-                            "value-keyword-case": null,
-                            "declaration-block-no-duplicate-properties": null,
-                            "declaration-block-no-shorthand-property-overrides": null,
-                            "selector-type-no-unknown": null,
-                            "length-zero-no-unit": null,
-                            "color-hex-case": null,
-                            "color-hex-length": null,
                             // These rules have to be disabled in .stylelintrc for scss compat.
                             "at-rule-no-unknown": true,
                             "no-browser-hacks": [true, {"severity": "warning"}]
@@ -204,6 +195,18 @@ module.exports = function(grunt) {
             scss: {
                 options: {syntax: 'scss'},
                 src: ['*/**/*.scss']
+            },
+            css: {
+                src: ['*/**/*.css'],
+                options: {
+                    configOverrides: {
+                        rules: {
+                            // These rules have to be disabled in .stylelintrc for scss compat.
+                            "at-rule-no-unknown": true,
+                            "no-browser-hacks": [true, {"severity": "warning"}]
+                        }
+                    }
+                }
             }
         }
     });
@@ -358,7 +361,7 @@ module.exports = function(grunt) {
     grunt.registerTask('js', ['amd', 'yui']);
 
     // Register CSS taks.
-    grunt.registerTask('css', ['stylelint:scss', 'stylelint:less', 'less:bootstrapbase']);
+    grunt.registerTask('css', ['stylelint:scss', 'stylelint:less', 'less:bootstrapbase', 'stylelint:css']);
 
     // Register the startup task.
     grunt.registerTask('startup', 'Run the correct tasks for the current directory', tasks.startup);
index d0a6531..80a8047 100644 (file)
@@ -13,10 +13,6 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configcheckbox('enablesafebrowserintegration', new lang_string('enablesafebrowserintegration', 'admin'), new lang_string('configenablesafebrowserintegration', 'admin'), 0));
 
     $temp->add(new admin_setting_configcheckbox('dndallowtextandlinks', new lang_string('dndallowtextandlinks', 'admin'), new lang_string('configdndallowtextandlinks', 'admin'), 0));
-    // The CSS optimiser setting. When changed we need to reset the theme caches in order to ensure they are regenerated through the optimiser.
-    $enablecssoptimiser = new admin_setting_configcheckbox('enablecssoptimiser', new lang_string('enablecssoptimiser','admin'), new lang_string('enablecssoptimiser_desc','admin'), 0);
-    $enablecssoptimiser->set_updatedcallback('theme_reset_all_caches');
-    $temp->add($enablecssoptimiser);
 
     $ADMIN->add('experimental', $temp);
 
index 9223cab..2dbff19 100644 (file)
@@ -320,22 +320,7 @@ if ($hassiteconfig) {
 
 /// Web services
     $ADMIN->add('modules', new admin_category('webservicesettings', new lang_string('webservices', 'webservice')));
-    // Mobile
-    $temp = new admin_settingpage('mobile', new lang_string('mobile','admin'), 'moodle/site:config', false);
-
-    // We should wait to the installation to finish since we depend on some configuration values that are set once
-    // the admin user profile is configured.
-    if (!during_initial_install()) {
-        $enablemobiledocurl = new moodle_url(get_docs_url('Enable_mobile_web_services'));
-        $enablemobiledoclink = html_writer::link($enablemobiledocurl, new lang_string('documentation'));
-        $default = is_https() ? 1 : 0;
-        $temp->add(new admin_setting_enablemobileservice('enablemobilewebservice',
-                new lang_string('enablemobilewebservice', 'admin'),
-                new lang_string('configenablemobilewebservice', 'admin', $enablemobiledoclink), $default));
-    }
 
-    $temp->add(new admin_setting_configtext('mobilecssurl', new lang_string('mobilecssurl', 'admin'), new lang_string('configmobilecssurl','admin'), '', PARAM_URL));
-    $ADMIN->add('webservicesettings', $temp);
     /// overview page
     $temp = new admin_settingpage('webservicesoverview', new lang_string('webservicesoverview', 'webservice'));
     $temp->add(new admin_setting_webservicesoverview());
index 277400b..bdb9fa5 100644 (file)
@@ -1,11 +1,23 @@
-#page-admin-tool-assignmentupgrade-listnotupgraded .tool_assignmentupgrade_upgradetable .c0 { display: none; }
-#page-admin-tool-assignmentupgrade-listnotupgraded.jsenabled .tool_assignmentupgrade_upgradetable .c0 { display: table-cell; }
+#page-admin-tool-assignmentupgrade-listnotupgraded .tool_assignmentupgrade_upgradetable .c0 {
+    display: none;
+}
+
+#page-admin-tool-assignmentupgrade-listnotupgraded.jsenabled .tool_assignmentupgrade_upgradetable .c0 {
+    display: table-cell;
+}
 /*
 .gradingbatchoperationsform { display: none; }
 .jsenabled .gradingbatchoperationsform { display: block; }
 */
 
-#page-admin-tool-assignmentupgrade-listnotupgraded .tool_assignmentupgrade_upgradetable tr.selectedrow td { background-color: #ffeecc; }
-#page-admin-tool-assignmentupgrade-listnotupgraded .tool_assignmentupgrade_upgradetable tr.unselectedrow td { background-color: white; }
+#page-admin-tool-assignmentupgrade-listnotupgraded .tool_assignmentupgrade_upgradetable tr.selectedrow td {
+    background-color: #fec;
+}
+
+#page-admin-tool-assignmentupgrade-listnotupgraded .tool_assignmentupgrade_upgradetable tr.unselectedrow td {
+    background-color: white;
+}
 
-#page-admin-tool-assignmentupgrade-listnotupgraded .tool_assignmentupgrade_paginationform .hidden { display: none; }
+#page-admin-tool-assignmentupgrade-listnotupgraded .tool_assignmentupgrade_paginationform .hidden {
+    display: none;
+}
index d7ca4d1..8c5634a 100644 (file)
@@ -1,5 +1,25 @@
-.steps-definitions{border-style:solid;border-width:1px;border-color:#BBB;padding:5px;margin:auto;width:50%;}
-.steps-definitions .step{margin: 10px 0px 10px 0px;}
-.steps-definitions .stepdescription{color:#bf8c12;}
-.steps-definitions .steptype{color:#1467a6;margin-right: 5px;}
-.steps-definitions .stepregex{color:#060;}
+.steps-definitions {
+    border-style: solid;
+    border-width: 1px;
+    border-color: #bbb;
+    padding: 5px;
+    margin: auto;
+    width: 50%;
+}
+
+.steps-definitions .step {
+    margin: 10px 0 10px 0;
+}
+
+.steps-definitions .stepdescription {
+    color: #bf8c12;
+}
+
+.steps-definitions .steptype {
+    color: #1467a6;
+    margin-right: 5px;
+}
+
+.steps-definitions .stepregex {
+    color: #060;
+}
index a3d1046..fe2b5a7 100644 (file)
@@ -1,32 +1,74 @@
-.path-admin-tool-capability .comparisontable {margin-top:150px;}
+.path-admin-tool-capability .comparisontable {
+    margin-top: 150px;
+}
+
 .path-admin-tool-capability .comparisontable th,
-.path-admin-tool-capability .comparisontable td {vertical-align:middle;padding:0.4em 0.5em 0.3em;}
-.path-admin-tool-capability .comparisontable thead th {vertical-align:bottom;background:none;}
-.path-admin-tool-capability .comparisontable thead th div {position:relative;}
-.path-admin-tool-capability .comparisontable thead th div > a {
-    position:absolute;
-    top:-1.75em;
-    left:1em;
-    width:150px;
-    text-align:left;
-    margin-bottom:1em;
-    text-indent:-1.45em;
+.path-admin-tool-capability .comparisontable td {
+    vertical-align: middle;
+    padding: 0.4em 0.5em 0.3em;
+}
 
-    -webkit-transform-origin: top left;
-    -moz-transform-origin:    top left;
-    -ms-transform-origin:     top left;
-    -o-transform-origin:      top left;
+.path-admin-tool-capability .comparisontable thead th {
+    vertical-align: bottom;
+    background: none;
+}
 
+.path-admin-tool-capability .comparisontable thead th div {
+    position: relative;
+}
+
+.path-admin-tool-capability .comparisontable thead th div > a {
+    position: absolute;
+    top: -1.75em;
+    left: 1em;
+    width: 150px;
+    text-align: left;
+    margin-bottom: 1em;
+    text-indent: -1.45em;
+    -webkit-transform-origin: top left;
+    -moz-transform-origin: top left;
+    -ms-transform-origin: top left;
+    -o-transform-origin: top left;
     -webkit-transform: rotate(315deg);
-    -moz-transform:    rotate(315deg);
-    -ms-transform:     rotate(315deg);
-    -o-transform:      rotate(315deg);
-}
-.path-admin-tool-capability .comparisontable tbody th {background-color:#EEE;text-align:right;border:1px solid #DFDFDF;}
-.path-admin-tool-capability .comparisontable tbody th span {display:block;color:#666;font-size:80%;}
-.path-admin-tool-capability .comparisontable tbody td {border:1px solid #DFDFDF;}
-
-.path-admin-tool-capability .comparisontable .inherit {color:#666}
-.path-admin-tool-capability .comparisontable .allow {background-color:#006600;font-weight:bold;color:white;}
-.path-admin-tool-capability .comparisontable .prevent {background-color:#ad6704;font-weight:bold;color:white;}
-.path-admin-tool-capability .comparisontable .prohibit {background-color:#880000;font-weight:bold;color:white;}
\ No newline at end of file
+    -moz-transform: rotate(315deg);
+    -ms-transform: rotate(315deg);
+    -o-transform: rotate(315deg);
+}
+
+.path-admin-tool-capability .comparisontable tbody th {
+    background-color: #eee;
+    text-align: right;
+    border: 1px solid #dfdfdf;
+}
+
+.path-admin-tool-capability .comparisontable tbody th span {
+    display: block;
+    color: #666;
+    font-size: 80%;
+}
+
+.path-admin-tool-capability .comparisontable tbody td {
+    border: 1px solid #dfdfdf;
+}
+
+.path-admin-tool-capability .comparisontable .inherit {
+    color: #666;
+}
+
+.path-admin-tool-capability .comparisontable .allow {
+    background-color: #060;
+    font-weight: bold;
+    color: white;
+}
+
+.path-admin-tool-capability .comparisontable .prevent {
+    background-color: #ad6704;
+    font-weight: bold;
+    color: white;
+}
+
+.path-admin-tool-capability .comparisontable .prohibit {
+    background-color: #800;
+    font-weight: bold;
+    color: white;
+}
\ No newline at end of file
index 963b289..9f9fa98 100644 (file)
@@ -11,7 +11,6 @@
     display: inline;
 }
 
-
 .path-admin-tool-customlang .mform.filterform {
     width: 70%;
     margin-left: auto;
index 6a625f6..ec486af 100644 (file)
@@ -1,4 +1,3 @@
-
 .path-admin-tool-health div#healthnoproblemsfound {
     width: 60%;
     margin: auto;
@@ -6,59 +5,72 @@
     border: 1px solid black;
     -moz-border-radius: 6px;
 }
+
 .path-admin-tool-health dl.healthissues {
     width: 60%;
     margin: auto;
 }
+
 .path-admin-tool-health dl.critical dt,
 .path-admin-tool-health dl.critical dd {
     background-color: #a71501;
 }
+
 .path-admin-tool-health dl.significant dt,
 .path-admin-tool-health dl.significant dd {
     background-color: #d36707;
 }
+
 .path-admin-tool-health dl.annoyance dt,
 .path-admin-tool-health dl.annoyance dd {
     background-color: #dba707;
 }
+
 .path-admin-tool-health dl.notice dt,
 .path-admin-tool-health dl.notice dd {
     background-color: #e5db36;
 }
+
 .path-admin-tool-health dl dt.solution,
 .path-admin-tool-health dl dd.solution,
 .path-admin-tool-health div#healthnoproblemsfound {
-    background-color: #5BB83E;
+    background-color: #5bb83e;
 }
+
 .path-admin-tool-health dl.healthissues dt,
 .path-admin-tool-health dl.healthissues dd {
-    margin: 0px;
+    margin: 0;
     padding: 1em;
     border: 1px solid black;
 }
+
 .path-admin-tool-health dl.healthissues dt {
     font-weight: bold;
     border-bottom: 0;
     padding-bottom: 0.5em;
 }
+
 .path-admin-tool-health dl.healthissues dd {
     border-top: 0;
     padding-top: 0.5em;
     margin-bottom: 10px;
 }
+
 .path-admin-tool-health dl.healthissues dd form {
     margin-top: 0.5em;
     text-align: right;
 }
+
 .path-admin-tool-health form#healthformreturn {
     text-align: center;
     margin: 2em;
 }
+
 .path-admin-tool-health dd.solution p {
-    padding: 0px;
-    margin: 1em 0px;
+    padding: 0;
+    margin: 1em 0;
 }
+
 .path-admin-tool-health dd.solution li {
     margin-top: 1em;
 }
index a499c0d..42d8a69 100644 (file)
@@ -1,5 +1,9 @@
-
-#page-admin-tool-langimport-index .generalbox table {margin:auto;width:100%;}
+#page-admin-tool-langimport-index .generalbox table {
+    margin: auto;
+    width: 100%;
+}
 
 #page-admin-tool-langimport-index .generalbox,
-#page-admin-tool-langimport-index .generalbox table {text-align: center;}
+#page-admin-tool-langimport-index .generalbox table {
+    text-align: center;
+}
index 21ebff6..e811b9a 100644 (file)
@@ -4,58 +4,67 @@
 .path-admin-tool-lp [data-region="competencylinktree"] ul li {
     list-style-type: none;
 }
+
 .path-admin-tool-lp .progresstext {
     display: inline-block;
     vertical-align: top;
 }
+
 .path-admin-tool-lp .progress {
     width: 100%;
     display: inline-block;
 }
+
 .path-admin-tool-lp .progress .bar {
     min-width: 3em;
 }
+
 .path-admin-tool-lp [data-region="managecompetencies"] ul[data-enhance="tree"],
 .path-admin-tool-lp [data-region="plans"] ul[data-enhance="tree"],
 .path-admin-tool-lp [data-region="competencylinktree"] ul[data-enhance="linktree"],
 .path-admin-tool-lp [data-region="competencymovetree"] ul[data-enhance="movetree"] {
     border: 1px solid #ccc;
-    box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
-    transition: border linear .2s,box-shadow linear .2s;
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+    transition: border linear .2s, box-shadow linear .2s;
     border-radius: 4px;
     padding-left: 20px;
     padding-right: 20px;
     margin-left: 10px;
     margin-right: 10px;
 }
+
 .path-admin-tool-lp [data-region="managecompetencies"] ul,
 .path-admin-tool-lp [data-region="plans"] ul,
 .path-admin-tool-lp [data-region="competencylinktree"] ul,
 .path-admin-tool-lp [data-region="competencymovetree"] ul {
     cursor: pointer;
 }
-.path-admin-tool-lp [data-region="competencylinktree"] ul li>span,
-.path-admin-tool-lp [data-region="competencymovetree"] ul li>span,
-.path-admin-tool-lp [data-region="plans"] ul li>span,
-.path-admin-tool-lp [data-region="managecompetencies"] ul li>span {
+
+.path-admin-tool-lp [data-region="competencylinktree"] ul li > span,
+.path-admin-tool-lp [data-region="competencymovetree"] ul li > span,
+.path-admin-tool-lp [data-region="plans"] ul li > span,
+.path-admin-tool-lp [data-region="managecompetencies"] ul li > span {
     padding-top: 2px;
     padding-bottom: 2px;
     padding-left: 4px;
     padding-right: 4px;
     border-radius: 4px;
 }
-.path-admin-tool-lp [data-region="competencylinktree"] ul [aria-selected="true"]>span,
-.path-admin-tool-lp [data-region="competencymovetree"] ul [aria-selected="true"]>span,
-.path-admin-tool-lp [data-region="plans"] ul [aria-selected="true"]>span,
-.path-admin-tool-lp [data-region="managecompetencies"] ul [aria-selected="true"]>span {
+
+.path-admin-tool-lp [data-region="competencylinktree"] ul [aria-selected="true"] > span,
+.path-admin-tool-lp [data-region="competencymovetree"] ul [aria-selected="true"] > span,
+.path-admin-tool-lp [data-region="plans"] ul [aria-selected="true"] > span,
+.path-admin-tool-lp [data-region="managecompetencies"] ul [aria-selected="true"] > span {
     background-color: #dfdfdf;
 }
-.path-admin-tool-lp [data-region="competencylinktree"] ul [tabindex="0"]>span,
-.path-admin-tool-lp [data-region="competencymovetree"] ul [tabindex="0"]>span,
-.path-admin-tool-lp [data-region="plans"] ul [tabindex="0"]>span,
-.path-admin-tool-lp [data-region="managecompetencies"] ul [tabindex="0"]>span {
+
+.path-admin-tool-lp [data-region="competencylinktree"] ul [tabindex="0"] > span,
+.path-admin-tool-lp [data-region="competencymovetree"] ul [tabindex="0"] > span,
+.path-admin-tool-lp [data-region="plans"] ul [tabindex="0"] > span,
+.path-admin-tool-lp [data-region="managecompetencies"] ul [tabindex="0"] > span {
     border: 2px solid #0070a8;
 }
+
 .path-admin-tool-lp [data-region="filtercompetencies"] input {
     margin-left: 10px;
 }
@@ -65,7 +74,7 @@
     text-align: center;
 }
 
-.path-admin-tool-lp [data-region="competencylinktree"]>ul {
+.path-admin-tool-lp [data-region="competencylinktree"] > ul {
     overflow-y: auto;
     height: 400px;
 }
     display: table;
     width: 100%;
 }
+
 .path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-outcome"],
 .path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-type"] {
     display: table-row;
 }
+
 .path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-outcome"] label,
 .path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-type"] label {
     padding-right: 10px;
 }
+
 .path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-outcome"] label,
 .path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-outcome"] select,
 .path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-type"] label,
 .path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-type"] select {
     display: table-cell;
 }
+
 .path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-outcome"] select,
 .path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-type"] select,
 .path-admin-tool-lp [data-region="competencylinktree"] select {
     width: 100%;
 }
+
 .path-admin-tool-lp [data-region] .generaltable.fullwidth {
     clear: both;
 }
 .path-admin-tool-lp .competency-rule-points {
     margin-top: 10px;
 }
+
 .path-admin-tool-lp .competency-rule-points table input {
     margin-bottom: 0;
 }
+
 .path-admin-tool-lp .competency-rule-points tr[data-competency] th {
     font-weight: normal;
 }
+
 .path-admin-tool-lp .competency-rule-points input[type="number"] {
     width: 50px;
 }
 .competency-heading {
     margin-bottom: 15px;
 }
+
 .competency-heading h4 {
     margin: 0;
 }
 .tool-lp-sub-menu li {
     float: none;
 }
+
 .tool-lp-menu .tool-lp-sub-menu[aria-hidden=false] {
     display: block;
 }
 
 /** This highlighting is copied from bootstrap - but can be overridden by a theme */
 .tool-lp-menu .tool-lp-sub-menu .menu-focus a {
-    color: #fff ;
+    color: #fff;
     text-decoration: none;
     background-color: #00699e;
-    background-image: linear-gradient(to bottom,#0070a8,#005f8f);
+    background-image: linear-gradient(to bottom, #0070a8, #005f8f);
     background-repeat: repeat-x;
 }
 
 /** check box and radio button on configure scale dialogue */
 input[type="radio"].tool_lp_scale_default,
 input[type="checkbox"].tool_lp_scale_proficient {
-    margin-top: 0px;
+    margin-top: 0;
 }
 
 /** User evidence */
@@ -185,11 +204,13 @@ input[type="checkbox"].tool_lp_scale_proficient {
     margin: 10px 20px;
     list-style: none;
 }
+
 .user-evidence-competencies,
 .user-evidence-documents li {
     margin-bottom: 5px;
     word-break: break-all;
 }
+
 [data-region="user-evidence-list"] .user-evidence-competencies,
 [data-region="user-evidence-list"] .user-evidence-documents {
     margin: 0;
@@ -199,9 +220,11 @@ input[type="checkbox"].tool_lp_scale_proficient {
 .user-competency-course-navigation select {
     display: none;
 }
+
 .user-competency-course-navigation {
     width: 240px;
 }
+
 .user-competency-course-navigation span {
     max-width: 100%;
     overflow: hidden;
diff --git a/admin/tool/lpimportcsv/classes/form/export.php b/admin/tool/lpimportcsv/classes/form/export.php
new file mode 100644 (file)
index 0000000..1eac37f
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the form export a competency framework.
+ *
+ * @package   tool_lpimportcsv
+ * @copyright 2015 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_lpimportcsv\form;
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+use moodleform;
+use context_system;
+use core_competency\api;
+
+require_once($CFG->libdir.'/formslib.php');
+
+/**
+ * Export Competency framework form.
+ *
+ * @package   tool_lpimportcsv
+ * @copyright 2015 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class export extends moodleform {
+
+    /**
+     * Define the form - called by parent constructor
+     */
+    public function definition() {
+        $mform = $this->_form;
+
+        $context = context_system::instance();
+        $frameworks = api::list_frameworks('shortname', 'ASC', null, null, $context);
+        $options = array();
+        foreach ($frameworks as $framework) {
+
+            $options[$framework->get_id()] = $framework->get_shortname();
+        }
+        if (empty($options)) {
+            $mform->addElement('static', 'frameworkid', '', get_string('noframeworks', 'tool_lpimportcsv'));
+        } else {
+            $mform->addElement('select', 'frameworkid', get_string('competencyframework', 'tool_lp'), $options);
+            $mform->setType('frameworkid', PARAM_INT);
+            $mform->addRule('frameworkid', null, 'required', null, 'client');
+            $this->add_action_buttons(true, get_string('export', 'tool_lpimportcsv'));
+        }
+        $mform->setDisableShortforms();
+    }
+
+}
diff --git a/admin/tool/lpimportcsv/classes/form/import.php b/admin/tool/lpimportcsv/classes/form/import.php
new file mode 100644 (file)
index 0000000..d2c8071
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the form for importing a framework from a file.
+ *
+ * @package   tool_lpimportcsv
+ * @copyright 2015 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_lpimportcsv\form;
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+use moodleform;
+use core_competency\api;
+use core_text;
+use csv_import_reader;
+
+require_once($CFG->libdir.'/formslib.php');
+
+/**
+ * Import Competency framework form.
+ *
+ * @package   tool_lpimportcsv
+ * @copyright 2015 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class import extends moodleform {
+
+    /**
+     * Define the form - called by parent constructor
+     */
+    public function definition() {
+        global $CFG;
+        require_once($CFG->libdir . '/csvlib.class.php');
+
+        $mform = $this->_form;
+        $element = $mform->createElement('filepicker', 'importfile', get_string('importfile', 'tool_lpimportcsv'));
+        $mform->addElement($element);
+        $mform->addRule('importfile', null, 'required');
+        $mform->addElement('hidden', 'confirm', 0);
+        $mform->setType('confirm', PARAM_BOOL);
+
+        $choices = csv_import_reader::get_delimiter_list();
+        $mform->addElement('select', 'delimiter_name', get_string('csvdelimiter', 'tool_lpimportcsv'), $choices);
+        if (array_key_exists('cfg', $choices)) {
+            $mform->setDefault('delimiter_name', 'cfg');
+        } else if (get_string('listsep', 'langconfig') == ';') {
+            $mform->setDefault('delimiter_name', 'semicolon');
+        } else {
+            $mform->setDefault('delimiter_name', 'comma');
+        }
+        $mform->addHelpButton('delimiter_name', 'csvdelimiter', 'tool_lpimportcsv');
+
+        $choices = core_text::get_encodings();
+        $mform->addElement('select', 'encoding', get_string('encoding', 'tool_lpimportcsv'), $choices);
+        $mform->setDefault('encoding', 'UTF-8');
+        $mform->addHelpButton('encoding', 'encoding', 'tool_lpimportcsv');
+
+        $this->add_action_buttons(false, get_string('import', 'tool_lpimportcsv'));
+    }
+
+    /**
+     * Display an error on the import form.
+     * @param string $msg
+     */
+    public function set_import_error($msg) {
+        $mform = $this->_form;
+
+        $mform->setElementError('importfile', $msg);
+    }
+
+}
diff --git a/admin/tool/lpimportcsv/classes/form/import_confirm.php b/admin/tool/lpimportcsv/classes/form/import_confirm.php
new file mode 100644 (file)
index 0000000..33d5918
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the form to confirm the import options for a framework.
+ *
+ * @package   tool_lpimportcsv
+ * @copyright 2015 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_lpimportcsv\form;
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+use moodleform;
+use core_competency\api;
+use tool_lpimportcsv\framework_importer;
+
+require_once($CFG->libdir.'/formslib.php');
+
+/**
+ * Import Competency framework form.
+ *
+ * @package   tool_lpimportcsv
+ * @copyright 2015 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class import_confirm extends moodleform {
+
+    /**
+     * Define the form - called by parent constructor
+     */
+    public function definition() {
+        $importer = $this->_customdata;
+
+        $mform = $this->_form;
+        $mform->addElement('hidden', 'confirm', 1);
+        $mform->setType('confirm', PARAM_BOOL);
+        $mform->addElement('hidden', 'importid', $importer->get_importid());
+        $mform->setType('importid', PARAM_INT);
+
+        $requiredheaders = $importer->list_required_headers();
+        $foundheaders = $importer->list_found_headers();
+
+        if (empty($foundheaders)) {
+            $foundheaders = range(0, count($requiredheaders));
+        }
+        $foundheaders[-1] = get_string('none');
+
+        foreach ($requiredheaders as $index => $requiredheader) {
+            $mform->addElement('select', 'header' . $index, $requiredheader, $foundheaders);
+            if (isset($foundheaders[$index])) {
+                $mform->setDefault('header' . $index, $index);
+            } else {
+                $mform->setDefault('header' . $index, -1);
+            }
+        }
+
+        $this->add_action_buttons(true, get_string('confirm', 'tool_lpimportcsv'));
+    }
+}
diff --git a/admin/tool/lpimportcsv/classes/framework_exporter.php b/admin/tool/lpimportcsv/classes/framework_exporter.php
new file mode 100644 (file)
index 0000000..e507dd9
--- /dev/null
@@ -0,0 +1,147 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the csv exporter for a competency framework.
+ *
+ * @package   tool_lpimportcsv
+ * @copyright 2015 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_lpimportcsv;
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+use core_competency\api;
+use stdClass;
+use csv_export_writer;
+
+/**
+ * Export Competency framework.
+ *
+ * @package   tool_lpimportcsv
+ * @copyright 2015 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class framework_exporter {
+
+    /** @var $framework \core_competency\competency_framework */
+    protected $framework = null;
+
+    /** @var $error string */
+    protected $error = '';
+
+    /**
+     * Constructor
+     * @param int $frameworkid The framework id
+     */
+    public function __construct($frameworkid) {
+        $this->framework = api::read_framework($frameworkid);
+    }
+
+    /**
+     * Export all the competencies from this framework to a csv file.
+     */
+    public function export() {
+        global $CFG;
+        require_once($CFG->libdir . '/csvlib.class.php');
+
+        $writer = new csv_export_writer();
+        $filename = clean_param($this->framework->get_shortname() . '-' . $this->framework->get_idnumber(), PARAM_FILE);
+        $writer->set_filename($filename);
+
+        $headers = framework_importer::list_required_headers();
+
+        $writer->add_data($headers);
+
+        // Order and number of columns must match framework_importer::list_required_headers().
+        $row = array(
+            '',
+            $this->framework->get_idnumber(),
+            $this->framework->get_shortname(),
+            $this->framework->get_description(),
+            $this->framework->get_descriptionformat(),
+            $this->framework->get_scale()->compact_items(),
+            $this->framework->get_scaleconfiguration(),
+            '',
+            '',
+            '',
+            '',
+            '',
+            true,
+            implode(',', $this->framework->get_taxonomies())
+        );
+        $writer->add_data($row);
+
+        $filters = array('competencyframeworkid' => $this->framework->get_id());
+        $competencies = api::list_competencies($filters);
+        // Index by id so we can lookup parents.
+        $indexed = array();
+        foreach ($competencies as $competency) {
+            $indexed[$competency->get_id()] = $competency;
+        }
+        foreach ($competencies as $competency) {
+            $parentidnumber = '';
+            if ($competency->get_parentid() > 0) {
+                $parent = $indexed[$competency->get_parentid()];
+                $parentidnumber = $parent->get_idnumber();
+            }
+
+            $scalevalues = '';
+            $scaleconfig = '';
+            if ($competency->get_scaleid() !== null) {
+                $scalevalues = $competency->get_scale()->compact_items();
+                $scaleconfig = $competency->get_scaleconfiguration();
+            }
+
+            $ruleconfig = $competency->get_ruleconfig();
+            if ($ruleconfig === null) {
+                $ruleconfig = "null";
+            }
+
+            $allrelated = $competency->get_related_competencies();
+
+            $relatedidnumbers = array();
+            foreach ($allrelated as $onerelated) {
+                $relatedidnumbers[] = str_replace(',', '%2C', $onerelated->get_idnumber());
+            }
+            $relatedidnumbers = implode(',', $relatedidnumbers);
+
+            // Order and number of columns must match framework_importer::list_required_headers().
+            $row = array(
+                $parentidnumber,
+                $competency->get_idnumber(),
+                $competency->get_shortname(),
+                $competency->get_description(),
+                $competency->get_descriptionformat(),
+                $scalevalues,
+                $scaleconfig,
+                $competency->get_ruletype(),
+                $competency->get_ruleoutcome(),
+                $ruleconfig,
+                $relatedidnumbers,
+                $competency->get_id(),
+                false,
+                ''
+            );
+
+            $writer->add_data($row);
+        }
+
+        $writer->download_file();
+    }
+}
diff --git a/admin/tool/lpimportcsv/classes/framework_importer.php b/admin/tool/lpimportcsv/classes/framework_importer.php
new file mode 100644 (file)
index 0000000..6613072
--- /dev/null
@@ -0,0 +1,455 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the class to import a competency framework.
+ *
+ * @package   tool_lpimportcsv
+ * @copyright 2015 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_lpimportcsv;
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+use core_competency\api;
+use grade_scale;
+use stdClass;
+use context_system;
+use csv_import_reader;
+
+/**
+ * This file contains the class to import a competency framework.
+ *
+ * @package   tool_lpimportcsv
+ * @copyright 2015 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class framework_importer {
+
+    /** @var string $error The errors message from reading the xml */
+    protected $error = '';
+
+    /** @var array $flat The flat competencies tree */
+    protected $flat = array();
+    /** @var array $framework The framework info */
+    protected $framework = array();
+    protected $mappings = array();
+    protected $importid = 0;
+    protected $importer = null;
+    protected $foundheaders = array();
+
+    /**
+     * Store an error message for display later
+     * @param string $msg
+     */
+    public function fail($msg) {
+        $this->error = $msg;
+        return false;
+    }
+
+    /**
+     * Get the CSV import id
+     * @return string The import id.
+     */
+    public function get_importid() {
+        return $this->importid;
+    }
+
+    /**
+     * Get the list of headers required for import.
+     * @return array The headers (lang strings)
+     */
+    public static function list_required_headers() {
+        return array(
+            get_string('parentidnumber', 'tool_lpimportcsv'),
+            get_string('idnumber', 'tool_lpimportcsv'),
+            get_string('shortname', 'tool_lpimportcsv'),
+            get_string('description', 'tool_lpimportcsv'),
+            get_string('descriptionformat', 'tool_lpimportcsv'),
+            get_string('scalevalues', 'tool_lpimportcsv'),
+            get_string('scaleconfiguration', 'tool_lpimportcsv'),
+            get_string('ruletype', 'tool_lpimportcsv'),
+            get_string('ruleoutcome', 'tool_lpimportcsv'),
+            get_string('ruleconfig', 'tool_lpimportcsv'),
+            get_string('relatedidnumbers', 'tool_lpimportcsv'),
+            get_string('exportid', 'tool_lpimportcsv'),
+            get_string('isframework', 'tool_lpimportcsv'),
+            get_string('taxonomy', 'tool_lpimportcsv'),
+        );
+    }
+
+    /**
+     * Get the list of headers found in the import.
+     * @return array The found headers (names from import)
+     */
+    public function list_found_headers() {
+        return $this->foundheaders;
+    }
+
+    /**
+     * Read the data from the mapping form.
+     * @param array The mapping data.
+     */
+    protected function read_mapping_data($data) {
+        if ($data) {
+            return array(
+                'parentidnumber' => $data->header0,
+                'idnumber' => $data->header1,
+                'shortname' => $data->header2,
+                'description' => $data->header3,
+                'descriptionformat' => $data->header4,
+                'scalevalues' => $data->header5,
+                'scaleconfiguration' => $data->header6,
+                'ruletype' => $data->header7,
+                'ruleoutcome' => $data->header8,
+                'ruleconfig' => $data->header9,
+                'relatedidnumbers' => $data->header10,
+                'exportid' => $data->header11,
+                'isframework' => $data->header12,
+                'taxonomies' => $data->header13
+            );
+        } else {
+            return array(
+                'parentidnumber' => 0,
+                'idnumber' => 1,
+                'shortname' => 2,
+                'description' => 3,
+                'descriptionformat' => 4,
+                'scalevalues' => 5,
+                'scaleconfiguration' => 6,
+                'ruletype' => 7,
+                'ruleoutcome' => 8,
+                'ruleconfig' => 9,
+                'relatedidnumbers' => 10,
+                'exportid' => 11,
+                'isframework' => 12,
+                'taxonomies' => 13
+            );
+        }
+    }
+
+    /**
+     * Get the a column from the imported data.
+     * @param array The imported raw row
+     * @param index The column index we want
+     * @return string The column data.
+     */
+    protected function get_column_data($row, $index) {
+        if ($index < 0) {
+            return '';
+        }
+        return isset($row[$index]) ? $row[$index] : '';
+    }
+
+    /**
+     * Constructor - parses the raw text for sanity.
+     * @param string $text The raw csv text.
+     * @param string $encoding The encoding of the csv file.
+     * @param string delimiter The specified delimiter for the file.
+     * @param string importid The id of the csv import.
+     * @param array mappingdata The mapping data from the import form.
+     */
+    public function __construct($text = null, $encoding = null, $delimiter = null, $importid = 0, $mappingdata = null) {
+        global $CFG;
+
+        // The format of our records is:
+        // Parent ID number, ID number, Shortname, Description, Description format, Scale values, Scale configuration,
+        // Rule type, Rule outcome, Rule config, Is framework, Taxonomy.
+
+        // The idnumber is concatenated with the category names.
+        require_once($CFG->libdir . '/csvlib.class.php');
+
+        $type = 'competency_framework';
+
+        if (!$importid) {
+            if ($text === null) {
+                return;
+            }
+            $this->importid = csv_import_reader::get_new_iid($type);
+
+            $this->importer = new csv_import_reader($this->importid, $type);
+
+            if (!$this->importer->load_csv_content($text, $encoding, $delimiter)) {
+                $this->fail(get_string('invalidimportfile', 'tool_lpimportcsv'));
+                $this->importer->cleanup();
+                return;
+            }
+
+        } else {
+            $this->importid = $importid;
+
+            $this->importer = new csv_import_reader($this->importid, $type);
+        }
+
+        if (!$this->importer->init()) {
+            $this->fail(get_string('invalidimportfile', 'tool_lpimportcsv'));
+            $this->importer->cleanup();
+            return;
+        }
+
+        $this->foundheaders = $this->importer->get_columns();
+
+        $domainid = 1;
+
+        $flat = array();
+        $framework = null;
+
+        while ($row = $this->importer->next()) {
+            $mapping = $this->read_mapping_data($mappingdata);
+
+            $parentidnumber = $this->get_column_data($row, $mapping['parentidnumber']);
+            $idnumber = $this->get_column_data($row, $mapping['idnumber']);
+            $shortname = $this->get_column_data($row, $mapping['shortname']);
+            $description = $this->get_column_data($row, $mapping['description']);
+            $descriptionformat = $this->get_column_data($row, $mapping['descriptionformat']);
+            $scalevalues = $this->get_column_data($row, $mapping['scalevalues']);
+            $scaleconfiguration = $this->get_column_data($row, $mapping['scaleconfiguration']);
+            $ruletype = $this->get_column_data($row, $mapping['ruletype']);
+            $ruleoutcome = $this->get_column_data($row, $mapping['ruleoutcome']);
+            $ruleconfig = $this->get_column_data($row, $mapping['ruleconfig']);
+            $relatedidnumbers = $this->get_column_data($row, $mapping['relatedidnumbers']);
+            $exportid = $this->get_column_data($row, $mapping['exportid']);
+            $isframework = $this->get_column_data($row, $mapping['isframework']);
+            $taxonomies = $this->get_column_data($row, $mapping['taxonomies']);
+
+            if ($isframework) {
+                $framework = new stdClass();
+                $framework->idnumber = shorten_text(clean_param($idnumber, PARAM_TEXT), 100);
+                $framework->shortname = shorten_text(clean_param($shortname, PARAM_TEXT), 100);
+                $framework->description = clean_param($description, PARAM_RAW);
+                $framework->descriptionformat = clean_param($descriptionformat, PARAM_INT);
+                $framework->scalevalues = $scalevalues;
+                $framework->scaleconfiguration = $scaleconfiguration;
+                $framework->taxonomies = $taxonomies;
+                $framework->children = array();
+            } else {
+                $competency = new stdClass();
+                $competency->parentidnumber = clean_param($parentidnumber, PARAM_TEXT);
+                $competency->idnumber = shorten_text(clean_param($idnumber, PARAM_TEXT), 100);
+                $competency->shortname = shorten_text(clean_param($shortname, PARAM_TEXT), 100);
+                $competency->description = clean_param($description, PARAM_RAW);
+                $competency->descriptionformat = clean_param($descriptionformat, PARAM_INT);
+                $competency->ruletype = $ruletype;
+                $competency->ruleoutcome = clean_param($ruleoutcome, PARAM_INT);
+                $competency->ruleconfig = $ruleconfig;
+                $competency->relatedidnumbers = $relatedidnumbers;
+                $competency->exportid = $exportid;
+                $competency->scalevalues = $scalevalues;
+                $competency->scaleconfiguration = $scaleconfiguration;
+                $competency->children = array();
+                $flat[$idnumber] = $competency;
+            }
+        }
+        $this->flat = $flat;
+        $this->framework = $framework;
+
+        $this->importer->close();
+        if ($this->framework == null) {
+            $this->fail(get_string('invalidimportfile', 'tool_lpimportcsv'));
+            return;
+        } else {
+            // Build a tree from this flat list.
+            $this->add_children($this->framework, '');
+        }
+    }
+
+    /**
+     * Add a competency to the parent with the specified idnumber.
+     *
+     * @param competency $node (pass by reference)
+     * @param string $parentidnumber Add this competency to the parent with this idnumber.
+     */
+    public function add_children(& $node, $parentidnumber) {
+        foreach ($this->flat as $competency) {
+            if ($competency->parentidnumber == $parentidnumber) {
+                $node->children[] = $competency;
+                $this->add_children($competency, $competency->idnumber);
+            }
+        }
+    }
+
+    /**
+     * Get parse errors.
+     * @return array of errors from parsing the xml.
+     */
+    public function get_error() {
+        return $this->error;
+    }
+
+    /**
+     * Recursive function to add a competency with all it's children.
+     *
+     * @param stdClass $record Raw data for the new competency
+     * @param competency $parent
+     * @param competency_framework $framework
+     */
+    public function create_competency($record, $parent, $framework) {
+        $competency = new stdClass();
+        $competency->competencyframeworkid = $framework->get_id();
+        $competency->shortname = $record->shortname;
+        if (!empty($record->description)) {
+            $competency->description = $record->description;
+            $competency->descriptionformat = $record->descriptionformat;
+        }
+        if ($record->scalevalues) {
+            $competency->scaleid = $this->get_scale_id($record->scalevalues, $competency->shortname);
+            $competency->scaleconfiguration = $this->get_scale_configuration($competency->scaleid, $record->scaleconfiguration);
+        }
+        if ($parent) {
+            $competency->parentid = $parent->get_id();
+        } else {
+            $competency->parentid = 0;
+        }
+        $competency->idnumber = $record->idnumber;
+
+        if (!empty($competency->idnumber) && !empty($competency->shortname)) {
+            $comp = api::create_competency($competency);
+            if ($record->exportid) {
+                $this->mappings[$record->exportid] = $comp;
+            }
+            $record->createdcomp = $comp;
+            foreach ($record->children as $child) {
+                $this->create_competency($child, $comp, $framework);
+            }
+
+            return $comp;
+        }
+        return false;
+    }
+
+    /**
+     * Recreate the scale config to point to a new scaleid.
+     * @param int $scaleid
+     * @param string $config json encoded scale data.
+     */
+    public function get_scale_configuration($scaleid, $config) {
+        $asarray = json_decode($config);
+        $asarray[0]->scaleid = $scaleid;
+        return json_encode($asarray);
+    }
+
+    /**
+     * Search for a global scale that matches this set of scalevalues.
+     * If one is not found it will be created.
+     * @param array $scalevalues
+     * @param string $competencyname (Used to create a new scale if required)
+     * @return int The id of the scale
+     */
+    public function get_scale_id($scalevalues, $competencyname) {
+        global $CFG, $USER;
+
+        require_once($CFG->libdir . '/gradelib.php');
+
+        $allscales = grade_scale::fetch_all_global();
+        $matchingscale = false;
+        foreach ($allscales as $scale) {
+            if ($scale->compact_items() == $scalevalues) {
+                $matchingscale = $scale;
+            }
+        }
+        if (!$matchingscale) {
+            // Create it.
+            $newscale = new grade_scale();
+            $newscale->name = get_string('competencyscale', 'tool_lpimportcsv', $competencyname);
+            $newscale->courseid = 0;
+            $newscale->userid = $USER->id;
+            $newscale->scale = $scalevalues;
+            $newscale->description = get_string('competencyscaledescription', 'tool_lpimportcsv');
+            $newscale->insert();
+            return $newscale->id;
+        }
+        return $matchingscale->id;
+    }
+
+    /**
+     * Walk through the idnumbers in the relatedidnumbers col and set the relations.
+     * @param stdClass $record
+     */
+    protected function set_related($record) {
+        $comp = $record->createdcomp;
+        if ($record->relatedidnumbers) {
+            $allidnumbers = explode(',', $record->relatedidnumbers);
+            foreach ($allidnumbers as $rawidnumber) {
+                $idnumber = str_replace('%2C', ',', $rawidnumber);
+
+                if (isset($this->flat[$idnumber])) {
+                    $relatedcomp = $this->flat[$idnumber]->createdcomp;
+                    api::add_related_competency($comp->get_id(), $relatedcomp->get_id());
+                }
+            }
+        }
+        foreach ($record->children as $child) {
+            $this->set_related($child);
+        }
+    }
+
+    /**
+     * Create any completion rule attached to this competency.
+     * @param stdClass $record
+     */
+    protected function set_rules($record) {
+        $comp = $record->createdcomp;
+        if ($record->ruletype) {
+            $class = $record->ruletype;
+            if (class_exists($class)) {
+                $oldruleconfig = $record->ruleconfig;
+                if ($oldruleconfig == "null") {
+                    $oldruleconfig = null;
+                }
+                $newruleconfig = $class::migrate_config($oldruleconfig, $this->mappings);
+                $comp->set_ruleconfig($newruleconfig);
+                $comp->set_ruletype($class);
+                $comp->set_ruleoutcome($record->ruleoutcome);
+                $comp->update();
+            }
+        }
+        foreach ($record->children as $child) {
+            $this->set_rules($child);
+        }
+    }
+
+    /**
+     * Do the job.
+     * @return competency_framework
+     */
+    public function import() {
+        $record = clone $this->framework;
+        unset($record->children);
+
+        $record->scaleid = $this->get_scale_id($record->scalevalues, $record->shortname);
+        $record->scaleconfiguration = $this->get_scale_configuration($record->scaleid, $record->scaleconfiguration);
+        unset($record->scalevalues);
+        $record->contextid = context_system::instance()->id;
+
+        $framework = api::create_framework($record);
+
+        // Now all the children.
+        foreach ($this->framework->children as $comp) {
+            $this->create_competency($comp, null, $framework);
+        }
+
+        // Now create the rules.
+        foreach ($this->framework->children as $record) {
+            $this->set_rules($record);
+            $this->set_related($record);
+        }
+
+        $this->importer->cleanup();
+        return $framework;
+    }
+}
diff --git a/admin/tool/lpimportcsv/continue.php b/admin/tool/lpimportcsv/continue.php
new file mode 100644 (file)
index 0000000..ef98a48
--- /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/>.
+
+/**
+ * Page to continue after an action.
+ *
+ * @package    tool_lpimportcsv
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+
+$pagetitle = get_string('pluginname', 'tool_lpimportcsv');
+
+$context = context_system::instance();
+
+$id = required_param('id', PARAM_INT);
+$url = new moodle_url("/admin/tool/lpimportcsv/index.php");
+$PAGE->set_context($context);
+$PAGE->set_url($url);
+$PAGE->set_title($pagetitle);
+$PAGE->set_pagelayout('admin');
+$PAGE->set_heading($pagetitle);
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading($pagetitle);
+$urlparams = ['competencyframeworkid' => $id, 'pagecontextid' => $context->id];
+$frameworksurl = new moodle_url('/admin/tool/lp/competencies.php', $urlparams);
+echo $OUTPUT->notification(get_string('competencyframeworkcreated', 'tool_lp'), 'notifysuccess');
+echo $OUTPUT->continue_button($frameworksurl);
+
+echo $OUTPUT->footer();
diff --git a/admin/tool/lpimportcsv/export.php b/admin/tool/lpimportcsv/export.php
new file mode 100644 (file)
index 0000000..a2d4b39
--- /dev/null
@@ -0,0 +1,57 @@
+<?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/>.
+
+/**
+ * Page to export a competency framework as a CSV.
+ *
+ * @package    tool_lpimportcsv
+ * @copyright  2016 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+
+$pagetitle = get_string('exportnavlink', 'tool_lpimportcsv');
+
+$context = context_system::instance();
+
+$url = new moodle_url("/admin/tool/lpimportcsv/export.php");
+$PAGE->set_context($context);
+$PAGE->set_url($url);
+$PAGE->set_title($pagetitle);
+$PAGE->set_pagelayout('admin');
+$PAGE->set_heading($pagetitle);
+
+$form = new \tool_lpimportcsv\form\export($url->out(false), array('persistent' => null, 'context' => $context));
+
+if ($form->is_cancelled()) {
+    redirect(new moodle_url('/admin/tool/lp/competencyframeworks.php', array('pagecontextid' => $context->id)));
+} else if ($data = $form->get_data()) {
+    require_sesskey();
+
+    $exporter = new \tool_lpimportcsv\framework_exporter($data->frameworkid);
+
+    $exporter->export();
+    die();
+}
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading($pagetitle);
+
+$form->display();
+
+echo $OUTPUT->footer();
diff --git a/admin/tool/lpimportcsv/index.php b/admin/tool/lpimportcsv/index.php
new file mode 100644 (file)
index 0000000..a2fc6cb
--- /dev/null
@@ -0,0 +1,83 @@
+<?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/>.
+
+/**
+ * Import a framework.
+ *
+ * @package    tool_lpimportcsv
+ * @copyright  2016 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+
+$pagetitle = get_string('pluginname', 'tool_lpimportcsv');
+
+$context = context_system::instance();
+
+$url = new moodle_url("/admin/tool/lpimportcsv/index.php");
+$PAGE->set_context($context);
+$PAGE->set_url($url);
+$PAGE->set_title($pagetitle);
+$PAGE->set_pagelayout('admin');
+$PAGE->set_heading($pagetitle);
+
+$form = null;
+if (optional_param('needsconfirm', 0, PARAM_BOOL)) {
+    $form = new \tool_lpimportcsv\form\import($url->out(false));
+} else if (optional_param('confirm', 0, PARAM_BOOL)) {
+    $importer = new \tool_lpimportcsv\framework_importer();
+    $form = new \tool_lpimportcsv\form\import_confirm(null, $importer);
+} else {
+    $form = new \tool_lpimportcsv\form\import($url->out(false));
+}
+
+if ($form->is_cancelled()) {
+    $form = new \tool_lpimportcsv\form\import($url->out(false));
+} else if ($data = $form->get_data()) {
+    require_sesskey();
+
+    if ($data->confirm) {
+        $importid = $data->importid;
+        $importer = new \tool_lpimportcsv\framework_importer(null, null, null, $importid, $data);
+
+        $error = $importer->get_error();
+        if ($error) {
+            $form = new \tool_lpimportcsv\form\import($url->out(false));
+            $form->set_import_error($error);
+        } else {
+            $framework = $importer->import();
+            redirect(new moodle_url('continue.php', array('id' => $framework->get_id())));
+            die();
+        }
+    } else {
+        $text = $form->get_file_content('importfile');
+        $encoding = $data->encoding;
+        $delimiter = $data->delimiter_name;
+        $importer = new \tool_lpimportcsv\framework_importer($text, $encoding, $delimiter);
+        $confirmform = new \tool_lpimportcsv\form\import_confirm(null, $importer);
+        $form = $confirmform;
+        $pagetitle = get_string('confirmcolumnmappings', 'tool_lpimportcsv');
+    }
+}
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading($pagetitle);
+
+$form->display();
+
+echo $OUTPUT->footer();
diff --git a/admin/tool/lpimportcsv/lang/en/tool_lpimportcsv.php b/admin/tool/lpimportcsv/lang/en/tool_lpimportcsv.php
new file mode 100644 (file)
index 0000000..813b096
--- /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/>.
+
+/**
+ * Strings for component 'tool_lpimportcsv', language 'en'
+ *
+ * @package    tool_lpimportcsv
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['competencyscale'] = 'Competency Scale: {$a}';
+$string['competencyscaledescription'] = 'Competency scale created by import';
+$string['confirmcolumnmappings'] = 'Confirm the columns mappings';
+$string['confirm'] = 'Confirm';
+$string['csvdelimiter'] = 'CSV delimiter';
+$string['csvdelimiter_help'] = 'CSV delimiter of the CSV file.';
+$string['description'] = 'Description';
+$string['descriptionformat'] = 'Description format';
+$string['encoding'] = 'Encoding';
+$string['encoding_help'] = 'Encoding of the CSV file.';
+$string['export'] = 'Export';
+$string['exportid'] = 'Exported id (optional)';
+$string['exportnavlink'] = 'Export competency framework';
+$string['idnumber'] = 'Id number';
+$string['importfile'] = 'CSV framework description file';
+$string['import'] = 'Import';
+$string['invalidimportfile'] = 'File format is invalid.';
+$string['isframework'] = 'Is framework';
+$string['noframeworks'] = 'No competency frameworks have been created yet';
+$string['parentidnumber'] = 'Parent id number';
+$string['pluginname'] = 'Import competency framework';
+$string['relatedidnumbers'] = 'Cross referenced competency id numbers';
+$string['ruleconfig'] = 'Rule config (optional)';
+$string['ruleoutcome'] = 'Rule outcome (optional)';
+$string['ruletype'] = 'Rule type (optional)';
+$string['scaleconfiguration'] = 'Scale configuration';
+$string['scalevalues'] = 'Scale values';
+$string['shortname'] = 'Shortname';
+$string['taxonomy'] = 'Taxonomy';
diff --git a/admin/tool/lpimportcsv/settings.php b/admin/tool/lpimportcsv/settings.php
new file mode 100644 (file)
index 0000000..8cc12da
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Links and settings
+ *
+ * This file contains links and settings used by tool_lpimportcsv
+ *
+ * @package    tool_lpimportcsv
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die;
+
+// Manage competency frameworks page.
+$temp = new admin_externalpage(
+    'toollpimportcsv',
+    get_string('pluginname', 'tool_lpimportcsv'),
+    new moodle_url('/admin/tool/lpimportcsv/index.php'),
+    'moodle/competency:competencymanage'
+);
+$ADMIN->add('competencies', $temp);
+// Export competency framework page.
+$temp = new admin_externalpage(
+    'toollpexportcsv',
+    get_string('exportnavlink', 'tool_lpimportcsv'),
+    new moodle_url('/admin/tool/lpimportcsv/export.php'),
+    'moodle/competency:competencymanage'
+);
+$ADMIN->add('competencies', $temp);
+
+// No report settings.
+$settings = null;
diff --git a/admin/tool/lpimportcsv/tests/fixtures/example.csv b/admin/tool/lpimportcsv/tests/fixtures/example.csv
new file mode 100644 (file)
index 0000000..19c7b05
--- /dev/null
@@ -0,0 +1,209 @@
+"Parent id number","Id number",Shortname,Description,"Description format","Scale values","Scale configuration","Rule type (optional)","Rule outcome (optional)","Rule config (optional)","Cross referenced competency id numbers","Exported id (optional)","Is framework",Taxonomy
+,OECD-CORE,OECD,"<p>The Core Competencies summarise the capabilities that are important across all jobs and that we believe\r
+collectively contribute to the OECD’s overall success. At the same time, the importance of Core\r
+Competencies may vary according to the specific job duties and requirements.\r
+The OECD Competency Framework displays fifteen Core Competencies grouped into three clusters.</p><p>• The delivery-related competencies <br>• The interpersonal competencies\r
+<br>• The strategic competencies</p>",1,"Not yet competent,Competent","[{""scaleid"":""2""},{""id"":2,""scaledefault"":1,""proficient"":1}]",,,,,,1,"competency,competency,competency,competency"
+,levels,Levels,"<p>Each level of the Core Competencies has behavioural indicators that highlight how an individual can\r
+demonstrate that competency. Behavioural indicators are designed to show the requirements for successful\r
+performance.<br></p>",1,,,,0,null,,1,,
+levels,level-1,"Level 1","<p>Level 1 is typically associated with jobs such as Assistants, Secretaries and Operators.<br></p>",1,,,,0,null,"S-DT-1,S-DT-2,S-OA-1,S-OA-2,S-SN-1,S-SN-2,S-SN-3,S-ST-1,DR-AT1,DR-AT-2,DR-AF-1,DR-AF-2,DR-AF-3,DR-DS-1,DR-DS-2,DR-FT-1,DR-FT-2,DR-FT-3,DR-MR-1,DR-MR-2,DR-TW-1,DR-TW-2,DR-TW-3,I-CF-1,I-CF-2,I-CF-3,I-DS-1,I-DS-2,I-DS-3,I-DS-4,I-I-1,I-I-2,I-I-3,I-I-4,I-N-1,I-N-3,I-OK-1,I-OK-2,I-OK-3",2,,
+levels,level-2,"Level 2","<p>Level 2 is typically associated with jobs such as Statisticians, Corporate Management and Administration\r
+Assistants/Officers, Logistics Officers and Documentalists.<br></p>",1,,,,0,null,,3,,
+levels,level-3,"Level 3","<p>Level 3 is typically associated with jobs such as Economists/Policy Analysts, IT Analysts and HR Advisers.<br></p>",1,,,,0,null,,4,,
+levels,level-4,"Level 4","<p>Level 4 is typically associated with jobs such as Senior Economists/Policy Analysts or Managers<br></p>",1,,,,0,null,,5,,
+levels,level-5,"Level 5","<p>Level 5 is typically associated with jobs such as Heads of Division, Counsellors, Deputy Directors and\r
+Directors.<br></p>",1,,,,0,null,,6,,
+,competencies,Competencies,,1,,,,0,null,,7,,
+competencies,delivery,Delivery-related,"<p>Achieving Results<br></p>",1,,,,0,null,,8,,
+competencies,interpersonal,Interpersonal,"<p>Building relationships</p>",1,,,,0,null,,9,,
+competencies,strategic,Strategic,"<p>Planning for the future</p>",1,,,,0,null,,10,,
+delivery,analytical-thinking,"Analytical Thinking","<p>Analytical Thinking is the ability to identify\r
+patterns across situations that are not obviously\r
+related, and to identify key or underlying issues in\r
+complex situations.<br></p>",1,,,,0,null,,11,,
+delivery,acheivement-focus,"Achievement Focus","<p>Achievement Focus is generating results by\r
+assuming responsibility for one's performance\r
+and the correctness of one's interventions, and\r
+recognising opportunities and acting efficiently at\r
+the appropriate moment and within the given\r
+deadlines.<br></p>",1,,,,0,null,,12,,
+delivery,drafting-skills,"Drafting Skills","<p>Drafting Skills are based on the ability to\r
+respectfully communicate ideas and information\r
+(often technical) in writing to ensure that\r
+information and messages are understood and\r
+have the desired impact.<br></p>",1,,,,0,null,,13,,
+delivery,flexible-thinking,"Flexible Thinking","<p>Flexible Thinking involves the ability to\r
+effectively adapt to a variety of situations,\r
+individuals or groups. It is based on the ability to\r
+understand and appreciate different and opposing perspectives on an issue, to adapt an approach\r
+as the requirements of a situation change, and to\r
+change or easily accept changes in one’s own\r
+organisational or job requirements.<br></p>",1,,,,0,null,,14,,
+delivery,managing-resources,"Managing Resources","<p>Managing Resources is about understanding\r
+human, financial, and operational resource issues\r
+to make decisions aimed at building and planning\r
+efficient project workflows, and at improving\r
+overall organisational performance.<br></p>",1,,,,0,null,,15,,
+delivery,teamwork,"Teamwork and Team Leadership","<p>Teamwork and Team Leadership implies\r
+working co-operatively with others, being a part of\r
+a team, and assuming the role of leader of a\r
+team. In the OECD, people work not only with\r
+their own teams but also with teams and groups\r
+across and outside the Organisation. Therefore\r
+they need to work together effectively with\r
+interdependent goals and common values and\r
+norms to foster a collaborative environment and\r
+drive teams in the same direction.<br></p>",1,,,,0,null,,16,,
+interpersonal,client-focus,"Client Focus","<p>Client Focus is based on the ability to\r
+understand internal/external clients’ (e.g.\r
+Committees, working groups, country\r
+representatives, etc.,) needs and concerns in the\r
+short to long-term and to provide sound\r
+recommendations and/or solutions.<br></p>",1,,,,0,null,,17,,
+interpersonal,diplomatic-sensitivity,"Diplomatic Sensitivity","<p>Diplomatic Sensitivity implies understanding\r
+other people. It includes the ability to hear\r
+accurately and understand unspoken, partly\r
+expressed thoughts, feelings and concerns of\r
+others. Included in this competency is an\r
+emphasis on cross-cultural sensitivity.\r
+Proficiency in Diplomatic Sensitivity requires the\r
+ability to keep one’s emotions under control and\r
+restrain negative actions when faced with\r
+opposition or hostility from others or when\r
+working under stress.<br></p>",1,,,,0,null,,18,,
+interpersonal,influencing,Influencing,"<p>Influencing implies an intention to convince\r
+others in an honest, respectful and sensitive\r
+manner in order to get them to go along with\r
+one’s objectives. It can also be the desire to have\r
+a specific impact or effect on others.<br></p>",1,,,,0,null,,19,,
+interpersonal,negotiating,Negotiating,"<p>Negotiating involves the ability to work towards\r
+win-win outcomes. At lower levels, this\r
+competency assumes an understanding of one’s\r
+counterparts and how to respond to them during\r
+negotiations. At the higher levels, the competency\r
+reflects a focus to achieve value-added results.<br></p>",1,,,,0,null,,20,,
+interpersonal,organisational-knowledge,"Organisational Knowledge","<p>Organisational Knowledge is the ability to\r
+understand the power relationships within the\r
+Organisation and with other organisations. It\r
+includes the ability to understand the formal rules\r
+and structures including the ability to identify who\r
+the real decision-makers are as well as the\r
+individuals who can influence them.<br></p>",1,,,,0,null,,21,,
+strategic,developing-talent,"Developing Talent","<p>Developing Talent means fostering an\r
+environment that will encourage professional and\r
+personal growth and the transfer of knowledge to\r
+future talent.<br></p>",1,,,,0,null,,22,,
+strategic,organisational-alignment,"Organisational Alignment","<p>Organisational Alignment is the ability and\r
+willingness to align one’s own behaviour with the\r
+needs, priorities, and goals of the Organisation,\r
+and to act in ways that promote the\r
+Organisation’s goals or meet organisational\r
+needs. Organisational Alignment means focusing\r
+on the Organisation's mission before one's own\r
+preferences or professional priorities.<br></p>",1,,,,0,null,,23,,
+strategic,strategic-networking,"Strategic Networking","<p>Strategic Networking involves working to build\r
+and maintain friendly, trustworthy and open\r
+internal and external relationships and networks\r
+with people who are, or might become, important\r
+actors in achieving strategic-related goals.<br></p>",1,,,,0,null,,24,,
+strategic,strategic-thinking,"Strategic Thinking","<p>Strategic Thinking is the ability to develop a\r
+broad, big-picture view of the Organisation and its\r
+mission. Competitive advantage and threats,\r
+industry trends, emerging technology, market\r
+opportunities, stakeholder focus – Strategic\r
+Thinking is where these all come together.\r
+Strategic Thinking keeps individuals and groups\r
+focused and helps decide where to invest critical\r
+resources. It includes the ability to link long-range\r
+visions and concepts to daily work.<br></p>",1,,,,0,null,,25,,
+analytical-thinking,DR-AT1,"Distinguishes between critical and irrelevant pieces of information","<p>Distinguishes between critical and irrelevant\r
+pieces of information<br></p>",1,,,,0,null,level-1,26,,
+analytical-thinking,DR-AT-2,"Gathers information from a variety of sources to reach a conclusion.","<p>Gathers information from a variety of sources\r
+to reach a conclusion.<br></p>",1,,,,0,null,level-1,27,,
+acheivement-focus,DR-AF-1,"Defines ambitious, but realistic, personal goals.","<p>Defines ambitious, but realistic, personal\r
+goals.<br></p>",1,,,,0,null,level-1,28,,
+acheivement-focus,DR-AF-2,"Works while meeting quality and performance standards","<p>Works while meeting quality and performance\r
+standards<br></p>",1,,,,0,null,level-1,29,,
+acheivement-focus,DR-AF-3,"Promptly and efficiently completes work assignments.","<p>Promptly and efficiently completes work\r
+assignments.<br></p>",1,,,,0,null,level-1,30,,
+drafting-skills,DR-DS-1,"Tailors communication (e.g. content, style and medium) to diverse audiences. ","<p>Tailors communication (e.g. content, style and\r
+medium) to diverse audiences.<br></p>",1,,,,0,null,level-1,31,,
+drafting-skills,DR-DS-2,"Writes and presents factual material in a concise manner. ","<p>Writes and presents factual material in a\r
+concise manner.<br></p>",1,,,,0,null,level-1,32,,
+flexible-thinking,DR-FT-1,"Proposes ways to do things differently.","<p>Proposes ways to do things differently.<br></p>",1,,,,0,null,level-1,33,,
+flexible-thinking,DR-FT-2,"Understands and recognises the value of other points of view and ways of doing things","<p>Understands and recognises the value of\r
+other points of view and ways of doing things<br></p>",1,,,,0,null,level-1,34,,
+flexible-thinking,DR-FT-3,"Displays a positive attitude in the face of ambiguity and change.","<p>Displays a positive attitude in the face of\r
+ambiguity and change.<br></p>",1,,,,0,null,level-1,35,,
+managing-resources,DR-MR-1,"Organises the use of resources to meet expectations and identifies difficulties. ","<p>Organises the use of resources to meet\r
+expectations and identifies difficulties.<br></p>",1,,,,0,null,level-1,36,,
+managing-resources,DR-MR-2,"Plans, coordinates and manages internal and external resources to accomplish assignments within the ","<p>Plans, coordinates and manages internal and\r
+external resources to accomplish\r
+assignments within the given deadlines.<br></p>",1,,,,0,null,level-1,37,,
+teamwork,DR-TW-1,"Initiates collaboration with others and spontaneously assists others in the delivery of their work","<p>Initiates collaboration with others and\r
+spontaneously assists others in the delivery of\r
+their work<br></p>",1,,,,0,null,level-1,38,,
+teamwork,DR-TW-2,"Shares all relevant information with others and seeks others' input. ","<p>Shares all relevant information with others\r
+and seeks others' input.<br></p>",1,,,,0,null,level-1,39,,
+teamwork,DR-TW-3,"Expresses own opinion while remaining factual and respectful.","<p>Expresses own opinion while remaining\r
+factual and respectful.<br></p>",1,,,,0,null,level-1,40,,
+client-focus,I-CF-1,"Responds to and anticipates client needs in a timely, professional, helpful and courteous manner, re","<p>Responds to and anticipates client needs in a\r
+timely, professional, helpful and courteous\r
+manner, regardless of client attitude.<br></p>",1,,,,0,null,level-1,41,,
+client-focus,I-CF-2,"Clearly shows clients that their perspectives are valued","<p>Clearly shows clients that their perspectives\r
+are valued<br></p>",1,,,,0,null,level-1,42,,
+client-focus,I-CF-3,"Strives to consistently meet service standards.","<p>Strives to consistently meet service\r
+standards.<br></p>",1,,,,0,null,level-1,43,,
+diplomatic-sensitivity,I-DS-1,"Listens actively, considers people’s concerns and adjusts own behaviour in a helpful manner.","<p>Listens actively, considers people’s concerns\r
+and adjusts own behaviour in a helpful\r
+manner.<br></p>",1,,,,0,null,level-1,44,,
+diplomatic-sensitivity,I-DS-2,"Understands the reason behind, or motivation for someone’s actions.","<p>Understands the reason behind, or motivation\r
+for someone’s actions.<br></p>",1,,,,0,null,level-1,45,,
+diplomatic-sensitivity,I-DS-3,"Is attentive when doing projects and assignments, or when interacting with people from different cou","<p>Is attentive when doing projects and\r
+assignments, or when interacting with people\r
+from different countries and backgrounds.<br></p>",1,,,,0,null,level-1,46,,
+diplomatic-sensitivity,I-DS-4,"Expresses negative feelings constructively.","<p>Expresses negative feelings constructively.<br></p>",1,,,,0,null,level-1,47,,
+influencing,I-I-1,"Checks own understanding of others' communication (e.g. paraphrases, asks questions).","<p>Checks own understanding of others'\r
+communication (e.g. paraphrases, asks\r
+questions).<br></p>",1,,,,0,null,level-1,48,,
+influencing,I-I-2,"Maintains continuous, open and consistent communication with others.","<p>Maintains continuous, open and consistent\r
+communication with others.<br></p>",1,,,,0,null,level-1,49,,
+influencing,I-I-3,"Builds on successful initiatives to gain support for ideas.","<p>Builds on successful initiatives to gain support\r
+for ideas.<br></p>",1,,,,0,null,level-1,50,,
+influencing,I-I-4,"Adapts arguments to others' needs/interests. ","<p>Adapts arguments to others' needs/interests.<br></p>",1,,,,0,null,level-1,51,,
+negotiating,I-N-1,"Identifies main negotiating points of a given issue and engages in negotiation.","<p>Identifies main negotiating points of a given\r
+issue and engages in negotiation.<br></p>",1,,,,0,null,level-1,52,,
+negotiating,I-N-3,"Listens to differing points of view and promotes mutual understanding.","<p>Listens to differing points of view and\r
+promotes mutual understanding.<br></p>",1,,,,0,null,level-1,53,,
+organisational-knowledge,I-OK-1,"Demonstrates understanding of the general environment in which the Organisation operates.","<p>Demonstrates understanding of the general\r
+environment in which the Organisation\r
+operates.<br></p>",1,,,,0,null,level-1,54,,
+organisational-knowledge,I-OK-2,"Understands and uses the Organisation's structures, rules and networks.","<p>Understands and uses the Organisation's\r
+structures, rules and networks.<br></p>",1,,,,0,null,level-1,55,,
+organisational-knowledge,I-OK-3,"Knows and respects the Organisation’s Code of Conduct and values.","<p>Knows and respects the Organisation’s Code\r
+of Conduct and values.<br></p>",1,,,,0,null,level-1,56,,
+developing-talent,S-DT-1,"Takes advantage of learning opportunities provided (e.g. courses, feedback from supervisor or peers)","<p>Takes advantage of learning opportunities\r
+provided (e.g. courses, feedback from\r
+supervisor or peers) to meet requirements of\r
+current job.<br></p>",1,,,,0,null,level-1,57,,
+developing-talent,S-DT-2,"Sets clear self-development expectations.","<p>Sets clear self-development expectations.<br></p>",1,,,,0,null,level-1,58,,
+organisational-alignment,S-OA-1,"Explains the role and goals of the Organisation and how they relate to own area of work.","<p>Explains the role and goals of the\r
+Organisation and how they relate to own area\r
+of work.<br></p>",1,,,,0,null,level-1,59,,
+organisational-alignment,S-OA-2,"Is able to explain how own work relates to the work of the Organisation.","<p>Is able to explain how own work relates to the\r
+work of the Organisation.<br></p>",1,,,,0,null,level-1,60,,
+strategic-networking,S-SN-1,"Actively nurtures both formal and informal contacts to facilitate the progress of work by proactivel","<p>Actively nurtures both formal and informal\r
+contacts to facilitate the progress of work by\r
+proactively sharing information, best\r
+practices, respective interests and areas of\r
+expertise.<br></p>",1,,,,0,null,level-1,61,,
+strategic-networking,S-SN-2,"Identifies current or past contacts that can provide work-related information or assistance","<p>Identifies current or past contacts that can\r
+provide work-related information or\r
+assistance<br></p>",1,,,,0,null,level-1,62,,
+strategic-networking,S-SN-3,"Fosters two-way trust in dealing with contacts (e.g. maintains confidentiality regarding sensitive i","<p>Fosters two-way trust in dealing with contacts\r
+(e.g. maintains confidentiality regarding\r
+sensitive information).<br></p>",1,,,,0,null,level-1,63,,
+strategic-thinking,S-ST-1,"Identifies new information or data to key decision-makers or stakeholders to support their understan","<p>Identifies new information or data to key\r
+decision-makers or stakeholders to support\r
+their understanding and decisions.<br></p>",1,,,,0,null,level-1,64,,
diff --git a/admin/tool/lpimportcsv/tests/import_test.php b/admin/tool/lpimportcsv/tests/import_test.php
new file mode 100644 (file)
index 0000000..3063b54
--- /dev/null
@@ -0,0 +1,62 @@
+<?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/>.
+/**
+ * External learning plans webservice API tests.
+ *
+ * @package tool_lpimportcsv
+ * @copyright 2015 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use tool_lpimportcsv\framework_importer;
+use tool_lpimportcsv\framework_exporter;
+use core_competency\api;
+
+/**
+ * External learning plans webservice API tests.
+ *
+ * @package tool_lpimportcsv
+ * @copyright 2015 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_lpimportcsv_import_testcase extends advanced_testcase {
+
+    public function test_import_framework() {
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+
+        $importer = new framework_importer(file_get_contents(__DIR__ . '/fixtures/example.csv'));
+
+        $this->assertEquals('', $importer->get_error());
+
+        $framework = $importer->import();
+        $this->assertEmpty('', $importer->get_error());
+
+        $this->assertGreaterThan(0, $framework->get_id());
+
+        $filters = [
+            'competencyframeworkid' => $framework->get_id()
+        ];
+        $count = api::count_competencies($filters);
+        $this->assertEquals(64, $count);
+
+        // We can't test the exporter because it sends force-download headers.
+    }
+
+
+}
diff --git a/admin/tool/lpimportcsv/version.php b/admin/tool/lpimportcsv/version.php
new file mode 100644 (file)
index 0000000..e17aa4a
--- /dev/null
@@ -0,0 +1,32 @@
+<?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/>.
+
+/**
+ * Plugin version info
+ *
+ * @package    tool_lpimportcsv
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+$plugin->version   = 2016083000; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2016051900; // Requires this Moodle version.
+$plugin->component = 'tool_lpimportcsv'; // Full name of the plugin (used for diagnostics).
+$plugin->dependencies = array('tool_lp' => 2016052300);
+
index c111010..9653d45 100644 (file)
@@ -27,6 +27,7 @@ namespace tool_mobile;
 use core_component;
 use core_plugin_manager;
 use context_system;
+use moodle_url;
 
 /**
  * API exposed by tool_mobile
@@ -37,6 +38,13 @@ use context_system;
  */
 class api {
 
+    /** @var int to identify the login via app. */
+    const LOGIN_VIA_APP = 1;
+    /** @var int to identify the login via browser. */
+    const LOGIN_VIA_BROWSER = 2;
+    /** @var int to identify the login via an embedded browser. */
+    const LOGIN_VIA_EMBEDDED_BROWSER = 3;
+
     /**
      * Returns a list of Moodle plugins supporting the mobile app.
      *
@@ -111,6 +119,19 @@ class api {
             'maintenanceenabled' => $CFG->maintenance_enabled,
             'maintenancemessage' => format_text($CFG->maintenance_message),
         );
+
+        $typeoflogin = get_config('tool_mobile', 'typeoflogin');
+        // Not found, edge case.
+        if ($typeoflogin === false) {
+            $typeoflogin = self::LOGIN_VIA_APP; // Defaults to via app.
+        }
+        $settings['typeoflogin'] = $typeoflogin;
+
+        if ($typeoflogin == self::LOGIN_VIA_BROWSER or
+                $typeoflogin == self::LOGIN_VIA_EMBEDDED_BROWSER) {
+            $url = new moodle_url("/$CFG->admin/tool/mobile/launch.php");
+            $settings['launchurl'] = $url->out(false);
+        }
         return $settings;
     }
 
index 148f8a4..4ab5e93 100644 (file)
@@ -140,6 +140,8 @@ class external extends external_api {
                 'enablemobilewebservice' => new external_value(PARAM_INT, 'Whether the Mobile service is enabled.'),
                 'maintenanceenabled' => new external_value(PARAM_INT, 'Whether site maintenance is enabled.'),
                 'maintenancemessage' => new external_value(PARAM_RAW, 'Maintenance message.'),
+                'typeoflogin' => new external_value(PARAM_INT, 'The type of login. 1 for app, 2 for browser, 3 for embedded.'),
+                'launchurl' => new external_value(PARAM_URL, 'SSO login launch URL. Empty if it won\'t be used.', VALUE_OPTIONAL),
                 'warnings' => new external_warnings(),
             )
         );
index 56a1360..9e70c4d 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['clickheretolaunchtheapp'] = 'Click here if the app does not open automatically.';
+$string['enablesmartappbanners'] = 'Enable Smart App Banners';
+$string['enablesmartappbanners_desc'] = 'This will display a banner promoting the Moodle Mobile app when visiting the site in Mobile Safari.';
+$string['forcedurlscheme'] = 'The URL scheme allows to open the mobile app from other apps like the browser. Use this setting if you want to allow only your custom branded app to be opened by the browser.';
+$string['forcedurlscheme_key'] = 'URL scheme';
+$string['iosappid'] = 'App\'s unique identifier';
+$string['iosappid_desc'] = 'You only need to change this value if you have a custom iOS app';
+$string['loginintheapp'] = 'Via the app';
+$string['logininthebrowser'] = 'Via a browser window (for SSO plugins)';
+$string['loginintheembeddedbrowser'] = 'Via an embedded browser (for SSO plugins)';
 $string['pluginname'] = 'Moodle Mobile tools';
+$string['smartappbanners'] = 'Smart App Banners (iOS only)';
+$string['typeoflogin'] = 'Type of login';
+$string['typeoflogin_desc'] = 'Choose the type of login.';
diff --git a/admin/tool/mobile/launch.php b/admin/tool/mobile/launch.php
new file mode 100644 (file)
index 0000000..40da9bb
--- /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/>.
+
+/**
+ * Launch page, launch the app using custom URL schemes.
+ *
+ * If the user is not logged when visiting this page, he will be redirected to the login page.
+ * Once he is logged, he will be redirected again to this page and the app launched via custom URL schemes.
+ *
+ * @package    tool_mobile
+ * @copyright  2016 Juan Leyva
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../../config.php');
+require_once($CFG->libdir . '/externallib.php');
+
+$serviceshortname  = required_param('service',  PARAM_ALPHANUMEXT);
+$passport          = required_param('passport',  PARAM_RAW);    // Passport send from the app to validate the response URL.
+$urlscheme         = optional_param('urlscheme', 'moodlemobile', PARAM_NOTAGS); // The URL scheme the app supports.
+
+// Check web services enabled.
+if (!$CFG->enablewebservices) {
+    throw new moodle_exception('enablewsdescription', 'webservice');
+}
+
+// Check if the plugin is properly configured.
+$typeoflogin = get_config('tool_mobile', 'typeoflogin');
+if ($typeoflogin != tool_mobile\api::LOGIN_VIA_BROWSER and
+        $typeoflogin != tool_mobile\api::LOGIN_VIA_EMBEDDED_BROWSER) {
+    throw new moodle_exception('pluginnotenabledorconfigured', 'local_mobile');
+}
+
+// Check if the service exists and is enabled.
+$service = $DB->get_record('external_services', array('shortname' => $serviceshortname, 'enabled' => 1));
+if (empty($service)) {
+    throw new moodle_exception('servicenotavailable', 'webservice');
+}
+
+require_login(0, false);
+
+// Require an active user: not guest, not suspended.
+core_user::require_active_user($USER);
+
+// Get an existing token or create a new one.
+$token = external_generate_token_for_current_user($service);
+
+// Log token access.
+$DB->set_field('external_tokens', 'lastaccess', time(), array('id' => $token->id));
+
+$params = array(
+    'objectid' => $token->id,
+);
+$event = \core\event\webservice_token_sent::create($params);
+$event->add_record_snapshot('external_tokens', $token);
+$event->trigger();
+
+// Passport is generated in the mobile app, so the app opening can be validated using that variable.
+// Passports are valid only one time, it's deleted in the app once used.
+$siteid = md5($CFG->wwwroot . $passport);
+$apptoken = base64_encode($siteid . ':::' . $token->token);
+
+// Redirect using the custom URL scheme checking first if a URL scheme is forced in the site settings.
+$forcedurlscheme = get_config('tool_mobile', 'forcedurlscheme');
+if (!empty($forcedurlscheme)) {
+    $urlscheme = $forcedurlscheme;
+}
+
+$location = "$urlscheme://token=$apptoken";
+
+// For iOS 10 onwards, we have to simulate a user click.
+if (core_useragent::is_ios()) {
+    $PAGE->set_context(null);
+    $PAGE->set_url('/local/mobile/launch.php', array('service' => $serviceshortname, 'passport' => $passport, 'urlscheme' => $urlscheme));
+
+    echo $OUTPUT->header();
+    $notice = get_string('clickheretolaunchtheapp', 'tool_mobile');
+    echo html_writer::link($location, $notice, array('id' => 'launchapp'));
+    echo html_writer::script(
+        "window.onload = function() {
+            document.getElementById('launchapp').click();
+        };"
+    );
+    echo $OUTPUT->footer();
+} else {
+    // For Android a http redirect will do fine.
+    header('Location: ' . $location);
+    die;
+}
diff --git a/admin/tool/mobile/settings.php b/admin/tool/mobile/settings.php
new file mode 100644 (file)
index 0000000..496557c
--- /dev/null
@@ -0,0 +1,72 @@
+<?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/>.
+
+/**
+ * Settings
+ *
+ * This file contains settings used by tool_mobile
+ *
+ * @package    tool_mobile
+ * @copyright  2016 Juan Leyva
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+if ($hassiteconfig) {
+
+    $temp = new admin_settingpage('mobile', new lang_string('mobile', 'admin'), 'moodle/site:config', false);
+
+    // We should wait to the installation to finish since we depend on some configuration values that are set once
+    // the admin user profile is configured.
+    if (!during_initial_install()) {
+        $enablemobiledocurl = new moodle_url(get_docs_url('Enable_mobile_web_services'));
+        $enablemobiledoclink = html_writer::link($enablemobiledocurl, new lang_string('documentation'));
+        $default = is_https() ? 1 : 0;
+        $temp->add(new admin_setting_enablemobileservice('enablemobilewebservice',
+                new lang_string('enablemobilewebservice', 'admin'),
+                new lang_string('configenablemobilewebservice', 'admin', $enablemobiledoclink), $default));
+    }
+
+    $temp->add(new admin_setting_configtext('mobilecssurl', new lang_string('mobilecssurl', 'admin'),
+                new lang_string('configmobilecssurl', 'admin'), '', PARAM_URL));
+
+    // Type of login.
+    $options = array(
+        tool_mobile\api::LOGIN_VIA_APP => new lang_string('loginintheapp', 'tool_mobile'),
+        tool_mobile\api::LOGIN_VIA_BROWSER => new lang_string('logininthebrowser', 'tool_mobile'),
+        tool_mobile\api::LOGIN_VIA_EMBEDDED_BROWSER => new lang_string('loginintheembeddedbrowser', 'tool_mobile'),
+    );
+    $temp->add(new admin_setting_configselect('tool_mobile/typeoflogin',
+                new lang_string('typeoflogin', 'tool_mobile'),
+                new lang_string('typeoflogin_desc', 'tool_mobile'), 1, $options));
+
+    $temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme',
+                new lang_string('forcedurlscheme_key', 'tool_mobile'),
+                new lang_string('forcedurlscheme', 'tool_mobile'), '', PARAM_NOTAGS));
+
+    $temp->add(new admin_setting_heading('tool_mobile/smartappbanners',
+                new lang_string('smartappbanners', 'tool_mobile'), ''));
+
+    $temp->add(new admin_setting_configcheckbox('tool_mobile/enablesmartappbanners',
+                new lang_string('enablesmartappbanners', 'tool_mobile'),
+                new lang_string('enablesmartappbanners_desc', 'tool_mobile'), 0));
+
+    $temp->add(new admin_setting_configtext('tool_mobile/iosappid', new lang_string('iosappid', 'tool_mobile'),
+                new lang_string('iosappid_desc', 'tool_mobile'), '633359593', PARAM_ALPHANUM));
+
+    $ADMIN->add('webservicesettings', $temp);
+}
index 0ee8fc3..3d2c0ed 100644 (file)
@@ -31,6 +31,7 @@ global $CFG;
 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
 
 use tool_mobile\external;
+use tool_mobile\api;
 
 /**
  * External learning plans webservice API tests.
@@ -78,17 +79,21 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
             'enablemobilewebservice' => $CFG->enablemobilewebservice,
             'maintenanceenabled' => $CFG->maintenance_enabled,
             'maintenancemessage' => format_text($CFG->maintenance_message),
+            'typeoflogin' => api::LOGIN_VIA_APP,
             'warnings' => array()
         );
         $this->assertEquals($expected, $result);
 
-        // Change a value.
+        // Change some values.
         set_config('registerauth', 'email');
         $authinstructions = 'Something with <b>html tags</b>';
         set_config('auth_instructions', $authinstructions);
+        set_config('typeoflogin', api::LOGIN_VIA_BROWSER, 'tool_mobile');
 
         $expected['registerauth'] = 'email';
         $expected['authinstructions'] = format_text($authinstructions);
+        $expected['typeoflogin'] = api::LOGIN_VIA_BROWSER;
+        $expected['launchurl'] = "$CFG->wwwroot/$CFG->admin/tool/mobile/launch.php";;
 
         $result = external::get_site_public_settings();
         $result = external_api::clean_returnvalue(external::get_site_public_settings_returns(), $result);
index 0c57e23..912b901 100644 (file)
@@ -23,6 +23,6 @@
  */
 
 defined('MOODLE_INTERNAL') || die();
-$plugin->version   = 2016052301; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2016052303; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2016051900; // Requires this Moodle version.
 $plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
index 4025312..24f86ca 100644 (file)
@@ -3,20 +3,25 @@
 .path-admin-tool-profiling .profilingruntable .label {
     font-weight: bold;
 }
+
 .path-admin-tool-profiling .profiling_worse {
     color: red;
 }
+
 .path-admin-tool-profiling .profiling_better {
     color: green;
 }
+
 .path-admin-tool-profiling .profiling_same {
     color: dimgrey;
 }
+
 .path-admin-tool-profiling .profiling_important,
 .path-admin-tool-profiling .flexible .referencerun {
     font-weight: bold;
 }
+
 .path-admin-tool-profiling .flexible {
-    margin-left:auto;
-    margin-right:auto
+    margin-left: auto;
+    margin-right: auto;
 }
index 781df18..83ffd7d 100644 (file)
@@ -1,6 +1,28 @@
-.block_activity_results h1 {margin: 4px;font-size: 1.1em;}
-.block_activity_results table.grades {text-align: left; width: 100%;}
-.block_activity_results table.grades .number{text-align: left; width:10%;}
-.block_activity_results table.grades .name{text-align: left; width:77%;}
-.block_activity_results table.grades .grade {text-align: right;}
-.block_activity_results table.grades caption {font-weight: bold; font-size: 18px;}
+.block_activity_results h1 {
+    margin: 4px;
+    font-size: 1.1em;
+}
+
+.block_activity_results table.grades {
+    text-align: left;
+    width: 100%;
+}
+
+.block_activity_results table.grades .number {
+    text-align: left;
+    width: 10%;
+}
+
+.block_activity_results table.grades .name {
+    text-align: left;
+    width: 77%;
+}
+
+.block_activity_results table.grades .grade {
+    text-align: right;
+}
+
+.block_activity_results table.grades caption {
+    font-weight: bold;
+    font-size: 18px;
+}
index 5df7165..9fc8a17 100644 (file)
@@ -1,20 +1,68 @@
-.block_blog_tags .s20 {font-size: 1.5em;font-weight: bold;}
-.block_blog_tags .s19 {font-size: 1.5em;}
-.block_blog_tags .s18 {font-size: 1.4em;font-weight: bold;}
-.block_blog_tags .s17 {font-size: 1.4em;}
-.block_blog_tags .s16 {font-size: 1.3em;font-weight: bold;}
-.block_blog_tags .s15 {font-size: 1.3em;}
-.block_blog_tags .s14 {font-size: 1.2em;font-weight: bold;}
-.block_blog_tags .s13 {font-size: 1.2em;}
+.block_blog_tags .s20 {
+    font-size: 1.5em;
+    font-weight: bold;
+}
+
+.block_blog_tags .s19 {
+    font-size: 1.5em;
+}
+
+.block_blog_tags .s18 {
+    font-size: 1.4em;
+    font-weight: bold;
+}
+
+.block_blog_tags .s17 {
+    font-size: 1.4em;
+}
+
+.block_blog_tags .s16 {
+    font-size: 1.3em;
+    font-weight: bold;
+}
+
+.block_blog_tags .s15 {
+    font-size: 1.3em;
+}
+
+.block_blog_tags .s14 {
+    font-size: 1.2em;
+    font-weight: bold;
+}
+
+.block_blog_tags .s13 {
+    font-size: 1.2em;
+}
+
 .block_blog_tags .s12,
-.block_blog_tags .s11 {font-size: 1.1em;font-weight: bold;}
+.block_blog_tags .s11 {
+    font-size: 1.1em;
+    font-weight: bold;
+}
+
 .block_blog_tags .s10,
-.block_blog_tags .s9 {font-size: 1.1em;}
+.block_blog_tags .s9 {
+    font-size: 1.1em;
+}
+
 .block_blog_tags .s8,
-.block_blog_tags .s7 {font-size: 1em;font-weight: bold;}
+.block_blog_tags .s7 {
+    font-size: 1em;
+    font-weight: bold;
+}
+
 .block_blog_tags .s6,
-.block_blog_tags .s5 {font-size: 1em;}
+.block_blog_tags .s5 {
+    font-size: 1em;
+}
+
 .block_blog_tags .s4,
-.block_blog_tags .s3 {font-size: 0.9em;font-weight: bold;}
+.block_blog_tags .s3 {
+    font-size: 0.9em;
+    font-weight: bold;
+}
+
 .block_blog_tags .s2,
-.block_blog_tags .s1 {font-size: 0.9em;}
\ No newline at end of file
+.block_blog_tags .s1 {
+    font-size: 0.9em;
+}
\ No newline at end of file
index 8264677..c6704e6 100644 (file)
 /** General display rules **/
 
 /* HUB SELECTOR */
-#page-blocks-community-communitycourse .hubscreenshot {float: left; }
-#page-blocks-community-communitycourse .hubtitlelink {color: #999; }
-#page-blocks-community-communitycourse .hubsmalllogo {padding-left: 3px; padding-right: 7px; float: left; }
-#page-blocks-community-communitycourse .hubtext {display: block; width: 68%; padding-left: 165px;}
-#page-blocks-community-communitycourse .hubimgandtext {display:table;}
-#page-blocks-community-communitycourse .hubimage {float: left; display: block; width: 100px;}
-#page-blocks-community-communitycourse .hubstats {padding-top: 10px}
-#page-blocks-community-communitycourse .hubstats .iconhelp {float: left; padding-right: 3px;}
-#page-blocks-community-communitycourse .hubadditionaldesc {color: #666666; font-size: 90%; display:block;}
-#page-blocks-community-communitycourse .hubscreenshot {margin-right: 10px;}
-#page-blocks-community-communitycourse .hubtrusted {display:inline;}
-#page-blocks-community-communitycourse .trustedtr {background-color: #ffe1c3;}
-#page-blocks-community-communitycourse .prioritisetr {background-color: #ffd4ff;}
-#page-blocks-community-communitycourse .blockdescription {font-size: 80%; color: #555555;}
-#page-blocks-community-communitycourse .trusted {font-size: 90%; color: #006633; font-weight: normal; font-style: italic;}
+#page-blocks-community-communitycourse .hubscreenshot {
+    float: left;
+}
+
+#page-blocks-community-communitycourse .hubtitlelink {
+    color: #999;
+}
+
+#page-blocks-community-communitycourse .hubsmalllogo {
+    padding-left: 3px;
+    padding-right: 7px;
+    float: left;
+}
+
+#page-blocks-community-communitycourse .hubtext {
+    display: block;
+    width: 68%;
+    padding-left: 165px;
+}
+
+#page-blocks-community-communitycourse .hubimgandtext {
+    display: table;
+}
+
+#page-blocks-community-communitycourse .hubimage {
+    float: left;
+    display: block;
+    width: 100px;
+}
+
+#page-blocks-community-communitycourse .hubstats {
+    padding-top: 10px;
+}
+
+#page-blocks-community-communitycourse .hubstats .iconhelp {
+    float: left;
+    padding-right: 3px;
+}
+
+#page-blocks-community-communitycourse .hubadditionaldesc {
+    color: #666;
+    font-size: 90%;
+    display: block;
+}
+
+#page-blocks-community-communitycourse .hubscreenshot {
+    margin-right: 10px;
+}
+
+#page-blocks-community-communitycourse .hubtrusted {
+    display: inline;
+}
+
+#page-blocks-community-communitycourse .trustedtr {
+    background-color: #ffe1c3;
+}
+
+#page-blocks-community-communitycourse .prioritisetr {
+    background-color: #ffd4ff;
+}
+
+#page-blocks-community-communitycourse .blockdescription {
+    font-size: 80%;
+    color: #555;
+}
+
+#page-blocks-community-communitycourse .trusted {
+    font-size: 90%;
+    color: #063;
+    font-weight: normal;
+    font-style: italic;
+}
 
 /* COURSES RESULT */
-#page-blocks-community-communitycourse .additionaldesc {font-size: 80%; color: #8B8989;}
-#page-blocks-community-communitycourse .comment-link {font-size: 80%; color: #555555;}
-#page-blocks-community-communitycourse .coursescreenshot {text-align:center;cursor: pointer;}
-#page-blocks-community-communitycourse .hubcourseinfo {margin-left: 15px;}
-#page-blocks-community-communitycourse .pagingbar {text-align: center;}
-#page-blocks-community-communitycourse .coursecomment {float: right;}
-#page-blocks-community-communitycourse .courseoperations {margin-top:9px; text-align:center}
-#page-blocks-community-communitycourse .hubcoursedownload:hover {background-color: #CDC9C9;}
-#page-blocks-community-communitycourse .courselinks {float: right;width: 180px}
-#page-blocks-community-communitycourse .ratingaggregate {float: left; padding-right: 4px;}
-#page-blocks-community-communitycourse .hubcourserating { padding-top: 3px; font-size: 80%; color: #555555;}
-#page-blocks-community-communitycourse .coursedescription {width: 70%; float: left;}
-#page-blocks-community-communitycourse .fullhubcourse {margin-bottom: 20px;}
-#page-blocks-community-communitycourse .hubcoursetitlepanel {margin-bottom: 6px;}
+#page-blocks-community-communitycourse .additionaldesc {
+    font-size: 80%;
+    color: #8b8989;
+}
+
+#page-blocks-community-communitycourse .comment-link {
+    font-size: 80%;
+    color: #555;
+}
+
+#page-blocks-community-communitycourse .coursescreenshot {
+    text-align: center;
+    cursor: pointer;
+}
+
+#page-blocks-community-communitycourse .hubcourseinfo {
+    margin-left: 15px;
+}
+
+#page-blocks-community-communitycourse .pagingbar {
+    text-align: center;
+}
+
+#page-blocks-community-communitycourse .coursecomment {
+    float: right;
+}
+
+#page-blocks-community-communitycourse .courseoperations {
+    margin-top: 9px;
+    text-align: center;
+}
+
+#page-blocks-community-communitycourse .hubcoursedownload:hover {
+    background-color: #cdc9c9;
+}
+
+#page-blocks-community-communitycourse .courselinks {
+    float: right;
+    width: 180px;
+}
+
+#page-blocks-community-communitycourse .ratingaggregate {
+    float: left;
+    padding-right: 4px;
+}
+
+#page-blocks-community-communitycourse .hubcourserating {
+    padding-top: 3px;
+    font-size: 80%;
+    color: #555;
+}
+
+#page-blocks-community-communitycourse .coursedescription {
+    width: 70%;
+    float: left;
+}
+
+#page-blocks-community-communitycourse .fullhubcourse {
+    margin-bottom: 20px;
+}
+
+#page-blocks-community-communitycourse .hubcoursetitlepanel {
+    margin-bottom: 6px;
+}
+
 #page-blocks-community-communitycourse .hubcourseresult {
-    background:none repeat scroll 0 0 #FFFFFF;
-    clear:both;
-    margin:30px auto 0;
-    z-index:90;
+    background: none repeat scroll 0 0 #fff;
+    clear: both;
+    margin: 30px auto 0;
+    z-index: 90;
     width: 95%;
     padding: 10px 10px 10px 10px;
     border-style: solid;
     border-width: 1px;
 }
+
 #page-blocks-community-communitycourse .hubcoursetitle {
-    -webkit-box-shadow: rgba(0, 0, 0, 0.546875) 0px 0px 4px;
-    -moz-box-shadow: rgba(0, 0, 0, 0.546875) 0px 0px 4px;
-    background: #8B8989;
+    -webkit-box-shadow: rgba(0, 0, 0, 0.546875) 0 0 4px;
+    -moz-box-shadow: rgba(0, 0, 0, 0.546875) 0 0 4px;
+    background: #8b8989;
     left: -15px;
     position: relative;
     z-index: 0;
-    border: 0px;
-    margin: 0px;
-    outline: 0px;
-    padding: 0px;
+    border: 0;
+    margin: 0;
+    outline: 0;
+    padding: 0;
     vertical-align: baseline;
     color: #fff;
     padding-top: 6px;
     padding-bottom: 6px;
-    text-shadow: 1px 1px 2px rgba(0,0,0,0.2);
+    text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
     text-align: left;
-    font-style:italic;
-    font-weight:normal;
-    line-height:1.2em;
+    font-style: italic;
+    font-weight: normal;
+    line-height: 1.2em;
     font-size: 140%;
     width: 102%;
     text-indent: 15px;
 }
+
 #page-blocks-community-communitycourse .hubcoursedownload {
     display: inline-block;
     padding: 5px 8px 6px;
     text-decoration: none;
     -moz-border-radius: 6px;
     -webkit-border-radius: 6px;
-    -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.6);
-    -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.6);
-    border-bottom: 1px solid rgba(0,0,0,0.25);
+    -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
+    -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
+    border-bottom: 1px solid rgba(0, 0, 0, 0.25);
     position: relative;
     cursor: pointer;
-    background-color: #EEE9E9;
+    background-color: #eee9e9;
     margin-left: 6px;
     font-size: 95%;
     margin-bottom: 9px;
 }
 
 /*  STAR RATING  */
-#page-blocks-community-communitycourse .ratingcount {color: #8B8989;font-size: 80%;vertical-align: top;}
-#page-blocks-community-communitycourse .norating {font-weight: bold;color:#8B8989;font-size: 80%;}
-#page-blocks-community-communitycourse .star-rating{
-    list-style:none;
+#page-blocks-community-communitycourse .ratingcount {
+    color: #8b8989;
+    font-size: 80%;
+    vertical-align: top;
+}
+
+#page-blocks-community-communitycourse .norating {
+    font-weight: bold;
+    color: #8b8989;
+    font-size: 80%;
+}
+
+#page-blocks-community-communitycourse .star-rating {
+    list-style: none;
     margin: 4px 0 4px;
-    padding:0px;
+    padding: 0;
     width: 100px;
     height: 20px;
     position: relative;
     background: url([[pix:i/star-rating]]) top left repeat-x;
     float: left;
 }
-#page-blocks-community-communitycourse .star-rating li{
-    padding:0px;
-    margin:0px;
-    height:20px;
+
+#page-blocks-community-communitycourse .star-rating li {
+    padding: 0;
+    margin: 0;
+    height: 20px;
     width: 20px;
     float: left;
 }
-#page-blocks-community-communitycourse .star-rating li.current-rating{
+
+#page-blocks-community-communitycourse .star-rating li.current-rating {
     background: url([[pix:i/star-rating]]) left bottom;
     position: absolute;
     height: 20px;
 }
 
 /* COMMENTS */
-#page-blocks-community-communitycourse .nocomments {font-weight: bold;color:#8B8989;font-size: 80%;}
-#page-blocks-community-communitycourse .hubcommentator {float: left;font-weight: bold;}
-#page-blocks-community-communitycourse .hubcommentdate {font-weight: bold;}
-#page-blocks-community-communitycourse .hubcommenttext{margin-bottom: 10px;}
-#page-blocks-community-communitycourse .hubnoscriptcoursecomments {margin-left: 5px;}
+#page-blocks-community-communitycourse .nocomments {
+    font-weight: bold;
+    color: #8b8989;
+    font-size: 80%;
+}
+
+#page-blocks-community-communitycourse .hubcommentator {
+    float: left;
+    font-weight: bold;
+}
+
+#page-blocks-community-communitycourse .hubcommentdate {
+    font-weight: bold;
+}
+
+#page-blocks-community-communitycourse .hubcommenttext {
+    margin-bottom: 10px;
+}
+
+#page-blocks-community-communitycourse .hubnoscriptcoursecomments {
+    margin-left: 5px;
+}
+
 #page-blocks-community-communitycourse .yui3-overlay-loading {
     /* Hide overlay markup while loading, if js is enabled */
-    top:-1000em;
-    left:-1000em;
-    position:absolute;
-    z-index:1000;
+    top: -1000em;
+    left: -1000em;
+    position: absolute;
+    z-index: 1000;
 }
+
 #page-blocks-community-communitycourse .hubcoursecomments {
     /* comment button */
     display: inline-block;
     -webkit-border-radius: 6px;
     position: relative;
     cursor: pointer;
-    background-color: #8B8989;
-    margin-left: 0px;
+    background-color: #8b8989;
+    margin-left: 0;
     font-size: 80%;
-    margin-top:15px;
+    margin-top: 15px;
+}
+
+#page-blocks-community-communitycourse .hubrateandcomment {
+    font-size: 80%;
+}
+
+#page-blocks-community-communitycourse .nextlink {
+    text-align: center;
+    margin-top: 6px;
+}
+
+#page-blocks-community-communitycourse .textinfo {
+    text-align: center;
 }
-#page-blocks-community-communitycourse .hubrateandcomment { font-size: 80%;}
-#page-blocks-community-communitycourse .nextlink {text-align: center;margin-top: 6px;}
-#page-blocks-community-communitycourse .textinfo { text-align: center;}
 
 #ss-mask {
     z-index: 10;
-    position:fixed;
-    top:0;
-    left:0;
-    bottom:0;
-    right:0;
-    opacity:0.35;
-    filter:alpha(opacity=35);
-    background:#000;
+    position: fixed;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    opacity: 0.35;
+    filter: alpha(opacity=35);
+    background: #000;
 }
 
 .hiddenoverlay {
     display: inline;
     cursor: pointer;
 }
+
 .imagetitle {
     display: inline;
     cursor: pointer;
 }
 
 #page-blocks-community-communitycourse .moodle-dialogue-base .moodle-dialogue {
-    -moz-border-radius:12px 12px 12px 12px;
-    -moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.6);
-    -webkit-border-radius:12px 12px 12px 12px;
-    -webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.6);
-    border-width:0 0 0 0;
+    -moz-border-radius: 12px 12px 12px 12px;
+    -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
+    -webkit-border-radius: 12px 12px 12px 12px;
+    -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
+    border-width: 0 0 0 0;
 }
 
 #page-blocks-community-communitycourse .moodle-dialogue-base .moodle-dialogue-wrap {
-    -moz-border-radius:12px 12px 0px 0px;
-    -webkit-border-radius:12px 12px 0px 0px;
-    background-color:#FFFFFF;
-    border:1px solid #555555;
+    -moz-border-radius: 12px 12px 0 0;
+    -webkit-border-radius: 12px 12px 0 0;
+    background-color: #fff;
+    border: 1px solid #555;
 }
 
 #page-blocks-community-communitycourse .moodle-dialogue-base .moodle-dialogue-hd {
-    -moz-border-radius:12px 12px 0 0;
-    -webkit-border-radius:12px 12px 0 0;
-    background-color:#F6F6F6;
-    border:1px solid #CCCCCC;
+    -moz-border-radius: 12px 12px 0 0;
+    -webkit-border-radius: 12px 12px 0 0;
+    background-color: #f6f6f6;
+    border: 1px solid #ccc;
     overflow: auto;
     padding: 7px 6px;
 }
 
 #page-blocks-community-communitycourse .moodle-dialogue-base .moodle-dialogue-bd {
-    padding:0px;
+    padding: 0;
     margin-bottom: -5px;
 }
 
 #page-blocks-community-communitycourse .moodle-dialogue-base .closebutton {
-    margin-top:4px;
+    margin-top: 4px;
     margin-right: 4px;
 }
index 7d75fce..b94fe89 100644 (file)
@@ -1,2 +1,7 @@
-.block_course_list .footer {margin-top: 5px;}
-.block_course_list .content li { margin-bottom: .3em;}
+.block_course_list .footer {
+    margin-top: 5px;
+}
+
+.block_course_list .content li {
+    margin-bottom: .3em;
+}
index d7d1482..2aab2a3 100644 (file)
@@ -3,13 +3,14 @@
     font-style: italic;
 }
 
-.block_course_overview .categorypath{
+.block_course_overview .categorypath {
     text-align: right;
 }
 
 .block_course_overview .content {
     margin: 0 20px;
 }
+
 .block_course_overview .content .notice {
     margin: 5px 0;
 }
@@ -22,6 +23,7 @@
 .block_course_overview .profilepicture {
     float: left;
 }
+
 .block_course_overview .welcome_area {
     width: 100%;
     padding-bottom: 5px;
@@ -30,7 +32,6 @@
 .block_course_overview .welcome_message {
     float: left;
     padding: 10px;
-    vertical-align: middle;
     border-collapse: separate;
     clear: none;
 }
 .block_course_overview .activity_overview {
     padding: 2px;
 }
-.block_course_overview .activity_overview img.iconlarge { vertical-align: text-bottom; margin-right: 6px; }
+
+.block_course_overview .activity_overview img.iconlarge {
+    vertical-align: text-bottom;
+    margin-right: 6px;
+}
 
 .block_course_overview .singleselect {
     text-align: left;
     margin: 0;
 }
 
-.block_course_overview .content .course_list .movehere{
+.block_course_overview .content .course_list .movehere {
     margin-bottom: 15px;
 }
index 7c58b8d..5d37beb 100644 (file)
@@ -1,2 +1,7 @@
-.block_course_summary .content {padding:10px;}
-.block_course_summary .editbutton {text-align:right;}
\ No newline at end of file
+.block_course_summary .content {
+    padding: 10px;
+}
+
+.block_course_summary .editbutton {
+    text-align: right;
+}
\ No newline at end of file
index b8b5c69..6b94c4d 100644 (file)
@@ -1,2 +1,7 @@
-.block_globalsearch .searchform {text-align: center;}
-.block_globalsearch .footer {text-align: center;}
+.block_globalsearch .searchform {
+    text-align: center;
+}
+
+.block_globalsearch .footer {
+    text-align: center;
+}
index b17f1cb..2f06516 100644 (file)
@@ -2,13 +2,16 @@
     padding: 0;
     text-transform: none;
 }
+
 .block_lp .sub-content {
     padding: 0 15px;
 }
+
 .block_lp ul {
     list-style: none;
     margin: 0;
 }
+
 .block_lp ul .more {
     padding-top: 10px;
 }
index 9b7ef1a..176f756 100644 (file)
@@ -1,6 +1,25 @@
-.block_messages .content {text-align:left;padding-top:5px;}
-.block_messages .content .list li.listentry {clear:both;}
-.block_messages .content .list li.listentry .user {float:left;position:relative;}
-.block_messages .content .list li.listentry .message {float:right;}
-.block_messages .content .info {text-align:center;}
-.block_messages .content .footer {clear:both;}
+.block_messages .content {
+    text-align: left;
+    padding-top: 5px;
+}
+
+.block_messages .content .list li.listentry {
+    clear: both;
+}
+
+.block_messages .content .list li.listentry .user {
+    float: left;
+    position: relative;
+}
+
+.block_messages .content .list li.listentry .message {
+    float: right;
+}
+
+.block_messages .content .info {
+    text-align: center;
+}
+
+.block_messages .content .footer {
+    clear: both;
+}
index 30df40d..ee3944e 100644 (file)
@@ -1,4 +1,14 @@
-.block_myprofile img.profilepicture { height:100px; width:100px;}
-.block_myprofile .myprofileitem.fullname {font-size:1.5em; font-weight: bold;}
-.block_myprofile .myprofileitem.edit {text-align: right;}
+.block_myprofile img.profilepicture {
+    height: 100px;
+    width: 100px;
+}
+
+.block_myprofile .myprofileitem.fullname {
+    font-size: 1.5em;
+    font-weight: bold;
+}
+
+.block_myprofile .myprofileitem.edit {
+    text-align: right;
+}
 
index 958c184..90be203 100644 (file)
@@ -1,5 +1,21 @@
-.block_online_users .content .list li.listentry {clear:both;}
-.block_online_users .content .list li.listentry .user {float:left;position:relative;}
-.block_online_users .content .list li.listentry .user .userpicture { vertical-align: text-bottom;}
-.block_online_users .content .list li.listentry .message {float:right; margin-top: 3px;}
-.block_online_users .content .info {text-align:center;}
+.block_online_users .content .list li.listentry {
+    clear: both;
+}
+
+.block_online_users .content .list li.listentry .user {
+    float: left;
+    position: relative;
+}
+
+.block_online_users .content .list li.listentry .user .userpicture {
+    vertical-align: text-bottom;
+}
+
+.block_online_users .content .list li.listentry .message {
+    float: right;
+    margin-top: 3px;
+}
+
+.block_online_users .content .info {
+    text-align: center;
+}
index 28b0cf1..3d1d9e3 100644 (file)
@@ -1,4 +1,12 @@
 .block_recent_activity .activitydate,
-.block_recent_activity .activityhead {text-align:center;}
-.block_recent_activity .unlist li {margin-bottom:1em;}
-.block_recent_activity li .head .date  {float:right;}
+.block_recent_activity .activityhead {
+    text-align: center;
+}
+
+.block_recent_activity .unlist li {
+    margin-bottom: 1em;
+}
+
+.block_recent_activity li .head .date {
+    float: right;
+}
index 26046cd..0c70060 100644 (file)
@@ -56,7 +56,8 @@ class block_rss_client_edit_form extends block_edit_form {
             $params += $inparams;
         }
 
-        $titlesql = "CASE WHEN preferredtitle = '' THEN {$DB->sql_compare_text('title', 64)} ELSE preferredtitle END";
+        $titlesql = "CASE WHEN {$DB->sql_isempty('block_rss_client','preferredtitle', false, false)}
+                      THEN {$DB->sql_compare_text('title', 64)} ELSE preferredtitle END";
 
         $rssfeeds = $DB->get_records_sql_menu("
                 SELECT id, $titlesql
index 1ec870d..b1afcae 100644 (file)
@@ -3,6 +3,7 @@
 .block_rss_client .list li:first-child {
     border-top-width: 0;
 }
+
 .block_rss_client .list li {
     border-top: 1px solid;
     padding: 5px;
index f424ebf..09217a5 100644 (file)
@@ -1,4 +1,16 @@
-.block_search_forums .searchform {text-align: center;}
-.block_search_forums .searchform img {vertical-align: middle;}
-.block_search_forums .searchform img.resize {width: 1em;height: 1.1em;}
-.block_search_forums .invisiblefieldset {display: block;}
\ No newline at end of file
+.block_search_forums .searchform {
+    text-align: center;
+}
+
+.block_search_forums .searchform img {
+    vertical-align: middle;
+}
+
+.block_search_forums .searchform img.resize {
+    width: 1em;
+    height: 1.1em;
+}
+
+.block_search_forums .invisiblefieldset {
+    display: block;
+}
\ No newline at end of file
index 12f39e2..e0c94b2 100644 (file)
@@ -1,22 +1,65 @@
-.block_settings .block_tree ul {margin-left: 18px;}
-.block_settings .block_tree p.hasicon {text-indent: -21px; padding-left: 21px;}
-.block_settings .block_tree p.hasicon img {width: 16px; height: 16px; margin-top: 3px; margin-right: 5px; vertical-align: top;}
-.block_settings .block_tree p.hasicon.visibleifjs {display: block;}
-
-.block_settings .block_tree .tree_item.branch {padding-left: 21px;}
-.block_settings .block_tree .tree_item {cursor:pointer; margin:3px 0px; background-position: 0 50%; background-repeat: no-repeat;}
-.block_settings .block_tree .active_tree_node {font-weight:bold;}
-
-.block_settings .block_tree [aria-expanded="true"] {background-image: url('[[pix:t/expanded]]');}
-.block_settings .block_tree [aria-expanded="false"] {background-image: url('[[pix:t/collapsed]]');}
-.block_settings .block_tree [aria-expanded="true"].emptybranch  {background-image: url('[[pix:t/collapsed_empty]]');}
-.block_settings .block_tree [aria-expanded="false"].loading  {background-image: url('[[pix:i/loading_small]]');}
+.block_settings .block_tree ul {
+    margin-left: 18px;
+}
+
+.block_settings .block_tree p.hasicon {
+    text-indent: -21px;
+    padding-left: 21px;
+}
+
+.block_settings .block_tree p.hasicon img {
+    width: 16px;
+    height: 16px;
+    margin-top: 3px;
+    margin-right: 5px;
+    vertical-align: top;
+}
+
+.block_settings .block_tree p.hasicon.visibleifjs {
+    display: block;
+}
+
+.block_settings .block_tree .tree_item.branch {
+    padding-left: 21px;
+}
+
+.block_settings .block_tree .tree_item {
+    cursor: pointer;
+    margin: 3px 0;
+    background-position: 0 50%;
+    background-repeat: no-repeat;
+}
+
+.block_settings .block_tree .active_tree_node {
+    font-weight: bold;
+}
+
+.block_settings .block_tree [aria-expanded="true"] {
+    background-image: url('[[pix:t/expanded]]');
+}
+
+.block_settings .block_tree [aria-expanded="false"] {
+    background-image: url('[[pix:t/collapsed]]');
+}
+
+.block_settings .block_tree [aria-expanded="true"].emptybranch {
+    background-image: url('[[pix:t/collapsed_empty]]');
+}
+
+.block_settings .block_tree [aria-expanded="false"].loading {
+    background-image: url('[[pix:i/loading_small]]');
+}
 /*rtl:raw:
 .block_settings .block_tree [aria-expanded="false"] {background-image: url('[[pix:t/collapsed_rtl]]');}
 .block_settings .block_tree [aria-expanded="true"].emptybranch {background-image: url('[[pix:t/collapsed_empty_rtl]]');}
 .block_settings .block_tree [aria-expanded="false"].loading {background-image: url('[[pix:i/loading_small]]');}
 */
-.block_settings .block_tree [aria-hidden="false"] {display: block;}
-.block_settings .block_tree  [aria-hidden="true"] {display: none;}
+.block_settings .block_tree [aria-hidden="false"] {
+    display: block;
+}
+
+.block_settings .block_tree  [aria-hidden="true"] {
+    display: none;
+}
 
 
index 9c00109..9f4747a 100644 (file)
@@ -1,9 +1,13 @@
-.block_site_main_menu li { clear: both; }
+.block_site_main_menu li {
+    clear: both;
+}
+
 .block_site_main_menu.block.list_block .unlist > li > .column {
     /* Made specific to win over .block.list_block .unlist > li > .column. */
     width: 100%;
     display: table;
 }
+
 .block_site_main_menu li .buttons {
     float: right;
     margin: 0;
     border: 0;
     background-color: inherit;
 }
-.block_site_main_menu li .buttons a img{ vertical-align: text-bottom;}
-.block_site_main_menu .footer { margin-top: 1em; }
-.block_site_main_menu .section_add_menus noscript div { display: inline;}
+
+.block_site_main_menu li .buttons a img {
+    vertical-align: text-bottom;
+}
+
+.block_site_main_menu .footer {
+    margin-top: 1em;
+}
+
+.block_site_main_menu .section_add_menus noscript div {
+    display: inline;
+}
+
 .block_site_main_menu .mod-indent,
-.block_site_main_menu .main-menu-content { display: table-cell; }
+.block_site_main_menu .main-menu-content {
+    display: table-cell;
+}
index 25a1846..14dea69 100644 (file)
@@ -1,4 +1,16 @@
-.block_social_activities li { clear: both; }
-.block_social_activities li .column { width: 100%; }
-.block_social_activities li .buttons { float: right; margin: 0; }
-.block_social_activities li .buttons a img{ vertical-align: text-bottom;}
+.block_social_activities li {
+    clear: both;
+}
+
+.block_social_activities li .column {
+    width: 100%;
+}
+
+.block_social_activities li .buttons {
+    float: right;
+    margin: 0;
+}
+
+.block_social_activities li .buttons a img {
+    vertical-align: text-bottom;
+}
index 7351ce8..08a9a60 100644 (file)
@@ -1 +1,3 @@
-.block_tag_flickr .flickr-photos {padding:3px;}
\ No newline at end of file
+.block_tag_flickr .flickr-photos {
+    padding: 3px;
+}
\ No newline at end of file
index 1def4e3..0956ff3 100644 (file)
@@ -1,2 +1,10 @@
-.block_tag_youtube .youtube-thumb {padding: 3px;padding-bottom: 0.5em;display: block;float: left;}
-.block_tag_youtube .yt-video-entry li {clear: left;}
\ No newline at end of file
+.block_tag_youtube .youtube-thumb {
+    padding: 3px;
+    padding-bottom: 0.5em;
+    display: block;
+    float: left;
+}
+
+.block_tag_youtube .yt-video-entry li {
+    clear: left;
+}
\ No newline at end of file
index 3f615bd..5d69243 100644 (file)
@@ -88,6 +88,13 @@ if ($externalblogform->is_cancelled()) {
                     context_user::instance($newexternal->userid), $data->autotags);
             blog_sync_external_entries($newexternal);
 
+            // Log this action.
+            $eventparms = array('context' => $context,
+                'objectid' => $newexternal->id,
+                'other' => array('url' => $newexternal->url));
+            $event = \core\event\blog_external_added::create($eventparms);
+            $event->trigger();
+
             break;
 
         case 'edit':
@@ -104,6 +111,14 @@ if ($externalblogform->is_cancelled()) {
                 $external->timemodified = time();
 
                 $DB->update_record('blog_external', $external);
+
+                // Log this action.
+                $eventparms = array('context' => $context,
+                    'objectid' => $external->id,
+                    'other' => array('url' => $external->url));
+                $event = \core\event\blog_external_updated::create($eventparms);
+                $event->trigger();
+
                 core_tag_tag::set_item_tags('core', 'blog_external', $external->id,
                         context_user::instance($external->userid), $data->autotags);
             } else {
index 89d9acb..996ee7a 100644 (file)
@@ -41,8 +41,8 @@ $strblogs = get_string('blogs', 'blog');
 $message = null;
 
 if ($delete && confirm_sesskey()) {
-    $externalbloguserid = $DB->get_field('blog_external', 'userid', array('id' => $delete));
-    if ($externalbloguserid == $USER->id) {
+    $externalblog = $DB->get_record('blog_external', array('id' => $delete));
+    if ($externalblog->userid == $USER->id) {
         // Delete the external blog.
         $DB->delete_records('blog_external', array('id' => $delete));
 
@@ -55,6 +55,11 @@ if ($delete && confirm_sesskey()) {
                                                                'userid' => $USER->id,
                                                                'delete' => $delete));
 
+        // Log this action.
+        $eventparms = array('context' => $context, 'objectid' => $delete);
+        $event = \core\event\blog_external_removed::create($eventparms);
+        $event->add_record_snapshot('blog_external', $externalblog);
+        $event->trigger();
         $message = get_string('externalblogdeleted', 'blog');
     }
 }
@@ -111,4 +116,8 @@ if (!empty($blogs)) {
 $newexternalurl = new moodle_url('/blog/external_blog_edit.php');
 echo html_writer::link($newexternalurl, $straddnewexternalblog);
 echo $OUTPUT->box_end();
+
+// Log this page.
+$event = \core\event\blog_external_viewed::create(array('context' => $context));
+$event->trigger();
 echo $OUTPUT->footer();
index af1b72e..40af88d 100644 (file)
@@ -403,11 +403,29 @@ class blog_entry implements renderable {
 
     /**
      * remove all associations for a blog entry
-     * @return voic
+     *
+     * @return void
      */
     public function remove_associations() {
         global $DB;
-        $DB->delete_records('blog_association', array('blogid' => $this->id));
+
+        $associations = $DB->get_records('blog_association', array('blogid' => $this->id));
+        foreach ($associations as $association) {
+
+            // Trigger an association deleted event.
+            $context = context::instance_by_id($association->contextid);
+            $eventparam = array(
+                'objectid' => $this->id,
+                'other' => array('subject' => $this->subject, 'blogid' => $this->id),
+                'relateduserid' => $this->userid
+            );
+            $event = \core\event\blog_association_deleted::create($eventparam);
+            $event->add_record_snapshot('blog_association', $association);
+            $event->trigger();
+
+            // Now remove the association.
+            $DB->delete_records('blog_association', array('id' => $association->id));
+        }
     }
 
     /**
diff --git a/blog/tests/events_test.php b/blog/tests/events_test.php
new file mode 100644 (file)
index 0000000..87f265d
--- /dev/null
@@ -0,0 +1,584 @@
+<?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/>.
+
+/**
+ * Events tests.
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2016 Stephen Bourget
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/blog/locallib.php');
+require_once($CFG->dirroot . '/blog/lib.php');
+
+/**
+ * Unit tests for the blog events.
+ *
+ * @copyright  2016 Stephen Bourget
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_blog_events_testcase extends advanced_testcase {
+
+    /** @var $courseid */
+    private $courseid;
+
+    /** @var $cmid */
+    private $cmid;
+
+    /** @var $groupid */
+    private $groupid;
+
+    /** @var $userid */
+    private $userid;
+
+    /** @var $tagid */
+    private $tagid;
+
+    /** @var $postid */
+    private $postid;
+
+    /**
+     * Setup the tests.
+     */
+    protected function setUp() {
+        global $DB;
+        parent::setUp();
+
+        $this->resetAfterTest();
+
+        // Create default course.
+        $course = $this->getDataGenerator()->create_course(array('category' => 1, 'shortname' => 'ANON'));
+        $this->assertNotEmpty($course);
+        $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
+        $this->assertNotEmpty($page);
+
+        // Create default group.
+        $group = new stdClass();
+        $group->courseid = $course->id;
+        $group->name = 'ANON';
+        $group->id = $DB->insert_record('groups', $group);
+
+        // Create default user.
+        $user = $this->getDataGenerator()->create_user(array(
+                'username' => 'testuser',
+                'firstname' => 'Jimmy',
+                'lastname' => 'Kinnon'
+        ));
+
+        // Create default tag.
+        $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id,
+            'rawname' => 'Testtagname', 'isstandard' => 1));
+
+        // Create default post.
+        $post = new stdClass();
+        $post->userid = $user->id;
+        $post->groupid = $group->id;
+        $post->content = 'test post content text';
+        $post->module = 'blog';
+        $post->id = $DB->insert_record('post', $post);
+
+        // Grab important ids.
+        $this->courseid = $course->id;
+        $this->cmid = $page->cmid;
+        $this->groupid  = $group->id;
+        $this->userid  = $user->id;
+        $this->tagid  = $tag->id;
+        $this->postid = $post->id;
+    }
+
+    /**
+     * Test various blog related events.
+     */
+    public function test_blog_entry_created_event() {
+        global $USER;
+
+        $this->setAdminUser();
+        $this->resetAfterTest();
+
+        // Create a blog entry for another user as Admin.
+        $sink = $this->redirectEvents();
+        $blog = new blog_entry();
+        $blog->subject = "Subject of blog";
+        $blog->userid = $this->userid;
+        $states = blog_entry::get_applicable_publish_states();
+        $blog->publishstate = reset($states);
+        $blog->add();
+        $events = $sink->get_events();
+        $sink->close();
+        $event = reset($events);
+        $sitecontext = context_system::instance();
+
+        // Validate event data.
+        $this->assertInstanceOf('\core\event\blog_entry_created', $event);
+        $url = new moodle_url('/blog/index.php', array('entryid' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
+        $this->assertEquals($sitecontext->id, $event->contextid);
+        $this->assertEquals($blog->id, $event->objectid);
+        $this->assertEquals($USER->id, $event->userid);
+        $this->assertEquals($this->userid, $event->relateduserid);
+        $this->assertEquals("post", $event->objecttable);
+        $arr = array(SITEID, 'blog', 'add', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id, $blog->subject);
+        $this->assertEventLegacyLogData($arr, $event);
+        $this->assertEquals("blog_entry_added", $event->get_legacy_eventname());
+        $this->assertEventLegacyData($blog, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Tests for event blog_entry_updated.
+     */
+    public function test_blog_entry_updated_event() {
+        global $USER;
+
+        $this->setAdminUser();
+        $this->resetAfterTest();
+        $sitecontext = context_system::instance();
+
+        // Edit a blog entry as Admin.
+        $blog = new blog_entry($this->postid);
+        $sink = $this->redirectEvents();
+        $blog->summary_editor = array('text' => 'Something', 'format' => FORMAT_MOODLE);
+        $blog->edit(array(), null, array(), array());
+        $events = $sink->get_events();
+        $event = array_pop($events);
+        $sink->close();
+
+        // Validate event data.
+        $this->assertInstanceOf('\core\event\blog_entry_updated', $event);
+        $url = new moodle_url('/blog/index.php', array('entryid' => $event->objectid));
+        $this->assertEquals($url, $event->get_url());
+        $this->assertEquals($sitecontext->id, $event->contextid);
+        $this->assertEquals($blog->id, $event->objectid);
+        $this->assertEquals($USER->id, $event->userid);
+        $this->assertEquals($this->userid, $event->relateduserid);
+        $this->assertEquals("post", $event->objecttable);
+        $this->assertEquals("blog_entry_edited", $event->get_legacy_eventname());
+        $this->assertEventLegacyData($blog, $event);
+        $arr = array (SITEID, 'blog', 'update', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id, $blog->subject);
+        $this->assertEventLegacyLogData($arr, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Tests for event blog_entry_deleted.
+     */
+    public function test_blog_entry_deleted_event() {
+        global $USER, $DB;
+
+        $this->setAdminUser();
+        $this->resetAfterTest();
+        $sitecontext = context_system::instance();
+
+        // Delete a user blog entry as Admin.
+        $blog = new blog_entry($this->postid);
+        $sink = $this->redirectEvents();
+        $record = $DB->get_record('post', array('id' => $blog->id));
+        $blog->delete();
+        $events = $sink->get_events();
+        $event = array_pop($events);
+        $sink->close();
+
+        // Validate event data.
+        $this->assertInstanceOf('\core\event\blog_entry_deleted', $event);
+        $this->assertEquals(null, $event->get_url());
+        $this->assertEquals($sitecontext->id, $event->contextid);
+        $this->assertEquals($blog->id, $event->objectid);
+        $this->assertEquals($USER->id, $event->userid);
+        $this->assertEquals($this->userid, $event->relateduserid);
+        $this->assertEquals("post", $event->objecttable);
+        $this->assertEquals($record, $event->get_record_snapshot("post", $blog->id));
+        $this->assertSame('blog_entry_deleted', $event->get_legacy_eventname());
+        $arr = array(SITEID, 'blog', 'delete', 'index.php?userid=' . $blog->userid, 'deleted blog entry with entry id# ' .
+                $blog->id);
+        $this->assertEventLegacyLogData($arr, $event);
+        $this->assertEventLegacyData($blog, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Tests for event blog_association_deleted.
+     */
+    public function test_blog_association_deleted_event() {
+        global $USER;
+
+        $this->setAdminUser();
+        $this->resetAfterTest();
+        $sitecontext = context_system::instance();
+        $coursecontext = context_course::instance($this->courseid);
+        $contextmodule = context_module::instance($this->cmid);
+
+        // Add blog associations with a course.
+        $blog = new blog_entry($this->postid);
+        $blog->add_association($coursecontext->id);
+
+        $sink = $this->redirectEvents();
+        $blog->remove_associations();
+        $events = $sink->get_events();
+        $event = reset($events);
+        $sink->close();
+
+        // Validate event data.
+        $this->assertInstanceOf('\core\event\blog_association_deleted', $event);
+        $this->assertEquals($sitecontext->id, $event->contextid);
+        $this->assertEquals($blog->id, $event->other['blogid']);
+        $this->assertEquals($USER->id, $event->userid);
+        $this->assertEquals($this->userid, $event->relateduserid);
+        $this->assertEquals('blog_association', $event->objecttable);
+
+        // Add blog associations with a module.
+        $blog = new blog_entry($this->postid);
+        $blog->add_association($contextmodule->id);
+        $sink = $this->redirectEvents();
+        $blog->remove_associations();
+        $events = $sink->get_events();
+        $event = reset($events);
+        $sink->close();
+
+        // Validate event data.
+        $this->assertEquals($blog->id, $event->other['blogid']);
+        $this->assertEquals($USER->id, $event->userid);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Tests for event blog_association_created.
+     */
+    public function test_blog_association_created_event() {
+        global $USER;
+
+        $this->setAdminUser();
+        $this->resetAfterTest();
+        $sitecontext = context_system::instance();
+        $coursecontext = context_course::instance($this->courseid);
+        $contextmodule = context_module::instance($this->cmid);
+
+        // Add blog associations with a course.
+        $blog = new blog_entry($this->postid);
+        $sink = $this->redirectEvents();
+        $blog->add_association($coursecontext->id);
+        $events = $sink->get_events();
+        $event = reset($events);
+        $sink->close();
+
+        // Validate event data.
+        $this->assertInstanceOf('\core\event\blog_association_created', $event);
+        $this->assertEquals($sitecontext->id, $event->contextid);
+        $url = new moodle_url('/blog/index.php', array('entryid' => $event->other['blogid']));
+        $this->assertEquals($url, $event->get_url());
+        $this->assertEquals($blog->id, $event->other['blogid']);
+        $this->assertEquals($this->courseid, $event->other['associateid']);
+        $this->assertEquals('course', $event->other['associatetype']);
+        $this->assertEquals($blog->subject, $event->other['subject']);
+        $this->assertEquals($USER->id, $event->userid);
+        $this->assertEquals($this->userid, $event->relateduserid);
+        $this->assertEquals('blog_association', $event->objecttable);
+        $arr = array(SITEID, 'blog', 'add association', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id,
+                     $blog->subject, 0, $this->userid);
+        $this->assertEventLegacyLogData($arr, $event);
+
+        // Add blog associations with a module.
+        $blog = new blog_entry($this->postid);
+        $sink = $this->redirectEvents();
+        $blog->add_association($contextmodule->id);
+        $events = $sink->get_events();
+        $event = reset($events);
+        $sink->close();
+
+        // Validate event data.
+        $this->assertEquals($blog->id, $event->other['blogid']);
+        $this->assertEquals($this->cmid, $event->other['associateid']);
+        $this->assertEquals('coursemodule', $event->other['associatetype']);
+        $arr = array(SITEID, 'blog', 'add association', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id,
+                     $blog->subject, $this->cmid, $this->userid);
+        $this->assertEventLegacyLogData($arr, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Tests for event blog_association_created validations.
+     */
+    public function test_blog_association_created_event_validations() {
+
+        $this->resetAfterTest();
+
+         // Make sure associatetype validations work.
+        try {
+            \core\event\blog_association_created::create(array(
+                'contextid' => 1,
+                'objectid' => 3,
+                'relateduserid' => 2,
+                'other' => array('associateid' => 2 , 'blogid' => 3, 'subject' => 'blog subject')));
+        } catch (coding_exception $e) {
+            $this->assertContains('The \'associatetype\' value must be set in other and be a valid type.', $e->getMessage());
+        }
+        try {
+            \core\event\blog_association_created::create(array(
+                'contextid' => 1,
+                'objectid' => 3,
+                'relateduserid' => 2,
+                'other' => array('associateid' => 2 , 'blogid' => 3, 'associatetype' => 'random', 'subject' => 'blog subject')));
+        } catch (coding_exception $e) {
+            $this->assertContains('The \'associatetype\' value must be set in other and be a valid type.', $e->getMessage());
+        }
+        // Make sure associateid validations work.
+        try {
+            \core\event\blog_association_created::create(array(
+                'contextid' => 1,
+                'objectid' => 3,
+                'relateduserid' => 2,
+                'other' => array('blogid' => 3, 'associatetype' => 'course', 'subject' => 'blog subject')));
+        } catch (coding_exception $e) {
+            $this->assertContains('The \'associateid\' value must be set in other.', $e->getMessage());
+        }
+        // Make sure blogid validations work.
+        try {
+            \core\event\blog_association_created::create(array(
+                'contextid' => 1,
+                'objectid' => 3,
+                'relateduserid' => 2,
+                'other' => array('associateid' => 3, 'associatetype' => 'course', 'subject' => 'blog subject')));
+        } catch (coding_exception $e) {
+            $this->assertContains('The \'blogid\' value must be set in other.', $e->getMessage());
+        }
+        // Make sure blogid validations work.
+        try {
+            \core\event\blog_association_created::create(array(
+                'contextid' => 1,
+                'objectid' => 3,
+                'relateduserid' => 2,
+                'other' => array('blogid' => 3, 'associateid' => 3, 'associatetype' => 'course')));
+        } catch (coding_exception $e) {
+            $this->assertContains('The \'subject\' value must be set in other.', $e->getMessage());
+        }
+    }
+
+    /**
+     * Tests for event blog_entries_viewed.
+     */
+    public function test_blog_entries_viewed_event() {
+
+        $this->setAdminUser();
+
+        $other = array('entryid' => $this->postid, 'tagid' => $this->tagid, 'userid' => $this->userid, 'modid' => $this->cmid,
+                       'groupid' => $this->groupid, 'courseid' => $this->courseid, 'search' => 'search', 'fromstart' => 2);
+
+        // Trigger event.
+        $sink = $this->redirectEvents();
+        $eventparams = array('other' => $other);
+        $eventinst = \core\event\blog_entries_viewed::create($eventparams);
+        $eventinst->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+        $sink->close();
+
+        // Validate event data.
+        $url = new moodle_url('/blog/index.php', $other);
+        $url2 = new moodle_url('index.php', $other);
+        $this->assertEquals($url, $event->get_url());
+        $arr = array(SITEID, 'blog', 'view', $url2->out(), 'view blog entry');
+        $this->assertEventLegacyLogData($arr, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test comment_created event.
+     */
+    public function test_blog_comment_created_event() {
+        global $USER, $CFG;
+
+        $this->setAdminUser();
+
+        require_once($CFG->dirroot . '/comment/lib.php');
+        $context = context_user::instance($USER->id);
+
+        $cmt = new stdClass();
+        $cmt->context = $context;
+        $cmt->courseid = $this->courseid;
+        $cmt->area = 'format_blog';
+        $cmt->itemid = $this->postid;
+        $cmt->showcount = 1;
+        $cmt->component = 'blog';
+        $manager = new comment($cmt);
+
+        // Triggering and capturing the event.
+        $sink = $this->redirectEvents();
+        $manager->add("New comment");
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Checking that the event contains the expected values.
+        $this->assertInstanceOf('\core\event\blog_comment_created', $event);
+        $this->assertEquals($context, $event->get_context());
+        $this->assertEquals($this->postid, $event->other['itemid']);
+        $url = new moodle_url('/blog/index.php', array('entryid' => $this->postid));
+        $this->assertEquals($url, $event->get_url());
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test comment_deleted event.
+     */
+    public function test_blog_comment_deleted_event() {
+        global $USER, $CFG;
+
+        $this->setAdminUser();
+
+        require_once($CFG->dirroot . '/comment/lib.php');
+        $context = context_user::instance($USER->id);
+
+        $cmt = new stdClass();
+        $cmt->context = $context;
+        $cmt->courseid = $this->courseid;
+        $cmt->area = 'format_blog';
+        $cmt->itemid = $this->postid;
+        $cmt->showcount = 1;
+        $cmt->component = 'blog';
+        $manager = new comment($cmt);
+        $newcomment = $manager->add("New comment");
+
+        // Triggering and capturing the event.
+        $sink = $this->redirectEvents();
+        $manager->delete($newcomment->id);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Checking that the event contains the expected values.
+        $this->assertInstanceOf('\core\event\blog_comment_deleted', $event);
+        $this->assertEquals($context, $event->get_context());
+        $this->assertEquals($this->postid, $event->other['itemid']);
+        $url = new moodle_url('/blog/index.php', array('entryid' => $this->postid));
+        $this->assertEquals($url, $event->get_url());
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test external blog added event.
+     *
+     * There is no external API for this, so the unit test will simply
+     * create and trigger the event and ensure data is returned as expected.
+     */
+    public function test_external_blog_added_event() {
+
+        // Trigger an event: external blog added.
+        $eventparams = array(
+            'context' => $context = context_system::instance(),
+            'objectid' => 1001,
+            'other' => array('url' => 'http://moodle.org')
+        );
+
+        $event = \core\event\blog_external_added::create($eventparams);
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\blog_external_added', $event);
+        $this->assertEquals(1001, $event->objectid);
+        $this->assertEquals('http://moodle.org', $event->other['url']);
+        $this->assertDebuggingNotCalled();
+    }
+
+    /**
+     * Test external blog updated event.
+     *
+     * There is no external API for this, so the unit test will simply
+     * create and trigger the event and ensure data is returned as expected.
+     */
+    public function test_external_blog_updated_event() {
+
+        // Trigger an event: external blog updated.
+        $eventparams = array(
+            'context' => $context = context_system::instance(),
+            'objectid' => 1001,
+            'other' => array('url' => 'http://moodle.org')
+        );
+
+        $event = \core\event\blog_external_updated::create($eventparams);
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\blog_external_updated', $event);
+        $this->assertEquals(1001, $event->objectid);
+        $this->assertEquals('http://moodle.org', $event->other['url']);
+        $this->assertDebuggingNotCalled();
+    }
+
+    /**
+     * Test external blog removed event.
+     *
+     * There is no external API for this, so the unit test will simply
+     * create and trigger the event and ensure data is returned as expected.
+     */
+    public function test_external_blog_removed_event() {
+
+        // Trigger an event: external blog removed.
+        $eventparams = array(
+            'context' => $context = context_system::instance(),
+            'objectid' => 1001,
+        );
+
+        $event = \core\event\blog_external_removed::create($eventparams);
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\blog_external_removed', $event);
+        $this->assertEquals(1001, $event->objectid);
+        $this->assertDebuggingNotCalled();
+    }
+
+    /**
+     * Test external blogs viewed event.
+     *
+     * There is no external API for this, so the unit test will simply
+     * create and trigger the event and ensure data is returned as expected.
+     */
+    public function test_external_blogs_viewed_event() {
+
+        // Trigger an event: external blogs viewed.
+        $eventparams = array(
+            'context' => $context = context_system::instance(),
+        );
+
+        $event = \core\event\blog_external_viewed::create($eventparams);
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\blog_external_viewed', $event);
+        $this->assertDebuggingNotCalled();
+    }
+}
+
index 80c3be5..a690e63 100644 (file)
@@ -23,6 +23,8 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+defined('MOODLE_INTERNAL') || die();
+
 global $CFG;
 require_once($CFG->dirroot . '/blog/locallib.php');
 require_once($CFG->dirroot . '/blog/lib.php');
@@ -151,329 +153,6 @@ class core_blog_lib_testcase extends advanced_testcase {
         $this->assertNotEquals($blogheaders['heading'], '');
     }
 
-    /**
-     * Test various blog related events.
-     */
-    public function test_blog_entry_created_event() {
-        global $USER;
-
-        $this->setAdminUser();
-        $this->resetAfterTest();
-
-        // Create a blog entry for another user as Admin.
-        $sink = $this->redirectEvents();
-        $blog = new blog_entry();
-        $blog->subject = "Subject of blog";
-        $blog->userid = $this->userid;
-        $states = blog_entry::get_applicable_publish_states();
-        $blog->publishstate = reset($states);
-        $blog->add();
-        $events = $sink->get_events();
-        $sink->close();
-        $event = reset($events);
-        $sitecontext = context_system::instance();
-
-        // Validate event data.
-        $this->assertInstanceOf('\core\event\blog_entry_created', $event);
-        $url = new moodle_url('/blog/index.php', array('entryid' => $event->objectid));
-        $this->assertEquals($url, $event->get_url());
-        $this->assertEquals($sitecontext->id, $event->contextid);
-        $this->assertEquals($blog->id, $event->objectid);
-        $this->assertEquals($USER->id, $event->userid);
-        $this->assertEquals($this->userid, $event->relateduserid);
-        $this->assertEquals("post", $event->objecttable);
-        $arr = array(SITEID, 'blog', 'add', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id, $blog->subject);
-        $this->assertEventLegacyLogData($arr, $event);
-        $this->assertEquals("blog_entry_added", $event->get_legacy_eventname());
-        $this->assertEventLegacyData($blog, $event);
-        $this->assertEventContextNotUsed($event);
-    }
-
-    /**
-     * Tests for event blog_entry_updated.
-     */
-    public function test_blog_entry_updated_event() {
-        global $USER;
-
-        $this->setAdminUser();
-        $this->resetAfterTest();
-        $sitecontext = context_system::instance();
-
-        // Edit a blog entry as Admin.
-        $blog = new blog_entry($this->postid);
-        $sink = $this->redirectEvents();
-        $blog->summary_editor = array('text' => 'Something', 'format' => FORMAT_MOODLE);
-        $blog->edit(array(), null, array(), array());
-        $events = $sink->get_events();
-        $event = array_pop($events);
-        $sink->close();
-
-        // Validate event data.
-        $this->assertInstanceOf('\core\event\blog_entry_updated', $event);
-        $url = new moodle_url('/blog/index.php', array('entryid' => $event->objectid));
-        $this->assertEquals($url, $event->get_url());
-        $this->assertEquals($sitecontext->id, $event->contextid);
-        $this->assertEquals($blog->id, $event->objectid);
-        $this->assertEquals($USER->id, $event->userid);
-        $this->assertEquals($this->userid, $event->relateduserid);
-        $this->assertEquals("post", $event->objecttable);
-        $this->assertEquals("blog_entry_edited", $event->get_legacy_eventname());
-        $this->assertEventLegacyData($blog, $event);
-        $arr = array (SITEID, 'blog', 'update', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id, $blog->subject);
-        $this->assertEventLegacyLogData($arr, $event);
-        $this->assertEventContextNotUsed($event);
-    }
-
-    /**
-     * Tests for event blog_entry_deleted.
-     */
-    public function test_blog_entry_deleted_event() {
-        global $USER, $DB;
-
-        $this->setAdminUser();
-        $this->resetAfterTest();
-        $sitecontext = context_system::instance();
-
-        // Delete a user blog entry as Admin.
-        $blog = new blog_entry($this->postid);
-        $sink = $this->redirectEvents();
-        $record = $DB->get_record('post', array('id' => $blog->id));
-        $blog->delete();
-        $events = $sink->get_events();
-        $event = array_pop($events);
-        $sink->close();
-
-        // Validate event data.
-        $this->assertInstanceOf('\core\event\blog_entry_deleted', $event);
-        $this->assertEquals(null, $event->get_url());
-        $this->assertEquals($sitecontext->id, $event->contextid);
-        $this->assertEquals($blog->id, $event->objectid);
-        $this->assertEquals($USER->id, $event->userid);
-        $this->assertEquals($this->userid, $event->relateduserid);
-        $this->assertEquals("post", $event->objecttable);
-        $this->assertEquals($record, $event->get_record_snapshot("post", $blog->id));
-        $this->assertSame('blog_entry_deleted', $event->get_legacy_eventname());
-        $arr = array(SITEID, 'blog', 'delete', 'index.php?userid=' . $blog->userid, 'deleted blog entry with entry id# ' .
-                $blog->id);
-        $this->assertEventLegacyLogData($arr, $event);
-        $this->assertEventLegacyData($blog, $event);
-        $this->assertEventContextNotUsed($event);
-    }
-
-
-    /**
-     * Tests for event blog_association_created.
-     */
-    public function test_blog_association_created_event() {
-        global $USER;
-
-        $this->setAdminUser();
-        $this->resetAfterTest();
-        $sitecontext = context_system::instance();
-        $coursecontext = context_course::instance($this->courseid);
-        $contextmodule = context_module::instance($this->cmid);
-
-        // Add blog associations with a course.
-        $blog = new blog_entry($this->postid);
-        $sink = $this->redirectEvents();
-        $blog->add_association($coursecontext->id);
-        $events = $sink->get_events();
-        $event = reset($events);
-        $sink->close();
-
-        // Validate event data.
-        $this->assertInstanceOf('\core\event\blog_association_created', $event);
-        $this->assertEquals($sitecontext->id, $event->contextid);
-        $url = new moodle_url('/blog/index.php', array('entryid' => $event->other['blogid']));
-        $this->assertEquals($url, $event->get_url());
-        $this->assertEquals($blog->id, $event->other['blogid']);
-        $this->assertEquals($this->courseid, $event->other['associateid']);
-        $this->assertEquals('course', $event->other['associatetype']);
-        $this->assertEquals($blog->subject, $event->other['subject']);
-        $this->assertEquals($USER->id, $event->userid);
-        $this->assertEquals($this->userid, $event->relateduserid);
-        $this->assertEquals('blog_association', $event->objecttable);
-        $arr = array(SITEID, 'blog', 'add association', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id,
-                     $blog->subject, 0, $this->userid);
-        $this->assertEventLegacyLogData($arr, $event);
-
-        // Add blog associations with a module.
-        $blog = new blog_entry($this->postid);
-        $sink = $this->redirectEvents();
-        $blog->add_association($contextmodule->id);
-        $events = $sink->get_events();
-        $event = reset($events);
-        $sink->close();
-
-        // Validate event data.
-        $this->assertEquals($blog->id, $event->other['blogid']);
-        $this->assertEquals($this->cmid, $event->other['associateid']);
-        $this->assertEquals('coursemodule', $event->other['associatetype']);
-        $arr = array(SITEID, 'blog', 'add association', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id,
-                     $blog->subject, $this->cmid, $this->userid);
-        $this->assertEventLegacyLogData($arr, $event);
-        $this->assertEventContextNotUsed($event);
-    }
-
-    /**
-     * Tests for event blog_association_created validations.
-     */
-    public function test_blog_association_created_event_validations() {
-
-        $this->resetAfterTest();
-
-         // Make sure associatetype validations work.
-        try {
-            \core\event\blog_association_created::create(array(
-                'contextid' => 1,
-                'objectid' => 3,
-                'relateduserid' => 2,
-                'other' => array('associateid' => 2 , 'blogid' => 3, 'subject' => 'blog subject')));
-        } catch (coding_exception $e) {
-            $this->assertContains('The \'associatetype\' value must be set in other and be a valid type.', $e->getMessage());
-        }
-        try {
-            \core\event\blog_association_created::create(array(
-                'contextid' => 1,
-                'objectid' => 3,
-                'relateduserid' => 2,
-                'other' => array('associateid' => 2 , 'blogid' => 3, 'associatetype' => 'random', 'subject' => 'blog subject')));
-        } catch (coding_exception $e) {
-            $this->assertContains('The \'associatetype\' value must be set in other and be a valid type.', $e->getMessage());
-        }
-        // Make sure associateid validations work.
-        try {
-            \core\event\blog_association_created::create(array(
-                'contextid' => 1,
-                'objectid' => 3,
-                'relateduserid' => 2,
-                'other' => array('blogid' => 3, 'associatetype' => 'course', 'subject' => 'blog subject')));
-        } catch (coding_exception $e) {
-            $this->assertContains('The \'associateid\' value must be set in other.', $e->getMessage());
-        }
-        // Make sure blogid validations work.
-        try {
-            \core\event\blog_association_created::create(array(
-                'contextid' => 1,
-                'objectid' => 3,
-                'relateduserid' => 2,
-                'other' => array('associateid' => 3, 'associatetype' => 'course', 'subject' => 'blog subject')));
-        } catch (coding_exception $e) {
-            $this->assertContains('The \'blogid\' value must be set in other.', $e->getMessage());
-        }
-        // Make sure blogid validations work.
-        try {
-            \core\event\blog_association_created::create(array(
-                'contextid' => 1,
-                'objectid' => 3,
-                'relateduserid' => 2,
-                'other' => array('blogid' => 3, 'associateid' => 3, 'associatetype' => 'course')));
-        } catch (coding_exception $e) {
-            $this->assertContains('The \'subject\' value must be set in other.', $e->getMessage());
-        }
-    }
-
-    /**
-     * Tests for event blog_entries_viewed.
-     */
-    public function test_blog_entries_viewed_event() {
-
-        $this->setAdminUser();
-
-        $other = array('entryid' => $this->postid, 'tagid' => $this->tagid, 'userid' => $this->userid, 'modid' => $this->cmid,
-                       'groupid' => $this->groupid, 'courseid' => $this->courseid, 'search' => 'search', 'fromstart' => 2);
-
-        // Trigger event.
-        $sink = $this->redirectEvents();
-        $eventparams = array('other' => $other);
-        $eventinst = \core\event\blog_entries_viewed::create($eventparams);
-        $eventinst->trigger();
-        $events = $sink->get_events();
-        $event = reset($events);
-        $sink->close();
-
-        // Validate event data.
-        $url = new moodle_url('/blog/index.php', $other);
-        $url2 = new moodle_url('index.php', $other);
-        $this->assertEquals($url, $event->get_url());
-        $arr = array(SITEID, 'blog', 'view', $url2->out(), 'view blog entry');
-        $this->assertEventLegacyLogData($arr, $event);
-        $this->assertEventContextNotUsed($event);
-    }
-
-    /**
-     * Test comment_created event.
-     */
-    public function test_blog_comment_created_event() {
-        global $USER, $CFG;
-
-        $this->setAdminUser();
-
-        require_once($CFG->dirroot . '/comment/lib.php');
-        $context = context_user::instance($USER->id);
-
-        $cmt = new stdClass();
-        $cmt->context = $context;
-        $cmt->courseid = $this->courseid;
-        $cmt->area = 'format_blog';
-        $cmt->itemid = $this->postid;
-        $cmt->showcount = 1;
-        $cmt->component = 'blog';
-        $manager = new comment($cmt);
-
-        // Triggering and capturing the event.
-        $sink = $this->redirectEvents();
-        $manager->add("New comment");
-        $events = $sink->get_events();
-        $this->assertCount(1, $events);
-        $event = reset($events);
-
-        // Checking that the event contains the expected values.
-        $this->assertInstanceOf('\core\event\blog_comment_created', $event);
-        $this->assertEquals($context, $event->get_context());
-        $this->assertEquals($this->postid, $event->other['itemid']);
-        $url = new moodle_url('/blog/index.php', array('entryid' => $this->postid));
-        $this->assertEquals($url, $event->get_url());
-        $this->assertEventContextNotUsed($event);
-    }
-
-    /**
-     * Test comment_deleted event.
-     */
-    public function test_blog_comment_deleted_event() {
-        global $USER, $CFG;
-
-        $this->setAdminUser();
-
-        require_once($CFG->dirroot . '/comment/lib.php');
-        $context = context_user::instance($USER->id);
-
-        $cmt = new stdClass();
-        $cmt->context = $context;
-        $cmt->courseid = $this->courseid;
-        $cmt->area = 'format_blog';
-        $cmt->itemid = $this->postid;
-        $cmt->showcount = 1;
-        $cmt->component = 'blog';
-        $manager = new comment($cmt);
-        $newcomment = $manager->add("New comment");
-
-        // Triggering and capturing the event.
-        $sink = $this->redirectEvents();
-        $manager->delete($newcomment->id);
-        $events = $sink->get_events();
-        $this->assertCount(1, $events);
-        $event = reset($events);
-
-        // Checking that the event contains the expected values.
-        $this->assertInstanceOf('\core\event\blog_comment_deleted', $event);
-        $this->assertEquals($context, $event->get_context());
-        $this->assertEquals($this->postid, $event->other['itemid']);
-        $url = new moodle_url('/blog/index.php', array('entryid' => $this->postid));
-        $this->assertEquals($url, $event->get_url());
-        $this->assertEventContextNotUsed($event);
-    }
-
     /**
      * Tests the core_blog_myprofile_navigation() function.
      */
diff --git a/cache/stores/apcu/addinstanceform.php b/cache/stores/apcu/addinstanceform.php
new file mode 100644 (file)
index 0000000..f9fc73e
--- /dev/null
@@ -0,0 +1,85 @@
+<?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 library file for the apcu cache store.
+ *
+ * This file is part of the apcu cache store, it contains the API for interacting with an instance of the store.
+ *
+ * @package    cachestore_apcu
+ * @copyright  2014 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot.'/cache/forms.php');
+/**
+ * Form for adding a apcu instance.
+ *
+ * @copyright  2014 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cachestore_apcu_addinstance_form extends cachestore_addinstance_form {
+    /**
+     * Add the desired form elements.
+     */
+    protected function configuration_definition() {
+        global $CFG;
+        $form = $this->_form;
+        $form->addElement('text', 'prefix', get_string('prefix', 'cachestore_apcu'),
+            array('maxlength' => 5, 'size' => 5));
+        $form->addHelpButton('prefix', 'prefix', 'cachestore_apcu');
+        $form->setType('prefix', PARAM_TEXT); // We set to text but we have a rule to limit to alphanumext.
+        $form->setDefault('prefix', $CFG->prefix);
+        $form->addRule('prefix', get_string('prefixinvalid', 'cachestore_apcu'), 'regex', '#^[a-zA-Z0-9\-_]+$#');
+        $form->addElement('header', 'apc_notice', get_string('notice', 'cachestore_apcu'));
+        $form->setExpanded('apc_notice');
+        $link = get_docs_url('Caching#APC');
+        $form->addElement('html', nl2br(get_string('clusternotice', 'cachestore_apcu', $link)));
+    }
+
+    /**
+     * Validates the configuration data.
+     *
+     * We need to check that prefix is unique.
+     *
+     * @param array $data
+     * @param array $files
+     * @param array $errors
+     * @return array
+     * @throws coding_exception
+     */
+    public function configuration_validation($data, $files, array $errors) {
+        if (empty($errors['prefix'])) {
+            $factory = cache_factory::instance();
+            $config = $factory->create_config_instance();
+            foreach ($config->get_all_stores() as $store) {
+                if ($store['plugin'] === 'apcu') {
+                    if (isset($store['configuration']['prefix'])) {
+                        if ($data['prefix'] === $store['configuration']['prefix']) {
+                            // The new store has the same prefix as an existing store, thats a problem.
+                            $errors['prefix'] = get_string('prefixnotunique', 'cachestore_apcu');
+                            break;
+                        }
+                    } else if (empty($data['prefix'])) {
+                        // The existing store hasn't got a prefix and neither does the new store, that's a problem.
+                        $errors['prefix'] = get_string('prefixnotunique', 'cachestore_apcu');
+                        break;
+                    }
+                }
+            }
+        }
+        return $errors;
+    }
+}
diff --git a/cache/stores/apcu/lang/en/cachestore_apcu.php b/cache/stores/apcu/lang/en/cachestore_apcu.php
new file mode 100644 (file)
index 0000000..95608a6
--- /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/>.
+
+/**
+ * APCu cache store language strings.
+ *
+ * @package    cachestore_apcu
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['clusternotice'] = 'Please be aware that APCu only a suitable choice for single node sites or caches that can be stored locally.
+For more information see <a href="{$a}">Moodle docs</a>';
+$string['notice'] = 'Notice';
+$string['pluginname'] = 'APC User Cache (APCu)';
+$string['prefix'] = 'Prefix';
+$string['prefix_help'] = 'The above prefix gets used for all keys being stored in this APC store instance. By default the database prefix is used.';
+$string['prefixinvalid'] = 'The prefix you have selected is invalid. You can only use a-z A-Z 0-9-_.';
+$string['prefixnotunique'] = 'The prefix you have selected is not unique. Please choose a unique prefix.';
+$string['testperformance'] = 'Test performance';
+$string['testperformance_desc'] = 'If enabled APCu performance will be included when viewing the Test performance page in the administration block. Enabling this on a production site is not recommended.';
diff --git a/cache/stores/apcu/lib.php b/cache/stores/apcu/lib.php
new file mode 100644 (file)
index 0000000..a0dd6b8
--- /dev/null
@@ -0,0 +1,418 @@
+<?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/>.
+
+/**
+ * APCu cache store main library.
+ *
+ * @package    cachestore_apcu
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The APCu cache store class.
+ *
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cachestore_apcu extends cache_store implements cache_is_key_aware, cache_is_configurable {
+
+    /**
+     * The required version of APCu for this extension.
+     */
+    const REQUIRED_VERSION = '4.0.0';
+
+    /**
+     * The name of this store instance.
+     * @var string
+     */
+    protected $name;
+
+    /**
+     * The definition used when this instance was initialised.
+     * @var cache_definition
+     */
+    protected $definition = null;
+
+    /**
+     * The storeprefix to use on all instances of this store.  Configured as part store setup.
+     * @var string
+     */
+    protected $storeprefix = null;
+
+    /**
+     * The prefix added specifically for this cache.
+     * @var string
+     */
+    protected $cacheprefix = null;
+
+    /**
+     * Static method to check that the APCu stores requirements have been met.
+     *
+     * It checks that the APCu extension has been loaded and that it has been enabled.
+     *
+     * @return bool True if the stores software/hardware requirements have been met and it can be used. False otherwise.
+     */
+    public static function are_requirements_met() {
+        if (!extension_loaded('apcu') || !ini_get('apc.enabled')) {
+            return false;
+        }
+
+        $version = phpversion('apcu');
+        return $version && version_compare($version, self::REQUIRED_VERSION, '>=');
+    }
+
+    /**
+     * Static method to check if a store is usable with the given mode.
+     *
+     * @param int $mode One of cache_store::MODE_*
+     * @return bool True if the mode is supported.
+     */
+    public static function is_supported_mode($mode) {
+        return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION);
+    }
+
+    /**
+     * Returns the supported features as a binary flag.
+     *
+     * @param array $configuration The configuration of a store to consider specifically.
+     * @return int The supported features.
+     */
+    public static function get_supported_features(array $configuration = array()) {
+        return self::SUPPORTS_NATIVE_TTL;
+    }
+
+    /**
+     * Returns the supported modes as a binary flag.
+     *
+     * @param array $configuration The configuration of a store to consider specifically.
+     * @return int The supported modes.
+     */
+    public static function get_supported_modes(array $configuration = array()) {
+        return self::MODE_APPLICATION + self::MODE_SESSION;
+    }
+
+    /**
+     * Constructs an instance of the cache store.
+     *
+     * This method should not create connections or perform and processing, it should be used
+     *
+     * @param string $name The name of the cache store
+     * @param array $configuration The configuration for this store instance.
+     */
+    public function __construct($name, array $configuration = array()) {
+        global $CFG;
+        $this->name = $name;
+        $this->storeprefix = $CFG->prefix;
+        if (isset($configuration['prefix'])) {
+            $this->storeprefix = $configuration['prefix'];
+        }
+    }
+
+    /**
+     * Returns the name of this store instance.
+     * @return string
+     */
+    public function my_name() {
+        return $this->name;
+    }
+
+    /**
+     * Initialises a new instance of the cache store given the definition the instance is to be used for.
+     *
+     * This function should prepare any given connections etc.
+     *
+     * @param cache_definition $definition
+     * @return bool
+     */
+    public function initialise(cache_definition $definition) {
+        $this->definition = $definition;
+        $this->cacheprefix = $this->storeprefix.$definition->generate_definition_hash().'__';
+        return true;
+    }
+
+    /**
+     * Returns true if this cache store instance has been initialised.
+     * @return bool
+     */
+    public function is_initialised() {
+        return ($this->definition !== null);
+    }
+
+    /**
+     * Returns true if this cache store instance is ready to use.
+     * @return bool
+     */
+    public function is_ready() {
+        // No set up is actually required, providing apc is installed and enabled.
+        return true;
+    }
+
+    /**
+     * Prepares the given key for use.
+     *
+     * Should be called before all interaction.
+     *
+     * @param string $key The key to prepare for storing in APCu.
+     *
+     * @return string
+     */
+    protected function prepare_key($key) {
+        return $this->cacheprefix . $key;
+    }
+
+    /**
+     * Retrieves an item from the cache store given its key.
+     *
+     * @param string $key The key to retrieve
+     * @return mixed The data that was associated with the key, or false if the key did not exist.
+     */
+    public function get($key) {
+        $key = $this->prepare_key($key);
+        $success = false;
+        $outcome = apcu_fetch($key, $success);
+        if ($success) {
+            return $outcome;
+        }
+        return $success;
+    }
+
+    /**
+     * Retrieves several items from the cache store in a single transaction.
+     *
+     * If not all of the items are available in the cache then the data value for those that are missing will be set to false.
+     *
+     * @param array $keys The array of keys to retrieve
+     * @return array An array of items from the cache. There will be an item for each key, those that were not in the store will
+     *      be set to false.
+     */
+    public function get_many($keys) {
+        $map = array();
+        foreach ($keys as $key) {
+            $map[$key] = $this->prepare_key($key);
+        }
+        $outcomes = array();
+        $success = false;
+        $results = apcu_fetch($map, $success);
+        if ($success) {
+            foreach ($map as $key => $used) {
+                if (array_key_exists($used, $results)) {
+                    $outcomes[$key] = $results[$used];
+                } else {
+                    $outcomes[$key] = false;
+                }
+            }
+        } else {
+            $outcomes = array_fill_keys($keys, false);
+        }
+        return $outcomes;
+    }
+
+    /**
+     * Sets an item in the cache given its key and data value.
+     *
+     * @param string $key The key to use.
+     * @param mixed $data The data to set.
+     * @return bool True if the operation was a success false otherwise.
+     */
+    public function set($key, $data) {
+        $key = $this->prepare_key($key);
+        return apcu_store($key, $data, $this->definition->get_ttl());
+    }
+
+    /**
+     * Sets many items in the cache in a single transaction.
+     *
+     * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
+     *      keys, 'key' and 'value'.
+     * @return int The number of items successfully set. It is up to the developer to check this matches the number of items
+     *      sent ... if they care that is.
+     */
+    public function set_many(array $keyvaluearray) {
+        $map = array();
+        foreach ($keyvaluearray as $pair) {
+            $key = $this->prepare_key($pair['key']);
+            $map[$key] = $pair['value'];
+        }
+        $result = apcu_store($map, null, $this->definition->get_ttl());
+        return count($map) - count($result);
+    }
+
+    /**
+     * Deletes an item from the cache store.
+     *
+     * @param string $key The key to delete.
+     * @return bool Returns true if the operation was a success, false otherwise.
+     */
+    public function delete($key) {
+        $key = $this->prepare_key($key);
+        return apcu_delete($key);
+    }
+
+    /**
+     * Deletes several keys from the cache in a single action.
+     *
+     * @param array $keys The keys to delete
+     * @return int The number of items successfully deleted.
+     */
+    public function delete_many(array $keys) {
+        $count = 0;
+        foreach ($keys as $key) {
+            if ($this->delete($key)) {
+                $count++;
+            }
+        }
+        return $count;
+    }
+
+    /**
+     * Purges the cache deleting all items within it.
+     *
+     * @return boolean True on success. False otherwise.
+     */
+    public function purge() {
+        if (class_exists('APCUIterator', false)) {
+            $iterator = new APCUIterator('#^' . preg_quote($this->cacheprefix, '#') . '#');
+        } else {
+            $iterator = new APCIterator('user', '#^' . preg_quote($this->cacheprefix, '#') . '#');
+        }
+        return apcu_delete($iterator);
+    }
+
+    /**
+     * Performs any necessary clean up when the store instance is being deleted.
+     */
+    public function instance_deleted() {
+        if (class_exists('APCUIterator', false)) {
+            $iterator = new APCUIterator('#^' . preg_quote($this->storeprefix, '#') . '#');
+        } else {
+            $iterator = new APCIterator('user', '#^' . preg_quote($this->storeprefix, '#') . '#');
+        }
+        return apcu_delete($iterator);
+    }
+
+    /**
+     * Generates an instance of the cache store that can be used for testing.
+     *
+     * Returns an instance of the cache store, or false if one cannot be created.
+     *
+     * @param cache_definition $definition
+     * @return cache_store
+     */
+    public static function initialise_test_instance(cache_definition $definition) {
+        $testperformance = get_config('cachestore_apcu', 'testperformance');
+        if (empty($testperformance)) {
+            return false;
+        }
+        if (!self::are_requirements_met()) {
+            return false;
+        }
+        $name = 'APCu test';
+        $cache = new cachestore_apcu($name);
+        $cache->initialise($definition);
+        return $cache;
+    }
+
+    /**
+     * Test is a cache has a key.
+     *
+     * @param string|int $key
+     * @return bool True if the cache has the requested key, false otherwise.
+     */
+    public function has($key) {
+        $key = $this->prepare_key($key);
+        return apcu_exists($key);
+    }
+
+    /**
+     * Test if a cache has at least one of the given keys.
+     *
+     * @param array $keys
+     * @return bool True if the cache has at least one of the given keys
+     */
+    public function has_any(array $keys) {
+        foreach ($keys as $arraykey => $key) {
+            $keys[$arraykey] = $this->prepare_key($key);
+        }
+        $result = apcu_exists($keys);
+        return count($result) > 0;
+    }
+
+    /**
+     * Test is a cache has all of the given keys.
+     *
+     * @param array $keys
+     * @return bool True if the cache has all of the given keys, false otherwise.
+     */
+    public function has_all(array $keys) {
+        foreach ($keys as $arraykey => $key) {
+            $keys[$arraykey] = $this->prepare_key($key);
+        }
+        $result = apcu_exists($keys);
+        return count($result) === count($keys);
+    }
+
+    /**
+     * Generates an instance of the cache store that can be used for testing.
+     *
+     * @param cache_definition $definition
+     * @return cachestore_apcu|false
+     */
+    public static function initialise_unit_test_instance(cache_definition $definition) {
+        if (!self::are_requirements_met()) {
+            return false;
+        }
+
+        $store = new cachestore_apcu('Test APCu', array('prefix' => 'phpunit'));
+        if (!$store->is_ready()) {
+            return false;
+        }
+        $store->initialise($definition);
+
+        return $store;
+    }
+
+    /**
+     * Given the data from the add instance form this function creates a configuration array.
+     *
+     * @param stdClass $data
+     * @return array
+     */
+    public static function config_get_configuration_array($data) {
+        $config = array();
+
+        if (isset($data->prefix)) {
+            $config['prefix'] = $data->prefix;
+        }
+        return $config;
+    }
+    /**
+     * Allows the cache store to set its data against the edit form before it is shown to the user.
+     *
+     * @param moodleform $editform
+     * @param array $config
+     */
+    public static function config_set_edit_form_data(moodleform $editform, array $config) {
+        if (isset($config['prefix'])) {
+            $data['prefix'] = $config['prefix'];
+        } else {
+            $data['prefix'] = '';
+        }
+        $editform->set_data($data);
+    }
+}
diff --git a/cache/stores/apcu/settings.php b/cache/stores/apcu/settings.php
new file mode 100644 (file)
index 0000000..0b35e95
--- /dev/null
@@ -0,0 +1,36 @@
+<?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 settings for the APCu store.
+ *
+ * This file is part of the APCu cache store, it contains the API for interacting with an instance of the store.
+ *
+ * @package    cachestore_apcu
+ * @copyright  2014 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$settings->add(
+    new admin_setting_configcheckbox(
+        'cachestore_apcu/testperformance',
+        new lang_string('testperformance', 'cachestore_apcu'),
+        new lang_string('testperformance_desc', 'cachestore_apcu'),
+        false
+    )
+);
diff --git a/cache/stores/apcu/tests/apcu_test.php b/cache/stores/apcu/tests/apcu_test.php
new file mode 100644 (file)
index 0000000..36fc1ae
--- /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/>.
+
+/**
+ * APCu unit tests.
+ *
+ * @package    cachestore_apcu
+ * @copyright  2014 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+// Include the necessary evils.
+global $CFG;
+require_once($CFG->dirroot.'/cache/tests/fixtures/stores.php');
+require_once($CFG->dirroot.'/cache/stores/apcu/lib.php');
+
+/**
+ * APC unit test class.
+ *
+ * @package    cachestore_apcu
+ * @copyright  2014 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cachestore_apcu_test extends cachestore_tests {
+    /**
+     * Returns the apcu class name
+     * @return string
+     */
+    protected function get_class_name() {
+        return 'cachestore_apcu';
+    }
+
+    public function setUp() {
+        if (!cachestore_apcu::are_requirements_met()) {
+            $this->markTestSkipped('Could not test cachestore_apcu. Requirements are not met.');
+        }
+        return parent::setUp();
+    }
+
+    /**
+     * Test that the Moodle APCu store doesn't cross paths with other code using APCu as well.
+     */
+    public function test_cross_application_interaction() {
+        $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_apcu', 'phpunit_test');
+        $instance = cachestore_apcu::initialise_unit_test_instance($definition);
+
+        // Test purge with custom data.
+        $this->assertTrue($instance->set('test', 'monster'));
+        $this->assertSame('monster', $instance->get('test'));
+        $this->assertTrue(apcu_store('test', 'pirate', 180));
+        $this->assertSame('monster', $instance->get('test'));
+        $this->assertTrue(apcu_exists('test'));
+        $this->assertSame('pirate', apcu_fetch('test'));
+        // Purge and check that our data is gone but the the custom data is still there.
+        $this->assertTrue($instance->purge());
+        $this->assertFalse($instance->get('test'));
+        $this->assertTrue(apcu_exists('test'));
+        $this->assertSame('pirate', apcu_fetch('test'));
+    }
+
+    public function test_different_caches_have_different_prefixes() {
+        $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_apcu', 'phpunit_test');
+        $instance = cachestore_apcu::initialise_unit_test_instance($definition);
+        $definition2 = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_apcu', 'phpunit_test2');
+        $instance2 = cachestore_apcu::initialise_unit_test_instance($definition2);
+
+        $instance->set('test1', 1);
+        $this->assertFalse($instance2->get('test1'));
+        $instance2->purge();
+        $this->assertSame(1, $instance->get('test1'));
+    }
+}
\ No newline at end of file
diff --git a/cache/stores/apcu/version.php b/cache/stores/apcu/version.php
new file mode 100644 (file)
index 0000000..91d983a
--- /dev/null
@@ -0,0 +1,30 @@
+<?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/>.
+
+/**
+ * APCu cache store version information.
+ *
+ * @package    cachestore_apcu
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$plugin->version = 2016081600;
+$plugin->requires = 2016081100;
+$plugin->maturity = MATURITY_STABLE;
+$plugin->component = 'cachestore_apcu';
index 2e32b32..d895c9a 100644 (file)
Binary files a/calendar/yui/build/moodle-calendar-info/assets/skins/sam/moodle-calendar-info.css and b/calendar/yui/build/moodle-calendar-info/assets/skins/sam/moodle-calendar-info.css differ
index 2e32b32..d895c9a 100644 (file)
@@ -1,4 +1,24 @@
-.calendar-event-panel {background-color:#666;border:2px solid #666;border-width: 0 2px 2px 0;}
-.calendar-event-panel .yui3-overlay-content {background-color:#fff;border:1px solid #555;margin-top:-5px;margin-left:-5px;}
-.calendar-event-panel .yui3-overlay-content h2.eventtitle {margin:3px 5px 2px;padding:0;text-align:center;}
-.calendar-event-panel .eventcontent {margin:5px;padding:0;text-align:center;}
+.calendar-event-panel {
+    background-color: #666;
+    border: 2px solid #666;
+    border-width: 0 2px 2px 0;
+}
+
+.calendar-event-panel .yui3-overlay-content {
+    background-color: #fff;
+    border: 1px solid #555;
+    margin-top: -5px;
+    margin-left: -5px;
+}
+
+.calendar-event-panel .yui3-overlay-content h2.eventtitle {
+    margin: 3px 5px 2px;
+    padding: 0;
+    text-align: center;
+}
+
+.calendar-event-panel .eventcontent {
+    margin: 5px;
+    padding: 0;
+    text-align: center;
+}
index 49d97e2..53133ad 100644 (file)
@@ -36,11 +36,14 @@ defined('MOODLE_INTERNAL') || die();
 class invalid_persistent_exception extends \moodle_exception {
 
     public function __construct(array $errors = array()) {
+        $forhumans = array();
         $debuginfo = array();
         foreach ($errors as $key => $message) {
             $debuginfo[] = "$key: $message";
+            $forhumans[] = $message;
         }
-        parent::__construct('invalidpersistent', 'core_competency', null, null, implode(' - ', $debuginfo));
+        parent::__construct('invalidpersistenterror', 'core_competency', null,
+                implode(', ', $forhumans), implode(' - ', $debuginfo));
     }
 
 }
index 713e233..641d22b 100644 (file)
@@ -7,6 +7,6 @@
     "require-dev": {
         "phpunit/phpunit": "5.4.*",
         "phpunit/dbUnit": "1.4.*",
-        "moodlehq/behat-extension": "3.32.2"
+        "moodlehq/behat-extension": "3.32.3"
     }
 }
index 5810eda..f980783 100644 (file)
@@ -4,40 +4,41 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "902ce5735f2446cf9bcc305c0d8a191b",
-    "content-hash": "b60ecd0f5b6430a10ada7e4d4c38e73b",
+    "hash": "c3da2812d8753f3fb2429366b373afa8",
+    "content-hash": "a4e8fa2277e59a3c14afb3ae729bf4e7",
     "packages": [],
     "packages-dev": [
         {
             "name": "behat/behat",
-            "version": "v3.1.0",
+            "version": "v3.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/Behat.git",
-                "reference": "359d987b3064d78f2d3a6ba3a355277f3b09b47f"
+                "reference": "df7d9225e9ee37fdaa54273e3e0aecf2515bbe76"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Behat/zipball/359d987b3064d78f2d3a6ba3a355277f3b09b47f",
-                "reference": "359d987b3064d78f2d3a6ba3a355277f3b09b47f",
+                "url": "https://api.github.com/repos/Behat/Behat/zipball/df7d9225e9ee37fdaa54273e3e0aecf2515bbe76",
+                "reference": "df7d9225e9ee37fdaa54273e3e0aecf2515bbe76",
                 "shasum": ""
             },
             "require": {
-                "behat/gherkin": "~4.4",
+                "behat/gherkin": "^4.4.4",
                 "behat/transliterator": "~1.0",
                 "ext-mbstring": "*",
                 "php": ">=5.3.3",
-                "symfony/class-loader": "~2.1|~3.0",
-                "symfony/config": "~2.3|~3.0",
-                "symfony/console": "~2.1|~3.0",
-                "symfony/dependency-injection": "~2.1|~3.0",
-                "symfony/event-dispatcher": "~2.1|~3.0",
-                "symfony/translation": "~2.3|~3.0",
-                "symfony/yaml": "~2.1|~3.0"
+                "symfony/class-loader": "~2.1||~3.0",
+                "symfony/config": "~2.3||~3.0",
+                "symfony/console": "~2.5||~3.0",
+                "symfony/dependency-injection": "~2.1||~3.0",
+                "symfony/event-dispatcher": "~2.1||~3.0",
+                "symfony/translation": "~2.3||~3.0",
+                "symfony/yaml": "~2.1||~3.0"
             },
             "require-dev": {
+                "herrera-io/box": "~1.6.1",
                 "phpunit/phpunit": "~4.5",
-                "symfony/process": "~2.1|~3.0"
+                "symfony/process": "~2.5|~3.0"
             },
             "suggest": {
                 "behat/mink-extension": "for integration with Mink testing framework",
@@ -50,7 +51,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1</