Merge branch 'MDL-48947_v3' of https://github.com/Syxton/moodle
authorDan Poltawski <dan@moodle.com>
Mon, 28 Sep 2015 14:06:21 +0000 (15:06 +0100)
committerDan Poltawski <dan@moodle.com>
Mon, 28 Sep 2015 14:06:21 +0000 (15:06 +0100)
400 files changed:
.jshintrc
Gruntfile.js
admin/cli/install.php
admin/tool/behat/tests/behat/basic_actions.feature [deleted file]
admin/tool/behat/tests/behat/data_generators.feature
admin/tool/task/cli/schedule_task.php
admin/tool/templatelibrary/amd/build/display.min.js
admin/tool/templatelibrary/amd/build/search.min.js
admin/tool/templatelibrary/amd/src/display.js
admin/tool/templatelibrary/amd/src/search.js
admin/tool/templatelibrary/classes/external.php
admin/tool/templatelibrary/db/services.php
admin/user.php
auth/email/auth.php
auth/shibboleth/auth.php
auth/upgrade.txt
backup/cc/cc_lib/cc_assesment_truefalse.php
backup/cc/entity.quiz.class.php
backup/cc/entity11.quiz.class.php
badges/backpack_form.php
badges/backpackconnect.php
blocks/activity_results/styles.css
blog/edit.php
calendar/export_execute.php
completion/classes/external.php
completion/tests/behat/behat_completion.php
completion/tests/externallib_test.php
course/delete.php
course/editsection.php
course/externallib.php
course/format/topics/tests/behat/edit_delete_sections.feature
course/format/weeks/tests/behat/edit_delete_sections.feature
course/recent.php
course/rest.php
course/tests/behat/create_delete_course.feature
course/tests/externallib_test.php
enrol/flatfile/classes/task/flatfile_sync_task.php [new file with mode: 0644]
enrol/flatfile/cli/sync.php
enrol/flatfile/db/tasks.php [new file with mode: 0644]
enrol/flatfile/lang/en/enrol_flatfile.php
enrol/flatfile/lib.php
enrol/flatfile/tests/flatfile_test.php
enrol/flatfile/version.php
enrol/manual/ajax.php
enrol/manual/db/upgrade.php
enrol/manual/lang/en/enrol_manual.php
enrol/manual/lib.php
enrol/manual/manage.php
enrol/manual/settings.php
enrol/manual/version.php
enrol/manual/yui/quickenrolment/quickenrolment.js
enrol/self/classes/empty_form.php [new file with mode: 0644]
enrol/self/lib.php
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js
filter/glossary/yui/src/autolinker/js/autolinker.js
grade/edit/tree/index.php
grade/edit/tree/lib.php
grade/export/xml/grade_export_xml.php
grade/grading/lib.php
grade/lib.php
grade/report/overview/lib.php
grade/tests/behat/behat_grade.php
grade/tests/behat/grade_aggregation.feature
grade/tests/behat/grade_aggregation_changes.feature
grade/tests/behat/grade_average.feature
grade/tests/behat/grade_calculated_grade_items.feature
grade/tests/behat/grade_calculated_grade_items_20150627.feature
grade/tests/behat/grade_calculated_weights.feature
grade/tests/behat/grade_contribution_with_extra_credit.feature
grade/tests/behat/grade_mingrade.feature
grade/tests/behat/grade_minmax.feature
grade/tests/behat/grade_natural_exclude_empty.feature
grade/tests/behat/grade_natural_exclude_empty_20150619.feature
grade/tests/behat/grade_natural_normalisation.feature
grade/tests/behat/grade_natural_normalisation_20150619.feature
grade/tests/behat/grade_scales.feature
grade/tests/behat/grade_scales_aggregation.feature
grade/tests/behat/grade_single_item_scales.feature
grade/tests/behat/grade_view.feature
grade/tests/edittreelib_test.php
group/externallib.php
group/tests/externallib_test.php
install/lang/xct/langconfig.php [new file with mode: 0644]
lang/en/blog.php
lang/en/deprecated.txt
lang/en/grades.php
lang/en/message.php
lang/en/my.php
lang/en/webservice.php
lib/adminlib.php
lib/ajax/service-nologin.php [new file with mode: 0644]
lib/ajax/service.php
lib/amd/build/ajax.min.js
lib/amd/build/event.min.js [new file with mode: 0644]
lib/amd/build/first.min.js
lib/amd/build/str.min.js
lib/amd/build/templates.min.js
lib/amd/src/ajax.js
lib/amd/src/event.js [new file with mode: 0644]
lib/amd/src/first.js
lib/amd/src/str.js
lib/amd/src/templates.js
lib/authlib.php
lib/badgeslib.php
lib/bennu/bennu.class.php
lib/bennu/iCalendar_rfc2445.php
lib/bennu/readme_moodle.txt
lib/blocklib.php
lib/classes/event/calendar_event_created.php
lib/classes/event/calendar_event_deleted.php
lib/classes/event/calendar_event_updated.php
lib/classes/event/message_deleted.php [new file with mode: 0644]
lib/classes/grades_external.php
lib/classes/message/inbound/handler.php
lib/classes/output/external.php
lib/classes/plugin_manager.php
lib/clilib.php
lib/cronlib.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/dml/mssql_native_moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/editor/atto/plugins/equation/db/upgrade.php [new file with mode: 0644]
lib/editor/atto/plugins/equation/db/upgradelib.php [new file with mode: 0644]
lib/editor/atto/plugins/equation/settings.php
lib/editor/atto/plugins/equation/tests/upgradelib_testcase.php [new file with mode: 0644]
lib/editor/atto/plugins/equation/version.php
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js
lib/editor/atto/plugins/equation/yui/src/button/js/button.js
lib/external/externallib.php
lib/externallib.php
lib/form/searchableselector.js
lib/grade/grade_category.php
lib/grade/grade_item.php
lib/javascript-static.js
lib/modinfolib.php
lib/moodlelib.php
lib/myprofilelib.php
lib/navigationlib.php
lib/outputrequirementslib.php
lib/templates/columns-1to1to1.mustache [new file with mode: 0644]
lib/templates/columns-1to2.mustache [new file with mode: 0644]
lib/templates/columns-2to1.mustache [new file with mode: 0644]
lib/templates/columns-autoflow-1to1to1.mustache [new file with mode: 0644]
lib/tests/behat/behat_general.php
lib/tests/blocklib_test.php
lib/tests/externallib_test.php
lib/tests/fixtures/messageinbound/gmail.test [new file with mode: 0644]
lib/tests/fixtures/messageinbound/outlook.test
lib/tests/messageinbound_test.php
lib/tests/navigationlib_test.php
lib/tests/other/completion.manualtest.txt
lib/upgrade.txt
lib/weblib.php
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-debug.js
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop.js
lib/yui/src/dragdrop/js/dragdrop.js
login/signup_form.php
message/lib.php
message/tests/events_test.php
mod/assign/db/upgrade.php
mod/assign/lang/en/assign.php
mod/book/classes/external.php
mod/book/db/services.php
mod/book/tests/externallib_test.php
mod/book/version.php
mod/book/view.php
mod/choice/classes/external.php
mod/choice/db/services.php
mod/choice/tests/behat/publish_results_anonymously.feature
mod/choice/tests/externallib_test.php
mod/choice/version.php
mod/data/backup/moodle2/backup_data_stepslib.php
mod/data/classes/external.php
mod/data/db/install.xml
mod/data/db/upgrade.php
mod/data/edit.php
mod/data/field/latlong/field.class.php
mod/data/lang/en/data.php
mod/data/lib.php
mod/data/mod_form.php
mod/data/tests/behat/manageapproved.feature [new file with mode: 0644]
mod/data/tests/behat/required_entries.feature
mod/data/tests/externallib_test.php
mod/data/tests/lib_test.php
mod/data/version.php
mod/data/view.php
mod/forum/db/services.php
mod/forum/discuss.php
mod/forum/externallib.php
mod/forum/index.php
mod/forum/lib.php
mod/forum/markposts.php
mod/forum/post.php
mod/forum/settracking.php
mod/forum/tests/behat/completion_condition_number_discussions.feature
mod/forum/tests/behat/posts_ordering_blog.feature [new file with mode: 0644]
mod/forum/tests/behat/posts_ordering_general.feature [new file with mode: 0644]
mod/forum/tests/externallib_test.php
mod/forum/tests/lib_test.php
mod/forum/upgrade.txt
mod/forum/view.php
mod/imscp/classes/external.php
mod/imscp/db/services.php
mod/imscp/tests/externallib_test.php
mod/imscp/version.php
mod/lesson/report.php
mod/lesson/tests/behat/completion_condition_end_reached.feature
mod/lesson/tests/behat/completion_condition_time_spent.feature
mod/lesson/tests/behat/lesson_report.feature [new file with mode: 0644]
mod/lti/launch.php
mod/lti/view.php
mod/quiz/tests/behat/completion_condition_attempts_used.feature
mod/quiz/tests/behat/completion_condition_passing_grade.feature
mod/scorm/backup/moodle1/lib.php
mod/scorm/classes/external.php
mod/scorm/db/install.xml
mod/scorm/db/services.php
mod/scorm/db/upgrade.php
mod/scorm/locallib.php
mod/scorm/tests/externallib_test.php
mod/scorm/tests/lib_test.php
mod/scorm/version.php
mod/workshop/form/edit_form.php
mod/workshop/form/rubric/edit_form.php
mod/workshop/form/rubric/lang/en/workshopform_rubric.php
my/indexsys.php
my/lib.php
my/tests/behat/reset_all_pages.feature [new file with mode: 0644]
pix/f/FileTypesIcons-LICENSE.txt
pix/f/Oxygen-LICENSE.txt
question/classes/bank/view.php
question/tests/behat/delete_questions.feature
question/type/ddimageortext/backup/moodle2/backup_qtype_ddimageortext_plugin.class.php [new file with mode: 0644]
question/type/ddimageortext/backup/moodle2/restore_qtype_ddimageortext_plugin.class.php [new file with mode: 0644]
question/type/ddimageortext/db/install.xml [new file with mode: 0644]
question/type/ddimageortext/edit_ddimageortext_form.php [new file with mode: 0644]
question/type/ddimageortext/edit_ddtoimage_form_base.php [new file with mode: 0644]
question/type/ddimageortext/lang/en/qtype_ddimageortext.php [new file with mode: 0644]
question/type/ddimageortext/lib.php [new file with mode: 0644]
question/type/ddimageortext/pix/icon.png [new file with mode: 0644]
question/type/ddimageortext/question.php [new file with mode: 0644]
question/type/ddimageortext/questionbase.php [new file with mode: 0644]
question/type/ddimageortext/questiontype.php [new file with mode: 0644]
question/type/ddimageortext/questiontypebase.php [new file with mode: 0644]
question/type/ddimageortext/renderer.php [new file with mode: 0644]
question/type/ddimageortext/rendererbase.php [new file with mode: 0644]
question/type/ddimageortext/styles.css [new file with mode: 0644]
question/type/ddimageortext/tests/behat/add.feature [new file with mode: 0644]
question/type/ddimageortext/tests/behat/backup_and_restore.feature [new file with mode: 0644]
question/type/ddimageortext/tests/behat/behat_qtype_ddimageortext.php [new file with mode: 0644]
question/type/ddimageortext/tests/behat/edit.feature [new file with mode: 0644]
question/type/ddimageortext/tests/behat/export.feature [new file with mode: 0644]
question/type/ddimageortext/tests/behat/import.feature [new file with mode: 0644]
question/type/ddimageortext/tests/behat/preview.feature [new file with mode: 0644]
question/type/ddimageortext/tests/fixtures/oceanflooranswer.jpg [new file with mode: 0644]
question/type/ddimageortext/tests/fixtures/oceanfloorbase.jpg [new file with mode: 0644]
question/type/ddimageortext/tests/fixtures/testquestion.moodle.xml [new file with mode: 0644]
question/type/ddimageortext/tests/helper.php [new file with mode: 0644]
question/type/ddimageortext/tests/question_test.php [new file with mode: 0644]
question/type/ddimageortext/tests/questiontype_test.php [new file with mode: 0644]
question/type/ddimageortext/tests/walkthrough_test.php [new file with mode: 0644]
question/type/ddimageortext/version.php [new file with mode: 0644]
question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd-debug.js [new file with mode: 0644]
question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd-min.js [new file with mode: 0644]
question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd.js [new file with mode: 0644]
question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-form/moodle-qtype_ddimageortext-form-debug.js [new file with mode: 0644]
question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-form/moodle-qtype_ddimageortext-form-min.js [new file with mode: 0644]
question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-form/moodle-qtype_ddimageortext-form.js [new file with mode: 0644]
question/type/ddimageortext/yui/src/ddimageortext/build.json [new file with mode: 0644]
question/type/ddimageortext/yui/src/ddimageortext/js/ddimageortext.js [new file with mode: 0644]
question/type/ddimageortext/yui/src/ddimageortext/meta/ddimageortext.json [new file with mode: 0644]
question/type/ddimageortext/yui/src/form/build.json [new file with mode: 0644]
question/type/ddimageortext/yui/src/form/js/form.js [new file with mode: 0644]
question/type/ddimageortext/yui/src/form/meta/form.json [new file with mode: 0644]
question/type/ddmarker/backup/moodle2/backup_qtype_ddmarker_plugin.class.php [new file with mode: 0644]
question/type/ddmarker/backup/moodle2/restore_qtype_ddmarker_plugin.class.php [new file with mode: 0644]
question/type/ddmarker/db/install.xml [new file with mode: 0644]
question/type/ddmarker/db/upgrade.php [new file with mode: 0644]
question/type/ddmarker/edit_ddmarker_form.php [new file with mode: 0644]
question/type/ddmarker/lang/en/qtype_ddmarker.php [new file with mode: 0644]
question/type/ddmarker/lib.php [new file with mode: 0644]
question/type/ddmarker/pix/crosshairs.png [new file with mode: 0644]
question/type/ddmarker/pix/crosshairs.xcf [new file with mode: 0644]
question/type/ddmarker/pix/grid.png [new file with mode: 0644]
question/type/ddmarker/pix/grid.xcf [new file with mode: 0644]
question/type/ddmarker/pix/icon.png [new file with mode: 0644]
question/type/ddmarker/question.php [new file with mode: 0644]
question/type/ddmarker/questiontype.php [new file with mode: 0644]
question/type/ddmarker/renderer.php [new file with mode: 0644]
question/type/ddmarker/shapes.php [new file with mode: 0644]
question/type/ddmarker/styles.css [new file with mode: 0644]
question/type/ddmarker/tests/behat/add.feature [new file with mode: 0644]
question/type/ddmarker/tests/behat/backup_and_restore.feature [new file with mode: 0644]
question/type/ddmarker/tests/behat/behat_qtype_ddmarker.php [new file with mode: 0644]
question/type/ddmarker/tests/behat/edit.feature [new file with mode: 0644]
question/type/ddmarker/tests/behat/export.feature [new file with mode: 0644]
question/type/ddmarker/tests/behat/import.feature [new file with mode: 0644]
question/type/ddmarker/tests/behat/preview.feature [new file with mode: 0644]
question/type/ddmarker/tests/fixtures/mkmap.png [new file with mode: 0644]
question/type/ddmarker/tests/fixtures/testquestion.moodle.xml [new file with mode: 0644]
question/type/ddmarker/tests/helper.php [new file with mode: 0644]
question/type/ddmarker/tests/question_test.php [new file with mode: 0644]
question/type/ddmarker/tests/questiontype_test.php [new file with mode: 0644]
question/type/ddmarker/tests/shapes_test.php [new file with mode: 0644]
question/type/ddmarker/tests/walkthrough_test.php [new file with mode: 0644]
question/type/ddmarker/version.php [new file with mode: 0644]
question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd-debug.js [new file with mode: 0644]
question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd-min.js [new file with mode: 0644]
question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd.js [new file with mode: 0644]
question/type/ddmarker/yui/build/moodle-qtype_ddmarker-form/moodle-qtype_ddmarker-form-debug.js [new file with mode: 0644]
question/type/ddmarker/yui/build/moodle-qtype_ddmarker-form/moodle-qtype_ddmarker-form-min.js [new file with mode: 0644]
question/type/ddmarker/yui/build/moodle-qtype_ddmarker-form/moodle-qtype_ddmarker-form.js [new file with mode: 0644]
question/type/ddmarker/yui/src/ddmarker/build.json [new file with mode: 0644]
question/type/ddmarker/yui/src/ddmarker/js/ddmarker.js [new file with mode: 0644]
question/type/ddmarker/yui/src/ddmarker/meta/ddmarker.json [new file with mode: 0644]
question/type/ddmarker/yui/src/form/build.json [new file with mode: 0644]
question/type/ddmarker/yui/src/form/js/form.js [new file with mode: 0644]
question/type/ddmarker/yui/src/form/meta/form.json [new file with mode: 0644]
question/type/ddwtos/backup/moodle2/backup_qtype_ddwtos_plugin.class.php [new file with mode: 0644]
question/type/ddwtos/backup/moodle2/restore_qtype_ddwtos_plugin.class.php [new file with mode: 0644]
question/type/ddwtos/db/install.xml [new file with mode: 0644]
question/type/ddwtos/edit_ddwtos_form.php [new file with mode: 0644]
question/type/ddwtos/lang/en/qtype_ddwtos.php [new file with mode: 0644]
question/type/ddwtos/lib.php [new file with mode: 0644]
question/type/ddwtos/pix/icon.png [new file with mode: 0644]
question/type/ddwtos/question.php [new file with mode: 0644]
question/type/ddwtos/questiontype.php [new file with mode: 0644]
question/type/ddwtos/renderer.php [new file with mode: 0644]
question/type/ddwtos/styles.css [new file with mode: 0644]
question/type/ddwtos/tests/behat/add.feature [new file with mode: 0644]
question/type/ddwtos/tests/behat/backup_and_restore.feature [new file with mode: 0644]
question/type/ddwtos/tests/behat/behat_qtype_ddwtos.php [new file with mode: 0644]
question/type/ddwtos/tests/behat/edit.feature [new file with mode: 0644]
question/type/ddwtos/tests/behat/export.feature [new file with mode: 0644]
question/type/ddwtos/tests/behat/import.feature [new file with mode: 0644]
question/type/ddwtos/tests/behat/preview.feature [new file with mode: 0644]
question/type/ddwtos/tests/fixtures/testquestion.moodle.xml [new file with mode: 0644]
question/type/ddwtos/tests/helper.php [new file with mode: 0644]
question/type/ddwtos/tests/question_test.php [new file with mode: 0644]
question/type/ddwtos/tests/questiontype_test.php [new file with mode: 0644]
question/type/ddwtos/tests/walkthrough_test.php [new file with mode: 0644]
question/type/ddwtos/version.php [new file with mode: 0644]
question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd-debug.js [new file with mode: 0644]
question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd-min.js [new file with mode: 0644]
question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd.js [new file with mode: 0644]
question/type/ddwtos/yui/src/ddwtos/build.json [new file with mode: 0644]
question/type/ddwtos/yui/src/ddwtos/js/ddwtos.js [new file with mode: 0644]
question/type/ddwtos/yui/src/ddwtos/meta/ddwtos.json [new file with mode: 0644]
question/type/gapselect/backup/moodle2/backup_qtype_gapselect_plugin.class.php [new file with mode: 0644]
question/type/gapselect/backup/moodle2/restore_qtype_gapselect_plugin.class.php [new file with mode: 0644]
question/type/gapselect/db/install.xml [new file with mode: 0644]
question/type/gapselect/edit_form_base.php [new file with mode: 0644]
question/type/gapselect/edit_gapselect_form.php [new file with mode: 0644]
question/type/gapselect/lang/en/qtype_gapselect.php [new file with mode: 0644]
question/type/gapselect/lib.php [new file with mode: 0644]
question/type/gapselect/pix/icon.png [new file with mode: 0644]
question/type/gapselect/question.php [new file with mode: 0644]
question/type/gapselect/questionbase.php [new file with mode: 0644]
question/type/gapselect/questiontype.php [new file with mode: 0644]
question/type/gapselect/questiontypebase.php [new file with mode: 0644]
question/type/gapselect/renderer.php [new file with mode: 0644]
question/type/gapselect/rendererbase.php [new file with mode: 0644]
question/type/gapselect/styles.css [new file with mode: 0644]
question/type/gapselect/tests/behat/basic_test.feature [new file with mode: 0644]
question/type/gapselect/tests/behat/behat_qtype_gapselect.php [new file with mode: 0644]
question/type/gapselect/tests/behat/import_test.feature [new file with mode: 0644]
question/type/gapselect/tests/edit_form_test.php [new file with mode: 0644]
question/type/gapselect/tests/fixtures/testquestion.moodle.xml [new file with mode: 0644]
question/type/gapselect/tests/helper.php [new file with mode: 0644]
question/type/gapselect/tests/question_test.php [new file with mode: 0644]
question/type/gapselect/tests/questiontype_test.php [new file with mode: 0644]
question/type/gapselect/tests/walkthrough_test.php [new file with mode: 0644]
question/type/gapselect/version.php [new file with mode: 0644]
question/type/match/question.php
question/type/match/tests/question_test.php
rss/file.php
tag/classes/external.php
tag/tests/behat/edit_tag.feature
theme/base/config.php
theme/base/style/templates.css [new file with mode: 0644]
theme/bootstrapbase/less/moodle.less
theme/bootstrapbase/less/moodle/grade.less
theme/bootstrapbase/less/moodle/templates.less [new file with mode: 0644]
theme/bootstrapbase/style/moodle.css
user/editlib.php
user/externallib.php
user/preferences.php
user/profilesys.php
user/tests/behat/view_preferences_page.feature [new file with mode: 0644]
version.php
webservice/renderer.php

index 8b8a806..ee94a05 100644 (file)
--- a/.jshintrc
+++ b/.jshintrc
@@ -35,7 +35,8 @@
     "plusplus":     false,
     "predef": [
         "M",
-        "define"
+        "define",
+        "require"
     ],
     "proto":        false,
     "regexdash":    false,
index 6237dfa..a3b699e 100644 (file)
@@ -24,6 +24,7 @@
 
 module.exports = function(grunt) {
     var path = require('path'),
+        fs = require('fs'),
         tasks = {},
         cwd = process.env.PWD || process.cwd();
 
@@ -102,22 +103,103 @@ module.exports = function(grunt) {
                 args.push('--lint-stderr');
             }
 
+            var execShifter = function() {
+
+                shifter = exec("node", args, {
+                    cwd: cwd,
+                    stdio: 'inherit',
+                    env: process.env
+                });
+
+                // Tidy up after exec.
+                shifter.on('exit', function (code) {
+                    if (code) {
+                        grunt.fail.fatal('Shifter failed with code: ' + code);
+                    } else {
+                        grunt.log.ok('Shifter build complete.');
+                        done();
+                    }
+                });
+            };
+
             // Actually run shifter.
-            shifter = exec("node", args, {
-                cwd: cwd,
-                stdio: 'inherit',
-                env: process.env
-            });
-
-            // Tidy up after exec.
-            shifter.on('exit', function (code) {
-                if (code) {
-                    grunt.fail.fatal('Shifter failed with code: ' + code);
-                } else {
-                    grunt.log.ok('Shifter build complete.');
-                    done();
-                }
-            });
+            if (!options.recursive) {
+                execShifter();
+            } else {
+                // Check that there are yui modules otherwise shifter ends with exit code 1.
+                var found = false;
+                var hasYuiModules = function(directory, callback) {
+                    fs.readdir(directory, function(err, files) {
+                        if (err) {
+                            return callback(err, null);
+                        }
+
+                        // If we already found a match there is no need to continue scanning.
+                        if (found === true) {
+                            return;
+                        }
+
+                        // We need to track the number of files to know when we return a result.
+                        var pending = files.length;
+
+                        // We first check files, so if there is a match we don't need further
+                        // async calls and we just return a true.
+                        for (var i = 0; i < files.length; i++) {
+                            if (files[i] === 'yui') {
+                                return callback(null, true);
+                            }
+                        }
+
+                        // Iterate through subdirs if there were no matches.
+                        files.forEach(function (file) {
+
+                            var p = path.join(directory, file);
+                            stat = fs.statSync(p);
+                            if (!stat.isDirectory()) {
+                                pending--;
+                            } else {
+
+                                // We defer the pending-1 until we scan the whole dir and subdirs.
+                                hasYuiModules(p, function(err, result) {
+                                    if (err) {
+                                        return callback(err);
+                                    }
+
+                                    if (result === true) {
+                                        // Once we get a true we notify the caller.
+                                        found = true;
+                                        return callback(null, true);
+                                    }
+
+                                    pending--;
+                                    if (pending === 0) {
+                                        // Notify the caller that the whole dir has been scaned and there are no matches.
+                                        return callback(null, false);
+                                    }
+                                });
+                            }
+
+                            // No subdirs here, otherwise the return would be deferred until all subdirs are scanned.
+                            if (pending === 0) {
+                                return callback(null, false);
+                            }
+                        });
+                    });
+                };
+
+                hasYuiModules(cwd, function(err, result) {
+                    if (err) {
+                        grunt.fail.fatal(err.message);
+                    }
+
+                    if (result === true) {
+                        execShifter();
+                    } else {
+                        grunt.log.ok('No YUI modules to build.');
+                        done();
+                    }
+                });
+            }
     };
 
     tasks.startup = function() {
index 131056d..51364b9 100644 (file)
@@ -273,7 +273,8 @@ $interactive = empty($options['non-interactive']);
 
 // set up language
 $lang = clean_param($options['lang'], PARAM_SAFEDIR);
-if (file_exists($CFG->dirroot.'/install/lang/'.$lang)) {
+$languages = get_string_manager()->get_list_of_translations();
+if (array_key_exists($lang, $languages)) {
     $CFG->lang = $lang;
 }
 
@@ -295,23 +296,34 @@ echo get_string('cliinstallheader', 'install', $CFG->target_release)."\n";
 //Fist select language
 if ($interactive) {
     cli_separator();
-    $languages = get_string_manager()->get_list_of_translations();
     // Do not put the langs into columns because it is not compatible with RTL.
-    $langlist = implode("\n", $languages);
     $default = $CFG->lang;
-    cli_heading(get_string('availablelangs', 'install'));
-    echo $langlist."\n";
+    cli_heading(get_string('chooselanguagehead', 'install'));
+    if (array_key_exists($default, $languages)) {
+        echo $default.' - '.$languages[$default]."\n";
+    }
+    if ($default !== 'en') {
+        echo 'en - English (en)'."\n";
+    }
+    echo '? - '.get_string('availablelangs', 'install')."\n";
     $prompt = get_string('clitypevaluedefault', 'admin', $CFG->lang);
     $error = '';
     do {
         echo $error;
         $input = cli_input($prompt, $default);
-        $input = clean_param($input, PARAM_SAFEDIR);
 
-        if (!file_exists($CFG->dirroot.'/install/lang/'.$input)) {
-            $error = get_string('cliincorrectvalueretry', 'admin')."\n";
+        if ($input === '?') {
+            echo implode("\n", $languages)."\n";
+            $error = "\n";
+
         } else {
-            $error = '';
+            $input = clean_param($input, PARAM_SAFEDIR);
+
+            if (!array_key_exists($input, $languages)) {
+                $error = get_string('cliincorrectvalueretry', 'admin')."\n";
+            } else {
+                $error = '';
+            }
         }
     } while ($error !== '');
     $CFG->lang = $input;
diff --git a/admin/tool/behat/tests/behat/basic_actions.feature b/admin/tool/behat/tests/behat/basic_actions.feature
deleted file mode 100644 (file)
index 86bfdaa..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-@tool @tool_behat
-Feature: Page contents assertions
-  In order to write good tests
-  As a tests writer
-  I need to check the page contents
-
-  @javascript
-  Scenario: Basic contents assertions
-    Given I log in as "admin"
-    And I am on site homepage
-    And I expand "Users" node
-    And I follow "Groups"
-    And I press "Create group"
-    And I set the following fields to these values:
-      | Group name | I'm the name |
-      | Group description | I'm the description |
-    And I press "Save changes"
-    When I follow "Overview"
-    And I wait until the page is ready
-    And I wait "2" seconds
-    And I hover "#region-main .generaltable td span" "css_element"
-    Then I should see "I'm the description"
-    And "Grouping" "select" in the "region-main" "region" should be visible
-    And "Group" "select" should be visible
-    And "Activity report" "link" in the "Administration" "block" should not be visible
-    And "Event monitoring rules" "link" should not be visible
-    And I should see "Filter groups by"
-    And I should not see "Filter groupssss by"
-    And I should see "Group members" in the "#region-main table th.c1" "css_element"
-    And I should not see "Group membersssss" in the "#region-main table th.c1" "css_element"
-    And I follow "Groups"
-    And the "#groupeditform #showcreateorphangroupform" "css_element" should be enabled
-    And the "#groupeditform #showeditgroupsettingsform" "css_element" should be disabled
-
-  @javascript
-  Scenario: Locators inside specific DOM nodes using CSS selectors
-    Given the following "courses" exist:
-      | fullname | shortname | category |
-      | Course 1 | C1 | 0 |
-    And I log in as "admin"
-    And I am on site homepage
-    And I follow "Course 1"
-    When I dock "Administration" block
-    Then I should not see "Question bank" in the ".block-region" "css_element"
-    And I click on "//div[@id='dock']/descendant::h2[normalize-space(.)='Administration']" "xpath_element"
-
-  @javascript
-  Scenario: Locators inside specific DOM nodes using XPath
-    Given the following "courses" exist:
-      | fullname | shortname | category |
-      | Course 1 | C1 | 0 |
-    And I log in as "admin"
-    When I dock "Administration" block
-    Then I should not see "Turn editing on" in the ".block-region" "css_element"
index 6a15dd4..9853ea5 100644 (file)
@@ -351,7 +351,7 @@ Feature: Set up contextual data for tests
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I expand "Setup" node
-    And I follow "Categories and items"
+    And I follow "Gradebook setup"
     Then I should see "Test Grade Item 1"
     And I follow "Edit   Test Grade Item 1"
     And I expand all fieldsets
@@ -434,7 +434,7 @@ Feature: Set up contextual data for tests
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I expand "Setup" node
-    And I follow "Categories and items"
+    And I follow "Gradebook setup"
     Then I should see "Test Outcome Grade Item 1"
     And I follow "Edit   Test Outcome Grade Item 1"
     And the field "Outcome" matches value "Grade outcome 1"
index 659ee2e..545e190 100644 (file)
@@ -110,7 +110,8 @@ if ($execute = $options['execute']) {
     $predbqueries = $DB->perf_get_queries();
     $pretime = microtime(true);
 
-    mtrace("Scheduled task: " . $task->get_name());
+    $fullname = $task->get_name() . ' (' . get_class($task) . ')';
+    mtrace('Execute scheduled task: ' . $fullname);
     // NOTE: it would be tricky to move this code to \core\task\manager class,
     //       because we want to do detailed error reporting.
     $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron');
@@ -138,7 +139,7 @@ if ($execute = $options['execute']) {
             mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries");
             mtrace("... used " . (microtime(1) - $pretime) . " seconds");
         }
-        mtrace("Task completed.");
+        mtrace('Scheduled task complete: ' . $fullname);
         \core\task\manager::scheduled_task_complete($task);
         get_mailer('close');
         exit(0);
@@ -148,7 +149,7 @@ if ($execute = $options['execute']) {
         }
         mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries");
         mtrace("... used " . (microtime(true) - $pretime) . " seconds");
-        mtrace("Task failed: " . $e->getMessage());
+        mtrace('Scheduled task failed: ' . $fullname . ',' . $e->getMessage());
         if ($CFG->debugdeveloper) {
             if (!empty($e->debuginfo)) {
                 mtrace("Debug info:");
index a5bf8ab..55ae25d 100644 (file)
Binary files a/admin/tool/templatelibrary/amd/build/display.min.js and b/admin/tool/templatelibrary/amd/build/display.min.js differ
index 062e585..b8458ae 100644 (file)
Binary files a/admin/tool/templatelibrary/amd/build/search.min.js and b/admin/tool/templatelibrary/amd/build/search.min.js differ
index 1585a18..05507ee 100644 (file)
@@ -99,9 +99,7 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
         }
         if (context) {
             templates.render(templateName, context).done(function(html, js) {
-                $('[data-region="displaytemplateexample"]').empty();
-                $('[data-region="displaytemplateexample"]').append(html);
-                templates.runTemplateJS(js);
+                templates.replaceNodeContents($('[data-region="displaytemplateexample"]'), html, js);
             }).fail(notification.exception);
         } else {
             str.get_string('templatehasnoexample', 'tool_templatelibrary').done(function(s) {
@@ -133,7 +131,7 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
                     component: component,
                     template: name
             }
-        }]);
+        }], true, false);
 
         // When returns a new promise that is resolved when all the passed in promises are resolved.
         // The arguments to the done become the values of each resolved promise.
index d4eefe6..2c9d828 100644 (file)
@@ -32,8 +32,8 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
      */
     var reloadListTemplate = function(templateList) {
         templates.render('tool_templatelibrary/search_results', { templates: templateList })
-            .done(function (result) {
-                $('[data-region="searchresults"]').replaceWith(result);
+            .done(function (result, js) {
+                templates.replaceNode($('[data-region="searchresults"]'), result, js);
             }).fail(notification.exception);
     };
 
@@ -53,7 +53,7 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
               args: { component: componentStr, search: searchStr },
               done: reloadListTemplate,
               fail: notification.exception }
-        ]);
+        ], true, false);
     };
 
     var throttle = null;
index 100172b..be583a2 100644 (file)
@@ -63,14 +63,6 @@ class external extends external_api {
         return new external_function_parameters($params);
     }
 
-    /**
-     * Expose to AJAX
-     * @return boolean
-     */
-    public static function list_templates_is_allowed_from_ajax() {
-        return true;
-    }
-
     /**
      * Loads the list of templates.
      * @param string $component Limit the search to a component.
@@ -108,16 +100,6 @@ class external extends external_api {
             );
     }
 
-    /**
-     * Can this function be called directly from ajax?
-     *
-     * @return boolean
-     * @since Moodle 2.9
-     */
-    public static function load_canonical_template_is_allowed_from_ajax() {
-        return true;
-    }
-
     /**
      * Return a mustache template.
      * Note - this function differs from the function core_output_load_template
index c1a9479..9dda647 100644 (file)
@@ -32,12 +32,16 @@ $functions = array(
         'description' => 'List/search templates by component.',
         'type'        => 'read',
         'capabilities'=> '',
+        'ajax'        => true,
+        'loginrequired' => false,
     ),
     'tool_templatelibrary_load_canonical_template' => array(
         'classname'   => 'tool_templatelibrary\external',
         'methodname'  => 'load_canonical_template',
         'description' => 'Load a canonical template by name (not the theme overidden one).',
-        'type'        => 'read'
+        'type'        => 'read',
+        'ajax'        => true,
+        'loginrequired' => false,
     ),
 
 );
index b05838c..2dffa22 100644 (file)
             echo $OUTPUT->header();
             $fullname = fullname($user, true);
             echo $OUTPUT->heading(get_string('deleteuser', 'admin'));
+
             $optionsyes = array('delete'=>$delete, 'confirm'=>md5($delete), 'sesskey'=>sesskey());
-            echo $OUTPUT->confirm(get_string('deletecheckfull', '', "'$fullname'"), new moodle_url($returnurl, $optionsyes), $returnurl);
+            $deleteurl = new moodle_url($returnurl, $optionsyes);
+            $deletebutton = new single_button($deleteurl, get_string('delete'), 'post');
+
+            echo $OUTPUT->confirm(get_string('deletecheckfull', '', "'$fullname'"), $deletebutton, $returnurl);
             echo $OUTPUT->footer();
             die;
         } else if (data_submitted() and !$user->deleted) {
index 3f037d7..0a0827d 100644 (file)
@@ -235,12 +235,11 @@ class auth_plugin_email extends auth_plugin_base {
     }
 
     /**
-     * Returns whether or not the captcha element is enabled, and the admin settings fulfil its requirements.
+     * Returns whether or not the captcha element is enabled.
      * @return bool
      */
     function is_captcha_enabled() {
-        global $CFG;
-        return isset($CFG->recaptchapublickey) && isset($CFG->recaptchaprivatekey) && get_config("auth/{$this->authtype}", 'recaptcha');
+        return get_config("auth/{$this->authtype}", 'recaptcha');
     }
 
 }
index ddfe96f..a4cebd8 100644 (file)
@@ -210,7 +210,8 @@ class auth_plugin_shibboleth extends auth_plugin_base {
             }
 
             // Overwrite redirect in order to send user to Shibboleth logout page and let him return back
-            $redirect = $this->config->logout_handler.'?return='.urlencode($temp_redirect);
+            $redirecturl = new moodle_url($this->config->logout_handler, array('return' => $temp_redirect));
+            $redirect = $redirecturl->out();
         }
     }
 
index d867a24..0024b81 100644 (file)
@@ -1,6 +1,10 @@
 This files describes API changes in /auth/* - plugins,
 information provided here is intended especially for developers.
 
+=== 3.0 ===
+
+* login_signup_form::signup_captcha_enabled() now calls is_captcha_enabled() from the current auth plugin instead of from auth_email
+
 === 2.9 ===
 
 * Do not update user->firstaccess from any auth plugin, the complete_user_login() does it automatically.
index 64d0e79..8a14e91 100644 (file)
@@ -27,8 +27,18 @@ class cc_assesment_question_truefalse extends cc_assesment_question_proc_base {
     public function __construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir) {
         parent::__construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
         $this->qtype = cc_qti_profiletype::true_false;
-        $this->correct_answer_node_id = $this->questions->nodeValue(
-            'plugin_qtype_truefalse_question/truefalse/trueanswer', $this->question_node);
+
+        // Determine the correct answer by finding out which answer has the non zero fraction...
+        // This is because a true / false question type can have 'false' as the correct answer.
+        $answers = $this->questions->nodeList('plugin_qtype_truefalse_question/answers/answer', $this->question_node);
+        foreach ($answers as $answer) {
+            $fraction = $this->questions->nodeValue('fraction', $answer);
+
+            if ($fraction != 0) {
+                $this->correct_answer_node_id = (int)$this->questions->nodeValue('@id', $answer);
+            }
+        }
+
         $maximum_quiz_grade = (int)$this->quiz->nodeValue('/activity/quiz/grade');
         $this->total_grade_value = ($maximum_quiz_grade + 1).'.0000000';
     }
index 56c42de..465422a 100644 (file)
@@ -885,27 +885,30 @@ class cc_quiz extends entities {
 
         $sheet_question_categories_question = cc2moodle::loadsheet(SHEET_COURSE_QUESTION_CATEGORIES_QUESTION_CATEGORY_QUESTION_TRUE_FALSE);
 
-        $max_score = 0;
-        $true_answer_id = 0;
-        $false_answer_id = 0;
+        $trueanswer  = null;
+        $falseanswer = null;
 
         if (!empty($question['answers'])) {
 
+            // Identify the true and false answers.
             foreach ($question['answers'] as $answer) {
-                if ($answer['score'] > $max_score) {
-                    $max_score = $answer['score'];
-                    $true_answer_id = $answer['id'];
+                if ($answer['identifier'] == 'true') {
+                    $trueanswer = $answer;
+                } else if ($answer['identifier'] == 'false') {
+                    $falseanswer = $answer;
+                } else {
+                    // Should not happen, but just in case.
+                    throw new coding_exception("Unknown answer identifier detected" .
+                            " in true/false quiz question with id {$question['id']}.");
                 }
 
                 $node_course_question_categories_question_answer .= $this->create_node_course_question_categories_question_category_question_answer($answer);
             }
 
-            foreach ($question['answers'] as $answer) {
-
-                if ($answer['id'] != $true_answer_id) {
-                    $max_score = $answer['score'];
-                    $false_answer_id = $answer['id'];
-                }
+            // Make sure the true and false answer was found.
+            if (is_null($trueanswer) || is_null($falseanswer)) {
+                throw new coding_exception("Unable to correctly identify the " .
+                        "true and false answers in the question with id {$question['id']}.");
             }
         }
 
@@ -914,8 +917,8 @@ class cc_quiz extends entities {
                            '[#false_answer_id#]');
 
         $replace_values = array($node_course_question_categories_question_answer,
-                                $true_answer_id,
-                                $false_answer_id);
+                                $trueanswer['id'],
+                                $falseanswer['id']);
 
         $node_question_categories_question = str_replace($find_tags, $replace_values, $sheet_question_categories_question);
 
index 5410caa..fb34758 100644 (file)
@@ -908,27 +908,30 @@ class cc11_quiz extends entities11 {
 
         $sheet_question_categories_question = cc112moodle::loadsheet(SHEET_COURSE_QUESTION_CATEGORIES_QUESTION_CATEGORY_QUESTION_TRUE_FALSE);
 
-        $max_score = 0;
-        $true_answer_id = 0;
-        $false_answer_id = 0;
+        $trueanswer  = null;
+        $falseanswer = null;
 
         if (!empty($question['answers'])) {
 
+            // Identify the true and false answers.
             foreach ($question['answers'] as $answer) {
-                if ($answer['score'] > $max_score) {
-                    $max_score = $answer['score'];
-                    $true_answer_id = $answer['id'];
+                if ($answer['identifier'] == 'true') {
+                    $trueanswer = $answer;
+                } else if ($answer['identifier'] == 'false') {
+                    $falseanswer = $answer;
+                } else {
+                    // Should not happen, but just in case.
+                    throw new coding_exception("Unknown answer identifier detected " .
+                            "in true/false quiz question with id {$question['id']}.");
                 }
 
                 $node_course_question_categories_question_answer .= $this->create_node_course_question_categories_question_category_question_answer($answer);
             }
 
-            foreach ($question['answers'] as $answer) {
-
-                if ($answer['id'] != $true_answer_id) {
-                    $max_score = $answer['score'];
-                    $false_answer_id = $answer['id'];
-                }
+            // Make sure the true and false answer was found.
+            if (is_null($trueanswer) || is_null($falseanswer)) {
+                throw new coding_exception("Unable to correctly identify the " .
+                        "true and false answers in the question with id {$question['id']}.");
             }
         }
 
@@ -937,8 +940,8 @@ class cc11_quiz extends entities11 {
                            '[#false_answer_id#]');
 
         $replace_values = array($node_course_question_categories_question_answer,
-                                $true_answer_id,
-                                $false_answer_id);
+                                $trueanswer['id'],
+                                $falseanswer['id']);
 
         $node_question_categories_question = str_replace($find_tags, $replace_values, $sheet_question_categories_question);
 
index 1941738..ad9d427 100644 (file)
@@ -45,7 +45,7 @@ class edit_backpack_form extends moodleform {
         $mform->addElement('html', html_writer::tag('span', '', array('class' => 'notconnected', 'id' => 'connection-error')));
         $mform->addElement('header', 'backpackheader', get_string('backpackconnection', 'badges'));
         $mform->addHelpButton('backpackheader', 'backpackconnection', 'badges');
-        $mform->addElement('static', 'url', get_string('url'), 'http://' . BADGE_BACKPACKURL);
+        $mform->addElement('static', 'url', get_string('url'), BADGE_BACKPACKURL);
         $status = html_writer::tag('span', get_string('notconnected', 'badges'),
             array('class' => 'notconnected', 'id' => 'connection-status'));
         $mform->addElement('static', 'status', get_string('status'), $status);
@@ -67,7 +67,7 @@ class edit_backpack_form extends moodleform {
         $mform->addElement('hidden', 'userid', $USER->id);
         $mform->setType('userid', PARAM_INT);
 
-        $mform->addElement('hidden', 'backpackurl', 'http://' . BADGE_BACKPACKURL);
+        $mform->addElement('hidden', 'backpackurl', BADGE_BACKPACKURL);
         $mform->setType('backpackurl', PARAM_URL);
 
     }
@@ -118,7 +118,7 @@ class edit_collections_form extends moodleform {
 
         $mform->addElement('header', 'backpackheader', get_string('backpackconnection', 'badges'));
         $mform->addHelpButton('backpackheader', 'backpackconnection', 'badges');
-        $mform->addElement('static', 'url', get_string('url'), 'http://' . BADGE_BACKPACKURL);
+        $mform->addElement('static', 'url', get_string('url'), BADGE_BACKPACKURL);
 
         $status = html_writer::tag('span', get_string('connected', 'badges'), array('class' => 'connected'));
         $mform->addElement('static', 'status', get_string('status'), $status);
index 382749a..9365afe 100644 (file)
@@ -87,7 +87,7 @@ if (!isset($data->status) || $data->status != 'okay') {
 
 // Make sure email matches a backpack.
 $check = new stdClass();
-$check->backpackurl = 'http://' . BADGE_BACKPACKURL;
+$check->backpackurl = BADGE_BACKPACKURL;
 $check->email = $data->email;
 
 $bp = new OpenBadgesBackpackHandler($check);
@@ -106,7 +106,7 @@ if (isset($request->status) && $request->status == 'missing') {
 $obj = new stdClass();
 $obj->userid = $USER->id;
 $obj->email = $data->email;
-$obj->backpackurl = 'http://' . BADGE_BACKPACKURL;
+$obj->backpackurl = BADGE_BACKPACKURL;
 $obj->backpackuid = $backpackuid;
 $obj->autosync = 0;
 $obj->password = '';
index 39ea1cc..ae319eb 100644 (file)
@@ -1,6 +1,11 @@
 .block_activity_results {text-align: center;}
 .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: right;width:10%;}
+.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 {margin: 1em 0px 0px 0px;border-bottom-width: 1px;border-bottom-style: solid;font-weight: bold;}
+.block_activity_results table.grades caption {font-weight: bold; font-size: 18px;}
+
+.dir-rtl .block_activity_results table.grades {text-align: right;}
+.dir-rtl .block_activity_results table.grades .number{text-align: right;}
+.dir-rtl .block_activity_results table.grades .name{text-align: right;}
index 24612dd..d82b244 100644 (file)
@@ -134,6 +134,9 @@ if ($action === 'delete') {
         $PAGE->set_heading($SITE->fullname);
         echo $OUTPUT->header();
 
+        // Output edit mode title.
+        echo $OUTPUT->heading($strblogs . ': ' . get_string('deleteentry', 'blog'), 2);
+
         // Output the entry.
         $entry->prepare_render();
         echo $output->render($entry);
@@ -146,10 +149,12 @@ if ($action === 'delete') {
         die;
     }
 } else if ($action == 'add') {
-    $PAGE->set_title("$SITE->shortname: $strblogs: " . get_string('addnewentry', 'blog'));
+    $editmodetitle = $strblogs . ': ' . get_string('addnewentry', 'blog');
+    $PAGE->set_title("$SITE->shortname: $editmodetitle");
     $PAGE->set_heading(fullname($USER));
 } else if ($action == 'edit') {
-    $PAGE->set_title("$SITE->shortname: $strblogs: " . get_string('editentry', 'blog'));
+    $editmodetitle = $strblogs . ': ' . get_string('editentry', 'blog');
+    $PAGE->set_title("$SITE->shortname: $editmodetitle");
     $PAGE->set_heading(fullname($USER));
 }
 
@@ -270,6 +275,10 @@ $entry->modid = $modid;
 $entry->courseid = $courseid;
 
 echo $OUTPUT->header();
+// Output title for editing mode.
+if (isset($editmodetitle)) {
+    echo $OUTPUT->heading($editmodetitle, 2);
+}
 $blogeditform->display();
 echo $OUTPUT->footer();
 
index 9f2700e..b999205 100644 (file)
@@ -189,10 +189,14 @@ foreach($events as $event) {
     $ev->add_property('class', 'PUBLIC'); // PUBLIC / PRIVATE / CONFIDENTIAL
     $ev->add_property('last-modified', Bennu::timestamp_to_datetime($event->timemodified));
     $ev->add_property('dtstamp', Bennu::timestamp_to_datetime()); // now
-    $ev->add_property('dtstart', Bennu::timestamp_to_datetime($event->timestart)); // when event starts
     if ($event->timeduration > 0) {
         //dtend is better than duration, because it works in Microsoft Outlook and works better in Korganizer
+        $ev->add_property('dtstart', Bennu::timestamp_to_datetime($event->timestart)); // when event starts.
         $ev->add_property('dtend', Bennu::timestamp_to_datetime($event->timestart + $event->timeduration));
+    } else {
+        // When no duration is present, ie an all day event, VALUE should be date instead of time and dtend = dtstart + 1 day.
+        $ev->add_property('dtstart', Bennu::timestamp_to_date($event->timestart), array('value' => 'DATE')); // All day event.
+        $ev->add_property('dtend', Bennu::timestamp_to_date($event->timestart + DAYSECS), array('value' => 'DATE')); // All day event.
     }
     if ($event->courseid != 0) {
         $coursecontext = context_course::instance($event->courseid);
index dc57b66..dcbad6b 100644 (file)
@@ -377,4 +377,84 @@ class core_completion_external extends external_api {
         );
     }
 
+    /**
+     * Describes the parameters for mark_course_self_completed.
+     *
+     * @return external_external_function_parameters
+     * @since Moodle 3.0
+     */
+    public static function mark_course_self_completed_parameters() {
+        return new external_function_parameters (
+            array(
+                'courseid' => new external_value(PARAM_INT, 'Course ID')
+            )
+        );
+    }
+
+    /**
+     * Update the course completion status for the current user (if course self-completion is enabled).
+     *
+     * @param  int $courseid    Course id
+     * @return array            Result and possible warnings
+     * @since Moodle 3.0
+     * @throws moodle_exception
+     */
+    public static function mark_course_self_completed($courseid) {
+        global $USER;
+
+        $warnings = array();
+        $params = self::validate_parameters(self::mark_course_self_completed_parameters(),
+                                            array('courseid' => $courseid));
+
+        $course = get_course($params['courseid']);
+        $context = context_course::instance($course->id);
+        self::validate_context($context);
+
+        // Set up completion object and check it is enabled.
+        $completion = new completion_info($course);
+        if (!$completion->is_enabled()) {
+            throw new moodle_exception('completionnotenabled', 'completion');
+        }
+
+        if (!$completion->is_tracked_user($USER->id)) {
+            throw new moodle_exception('nottracked', 'completion');
+        }
+
+        $completion = $completion->get_completion($USER->id, COMPLETION_CRITERIA_TYPE_SELF);
+
+        // Self completion criteria not enabled.
+        if (!$completion) {
+            throw new moodle_exception('noselfcompletioncriteria', 'completion');
+        }
+
+        // Check if the user has already marked himself as complete.
+        if ($completion->is_complete()) {
+            throw new moodle_exception('useralreadymarkedcomplete', 'completion');
+        }
+
+        // Mark the course complete.
+        $completion->mark_complete();
+
+        $result = array();
+        $result['status'] = true;
+        $result['warnings'] = $warnings;
+        return $result;
+    }
+
+    /**
+     * Describes the mark_course_self_completed return value.
+     *
+     * @return external_single_structure
+     * @since Moodle 3.0
+     */
+    public static function mark_course_self_completed_returns() {
+
+        return new external_single_structure(
+            array(
+                'status'    => new external_value(PARAM_BOOL, 'status, true if success'),
+                'warnings'  => new external_warnings(),
+            )
+        );
+    }
+
 }
index 6790dbf..3762290 100644 (file)
@@ -122,4 +122,40 @@ class behat_completion extends behat_base {
             new Given('I press "'.get_string('savechangesanddisplay').'"')
         );
     }
+
+    /**
+     * Checks if the activity with specified name is maked as complete.
+     *
+     * @Given /^the "(?P<activityname_string>(?:[^"]|\\")*)" "(?P<activitytype_string>(?:[^"]|\\")*)" activity with "(manual|auto)" completion should be marked as complete$/
+     * @return array
+     */
+    public function activity_marked_as_complete($activityname, $activitytype, $completiontype) {
+        if ($completiontype == "manual") {
+            $imgalttext = get_string("completion-alt-manual-y", 'core_completion', $activityname);
+        } else {
+            $imgalttext = get_string("completion-alt-auto-y", 'core_completion', $activityname);
+        }
+        $csselementforactivitytype = "li.modtype_".strtolower($activitytype);
+
+        return new Given('"//img[contains(@alt, \''.$imgalttext.'\')]" "xpath_element" ' .
+            'should exist in the "'.$csselementforactivitytype.'" "css_element"');
+    }
+
+    /**
+     * Checks if the activity with specified name is maked as complete.
+     *
+     * @Given /^the "(?P<activityname_string>(?:[^"]|\\")*)" "(?P<activitytype_string>(?:[^"]|\\")*)" activity with "(manual|auto)" completion should be marked as not complete$/
+     * @return array
+     */
+    public function activity_marked_as_not_complete($activityname, $activitytype, $completiontype) {
+        if ($completiontype == "manual") {
+            $imgalttext = get_string("completion-alt-manual-n", 'core_completion', $activityname);
+        } else {
+            $imgalttext = get_string("completion-alt-auto-n", 'core_completion', $activityname);
+        }
+        $csselementforactivitytype = "li.modtype_".strtolower($activitytype);
+
+        return new Given('"//img[contains(@alt, \''.$imgalttext.'\')]" "xpath_element" ' .
+            'should exist in the "'.$csselementforactivitytype.'" "css_element"');
+    }
 }
index c16b29c..ebcedc0 100644 (file)
@@ -317,4 +317,71 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
 
     }
 
+    /**
+     * Test mark_course_self_completed
+     */
+    public function test_mark_course_self_completed() {
+        global $DB, $CFG;
+        require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
+
+        $this->resetAfterTest(true);
+
+        $CFG->enablecompletion = true;
+        $student = $this->getDataGenerator()->create_user();
+        $teacher = $this->getDataGenerator()->create_user();
+
+        $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
+
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
+
+        // Set completion rules.
+        $completion = new completion_info($course);
+
+        $criteriadata = new stdClass();
+        $criteriadata->id = $course->id;
+        $criteriadata->criteria_activity = array();
+
+        // Self completion.
+        $criteriadata->criteria_self = COMPLETION_CRITERIA_TYPE_SELF;
+        $class = 'completion_criteria_self';
+        $criterion = new $class();
+        $criterion->update_config($criteriadata);
+
+        // Handle overall aggregation.
+        $aggdata = array(
+            'course'        => $course->id,
+            'criteriatype'  => null
+        );
+        $aggregation = new completion_aggregation($aggdata);
+        $aggregation->setMethod(COMPLETION_AGGREGATION_ALL);
+        $aggregation->save();
+
+        $this->setUser($student);
+
+        $result = core_completion_external::mark_course_self_completed($course->id);
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $result = external_api::clean_returnvalue(
+            core_completion_external::mark_course_self_completed_returns(), $result);
+
+        // We expect a valid result.
+        $this->assertEquals(true, $result['status']);
+
+        $result = core_completion_external::get_course_completion_status($course->id, $student->id);
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $result = external_api::clean_returnvalue(
+            core_completion_external::get_course_completion_status_returns(), $result);
+
+        // Course must be completed.
+        $this->assertEquals(COMPLETION_COMPLETE, $result['completionstatus']['completions'][0]['complete']);
+
+        try {
+            $result = core_completion_external::mark_course_self_completed($course->id);
+            $this->fail('Exception expected due course already self completed.');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('useralreadymarkedcomplete', $e->errorcode);
+        }
+
+    }
+
 }
index 31d4817..1ddc12d 100644 (file)
@@ -76,11 +76,12 @@ $strdeletecoursecheck = get_string("deletecoursecheck");
 $message = "{$strdeletecoursecheck}<br /><br />{$coursefullname} ({$courseshortname})";
 
 $continueurl = new moodle_url('/course/delete.php', array('id' => $course->id, 'delete' => md5($course->timemodified)));
+$continuebutton = new single_button($continueurl, get_string('delete'), 'post');
 
 $PAGE->navbar->add($strdeletecheck);
 $PAGE->set_title("$SITE->shortname: $strdeletecheck");
 $PAGE->set_heading($SITE->fullname);
 echo $OUTPUT->header();
-echo $OUTPUT->confirm($message, $continueurl, $categoryurl);
+echo $OUTPUT->confirm($message, $continuebutton, $categoryurl);
 echo $OUTPUT->footer();
-exit;
\ No newline at end of file
+exit;
index 76b49eb..ea4724a 100644 (file)
@@ -66,7 +66,7 @@ if ($deletesection) {
             echo $OUTPUT->box_start('noticebox');
             $optionsyes = array('id' => $id, 'confirm' => 1, 'delete' => 1, 'sesskey' => sesskey());
             $deleteurl = new moodle_url('/course/editsection.php', $optionsyes);
-            $formcontinue = new single_button($deleteurl, get_string('continue'));
+            $formcontinue = new single_button($deleteurl, get_string('delete'));
             $formcancel = new single_button($cancelurl, get_string('cancel'), 'get');
             echo $OUTPUT->confirm(get_string('confirmdeletesection', '',
                 get_section_name($course, $sectioninfo)), $formcontinue, $formcancel);
index 18633bd..e36b268 100644 (file)
@@ -2122,6 +2122,302 @@ class core_course_external extends external_api {
         );
     }
 
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.0
+     */
+    public static function search_courses_parameters() {
+        return new external_function_parameters(
+            array(
+                'criterianame'  => new external_value(PARAM_ALPHA, 'criteria name
+                                                        (search, modulelist (only admins), blocklist (only admins), tagid)'),
+                'criteriavalue' => new external_value(PARAM_RAW, 'criteria value'),
+                'page'          => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0),
+                'perpage'       => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0)
+            )
+        );
+    }
+
+    /**
+     * Search courses following the specified criteria.
+     *
+     * @param string $criterianame  Criteria name (search, modulelist (only admins), blocklist (only admins), tagid)
+     * @param string $criteriavalue Criteria value
+     * @param int $page             Page number (for pagination)
+     * @param int $perpage          Items per page
+     * @return array of course objects and warnings
+     * @since Moodle 3.0
+     * @throws moodle_exception
+     */
+    public static function search_courses($criterianame, $criteriavalue, $page=0, $perpage=0) {
+        global $CFG;
+        require_once($CFG->libdir . '/coursecatlib.php');
+
+        $warnings = array();
+
+        $parameters = array(
+            'criterianame'  => $criterianame,
+            'criteriavalue' => $criteriavalue,
+            'page'          => $page,
+            'perpage'       => $perpage
+        );
+        $params = self::validate_parameters(self::search_courses_parameters(), $parameters);
+
+        $allowedcriterianames = array('search', 'modulelist', 'blocklist', 'tagid');
+        if (!in_array($params['criterianame'], $allowedcriterianames)) {
+            throw new invalid_parameter_exception('Invalid value for criterianame parameter (value: '.$params['criterianame'].'),' .
+                'allowed values are: '.implode(',', $allowedcriterianames));
+        }
+
+        if ($params['criterianame'] == 'modulelist' or $params['criterianame'] == 'blocklist') {
+            require_capability('moodle/site:config', context_system::instance());
+        }
+
+        $paramtype = array(
+            'search' => PARAM_RAW,
+            'modulelist' => PARAM_PLUGIN,
+            'blocklist' => PARAM_INT,
+            'tagid' => PARAM_INT
+        );
+        $params['criteriavalue'] = clean_param($params['criteriavalue'], $paramtype[$params['criterianame']]);
+
+        // Prepare the search API options.
+        $searchcriteria = array();
+        $searchcriteria[$params['criterianame']] = $params['criteriavalue'];
+
+        $options = array();
+        if ($params['perpage'] != 0) {
+            $offset = $params['page'] * $params['perpage'];
+            $options = array('offset' => $offset, 'limit' => $params['perpage']);
+        }
+
+        // Search the courses.
+        $courses = coursecat::search_courses($searchcriteria, $options);
+        $totalcount = coursecat::search_courses_count($searchcriteria);
+
+        $finalcourses = array();
+        $categoriescache = array();
+
+        foreach ($courses as $course) {
+
+            $coursecontext = context_course::instance($course->id);
+
+            // Category information.
+            if (!isset($categoriescache[$course->category])) {
+                $categoriescache[$course->category] = coursecat::get($course->category);
+            }
+            $category = $categoriescache[$course->category];
+
+            // Retrieve course overfiew used files.
+            $files = array();
+            foreach ($course->get_course_overviewfiles() as $file) {
+                $fileurl = moodle_url::make_webservice_pluginfile_url($file->get_contextid(), $file->get_component(),
+                                                                        $file->get_filearea(), null, $file->get_filepath(),
+                                                                        $file->get_filename())->out(false);
+                $files[] = array(
+                    'filename' => $file->get_filename(),
+                    'fileurl' => $fileurl,
+                    'filesize' => $file->get_filesize()
+                );
+            }
+
+            // Retrieve the course contacts,
+            // we need here the users fullname since if we are not enrolled can be difficult to obtain them via other Web Services.
+            $coursecontacts = array();
+            foreach ($course->get_course_contacts() as $contact) {
+                 $coursecontacts[] = array(
+                    'id' => $contact['user']->id,
+                    'fullname' => $contact['username']
+                );
+            }
+
+            // Allowed enrolment methods (maybe we can self-enrol).
+            $enroltypes = array();
+            $instances = enrol_get_instances($course->id, true);
+            foreach ($instances as $instance) {
+                $enroltypes[] = $instance->enrol;
+            }
+
+            // Format summary.
+            list($summary, $summaryformat) =
+                external_format_text($course->summary, $course->summaryformat, $coursecontext->id, 'course', 'summary', null);
+
+            $coursereturns = array();
+            $coursereturns['id']                = $course->id;
+            $coursereturns['fullname']          = $course->get_formatted_fullname();
+            $coursereturns['shortname']         = $course->get_formatted_shortname();
+            $coursereturns['categoryid']        = $course->category;
+            $coursereturns['categoryname']      = $category->name;
+            $coursereturns['summary']           = $summary;
+            $coursereturns['summaryformat']     = $summaryformat;
+            $coursereturns['overviewfiles']     = $files;
+            $coursereturns['contacts']          = $coursecontacts;
+            $coursereturns['enrollmentmethods'] = $enroltypes;
+            $finalcourses[] = $coursereturns;
+        }
+
+        return array(
+            'total' => $totalcount,
+            'courses' => $finalcourses,
+            'warnings' => $warnings
+        );
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     * @since Moodle 3.0
+     */
+    public static function search_courses_returns() {
+
+        return new external_single_structure(
+            array(
+                'total' => new external_value(PARAM_INT, 'total course count'),
+                'courses' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'id' => new external_value(PARAM_INT, 'course id'),
+                            'fullname' => new external_value(PARAM_TEXT, 'course full name'),
+                            'shortname' => new external_value(PARAM_TEXT, 'course short name'),
+                            'categoryid' => new external_value(PARAM_INT, 'category id'),
+                            'categoryname' => new external_value(PARAM_TEXT, 'category name'),
+                            'summary' => new external_value(PARAM_RAW, 'summary'),
+                            'summaryformat' => new external_format_value('summary'),
+                            'overviewfiles' => new external_multiple_structure(
+                                new external_single_structure(
+                                    array(
+                                        'filename' => new external_value(PARAM_FILE, 'overview file name'),
+                                        'fileurl'  => new external_value(PARAM_URL, 'overview file url'),
+                                        'filesize'  => new external_value(PARAM_INT, 'overview file size'),
+                                    )
+                                ),
+                                'additional overview files attached to this course'
+                            ),
+                            'contacts' => new external_multiple_structure(
+                                new external_single_structure(
+                                    array(
+                                        'id' => new external_value(PARAM_INT, 'contact user id'),
+                                        'fullname'  => new external_value(PARAM_NOTAGS, 'contact user fullname'),
+                                    )
+                                ),
+                                'contact users'
+                            ),
+                            'enrollmentmethods' => new external_multiple_structure(
+                                new external_value(PARAM_PLUGIN, 'enrollment method'),
+                                'enrollment methods list'
+                            ),
+                        )
+                    ), 'course'
+                ),
+                'warnings' => new external_warnings()
+            )
+        );
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.0
+     */
+    public static function get_course_module_parameters() {
+        return new external_function_parameters(
+            array(
+                'cmid' => new external_value(PARAM_INT, 'The course module id')
+            )
+        );
+    }
+
+    /**
+     * Return information about a course module.
+     *
+     * @param int $cmid the course module id
+     * @return array of warnings and the course module
+     * @since Moodle 3.0
+     * @throws moodle_exception
+     */
+    public static function get_course_module($cmid) {
+
+        $params = self::validate_parameters(self::get_course_module_parameters(),
+                                            array(
+                                                'cmid' => $cmid,
+                                            ));
+
+        $warnings = array();
+
+        $cm = get_coursemodule_from_id(null, $params['cmid'], 0, true, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+        self::validate_context($context);
+
+        // If the user has permissions to manage the activity, return all the information.
+        if (has_capability('moodle/course:manageactivities', $context)) {
+            $info = $cm;
+        } else {
+            // Return information is safe to show to any user.
+            $info = new stdClass();
+            $info->id = $cm->id;
+            $info->course = $cm->course;
+            $info->module = $cm->module;
+            $info->modname = $cm->modname;
+            $info->instance = $cm->instance;
+            $info->section = $cm->section;
+            $info->sectionnum = $cm->sectionnum;
+            $info->groupmode = $cm->groupmode;
+            $info->groupingid = $cm->groupingid;
+            $info->completion = $cm->completion;
+        }
+        // Format name.
+        $info->name = format_string($cm->name, true, array('context' => $context));
+
+        $result = array();
+        $result['cm'] = $info;
+        $result['warnings'] = $warnings;
+        return $result;
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     * @since Moodle 3.0
+     */
+    public static function get_course_module_returns() {
+        return new external_single_structure(
+            array(
+                'cm' => new external_single_structure(
+                    array(
+                        'id' => new external_value(PARAM_INT, 'The course module id'),
+                        'course' => new external_value(PARAM_INT, 'The course id'),
+                        'module' => new external_value(PARAM_INT, 'The module type id'),
+                        'name' => new external_value(PARAM_TEXT, 'The activity name'),
+                        'modname' => new external_value(PARAM_COMPONENT, 'The module component name (forum, assign, etc..)'),
+                        'instance' => new external_value(PARAM_INT, 'The activity instance id'),
+                        'section' => new external_value(PARAM_INT, 'The module section id'),
+                        'sectionnum' => new external_value(PARAM_INT, 'The module section number'),
+                        'groupmode' => new external_value(PARAM_INT, 'Group mode'),
+                        'groupingid' => new external_value(PARAM_INT, 'Grouping id'),
+                        'completion' => new external_value(PARAM_INT, 'If completion is enabled'),
+                        'idnumber' => new external_value(PARAM_RAW, 'Module id number', VALUE_OPTIONAL),
+                        'added' => new external_value(PARAM_INT, 'Time added', VALUE_OPTIONAL),
+                        'score' => new external_value(PARAM_INT, 'Score', VALUE_OPTIONAL),
+                        'indent' => new external_value(PARAM_INT, 'Indentation', VALUE_OPTIONAL),
+                        'visible' => new external_value(PARAM_INT, 'If visible', VALUE_OPTIONAL),
+                        'visibleold' => new external_value(PARAM_INT, 'Visible old', VALUE_OPTIONAL),
+                        'completiongradeitemnumber' => new external_value(PARAM_INT, 'Completion grade item', VALUE_OPTIONAL),
+                        'completionview' => new external_value(PARAM_INT, 'Completion view setting', VALUE_OPTIONAL),
+                        'completionexpected' => new external_value(PARAM_INT, 'Completion time expected', VALUE_OPTIONAL),
+                        'showdescription' => new external_value(PARAM_INT, 'If the description is showed', VALUE_OPTIONAL),
+                        'availability' => new external_value(PARAM_RAW, 'Availability settings', VALUE_OPTIONAL),
+                    )
+                ),
+                'warnings' => new external_warnings()
+            )
+        );
+    }
+
 }
 
 /**
index 17f2c3a..6973dea 100644 (file)
@@ -43,7 +43,7 @@ Feature: Sections can be edited and deleted in topics format
   Scenario: Deleting the last section in topics format
     When I delete section "5"
     Then I should see "Are you absolutely sure you want to completely delete \"Topic 5\" and all the activities it contains?"
-    And I press "Continue"
+    And I press "Delete"
     And I should not see "Topic 5"
     And I navigate to "Edit settings" node in "Course administration"
     And I expand all fieldsets
@@ -51,7 +51,7 @@ Feature: Sections can be edited and deleted in topics format
 
   Scenario: Deleting the middle section in topics format
     When I delete section "4"
-    And I press "Continue"
+    And I press "Delete"
     Then I should not see "Topic 5"
     And I should not see "Test chat name"
     And I should see "Test choice name" in the "li#section-4" "css_element"
@@ -63,7 +63,7 @@ Feature: Sections can be edited and deleted in topics format
     When I follow "Reduce the number of sections"
     Then I should see "Orphaned activities (section 5)" in the "li#section-5" "css_element"
     And I delete section "5"
-    And I press "Continue"
+    And I press "Delete"
     And I should not see "Topic 5"
     And I should not see "Orphaned activities"
     And "li#section-5" "css_element" should not exist
@@ -77,7 +77,7 @@ Feature: Sections can be edited and deleted in topics format
     And "li#section-5.orphaned" "css_element" should exist
     And "li#section-4.orphaned" "css_element" should not exist
     And I delete section "1"
-    And I press "Continue"
+    And I press "Delete"
     And I should not see "Test book name"
     And I should see "Orphaned activities (section 4)" in the "li#section-4" "css_element"
     And "li#section-5" "css_element" should not exist
index 96ba213..255fa83 100644 (file)
@@ -45,7 +45,7 @@ Feature: Sections can be edited and deleted in weeks format
     Given I should see "29 May - 4 June" in the "li#section-5" "css_element"
     When I delete section "5"
     Then I should see "Are you absolutely sure you want to completely delete \"29 May - 4 June\" and all the activities it contains?"
-    And I press "Continue"
+    And I press "Delete"
     And I should not see "29 May - 4 June"
     And I navigate to "Edit settings" node in "Course administration"
     And I expand all fieldsets
@@ -54,7 +54,7 @@ Feature: Sections can be edited and deleted in weeks format
   Scenario: Deleting the middle section in weeks format
     Given I should see "29 May - 4 June" in the "li#section-5" "css_element"
     When I delete section "4"
-    And I press "Continue"
+    And I press "Delete"
     Then I should not see "29 May - 4 June"
     And I should not see "Test chat name"
     And I should see "Test choice name" in the "li#section-4" "css_element"
@@ -66,7 +66,7 @@ Feature: Sections can be edited and deleted in weeks format
     When I follow "Reduce the number of sections"
     Then I should see "Orphaned activities (section 5)" in the "li#section-5" "css_element"
     And I delete section "5"
-    And I press "Continue"
+    And I press "Delete"
     And I should not see "29 May - 4 June"
     And I should not see "Orphaned activities"
     And "li#section-5" "css_element" should not exist
@@ -80,7 +80,7 @@ Feature: Sections can be edited and deleted in weeks format
     And "li#section-5.orphaned" "css_element" should exist
     And "li#section-4.orphaned" "css_element" should not exist
     And I delete section "1"
-    And I press "Continue"
+    And I press "Delete"
     And I should not see "Test book name"
     And I should see "Orphaned activities (section 4)" in the "li#section-4" "css_element"
     And "li#section-5" "css_element" should not exist
index 72bb761..9738ac4 100644 (file)
@@ -212,7 +212,7 @@ if (!empty($activities)) {
                 echo $OUTPUT->spacer(array('height'=>30, 'br'=>true)); // should be done with CSS instead
             }
             echo $OUTPUT->box_start();
-            if (!empty($activity->name)) {
+            if (strval($activity->name) !== '') {
                 echo html_writer::tag('h2', $activity->name);
             }
             $inbox = true;
index eed9a3d..675f367 100644 (file)
@@ -173,7 +173,7 @@ switch($requestmethod) {
                             $module->name = clean_param($title, PARAM_CLEANHTML);
                         }
 
-                        if (!empty($module->name)) {
+                        if (strval($module->name) !== '') {
                             $DB->update_record($cm->modname, $module);
                             $cm->name = $module->name;
                             \core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
index 1c74098..fba44dd 100644 (file)
@@ -54,7 +54,7 @@ Feature: Test we can both create and delete a course.
     # Redirect
     And I should see "Delete TCCAC"
     And I should see "Test course: create a course (TCCAC)"
-    And I press "Continue"
+    And I press "Delete"
     # Redirect
     And I should see "Deleting TCCAC"
     And I should see "TCCAC has been completely deleted"
@@ -93,7 +93,7 @@ Feature: Test we can both create and delete a course.
     # Redirect
     And I should see "Delete TCCAC"
     And I should see "Test course: create a course (TCCAC)"
-    And I press "Continue"
+    And I press "Delete"
     # Redirect
     And I should see "Deleting TCCAC"
     And I should see "TCCAC has been completely deleted"
index 1b8bd90..875d882 100644 (file)
@@ -600,6 +600,71 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         $this->assertEquals($DB->count_records('course'), count($courses));
     }
 
+    /**
+     * Test search_courses
+     */
+    public function test_search_courses () {
+
+        global $DB, $CFG;
+        require_once($CFG->dirroot . '/tag/lib.php');
+
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+        $generatedcourses = array();
+        $coursedata1['fullname'] = 'FIRST COURSE';
+        $course1  = self::getDataGenerator()->create_course($coursedata1);
+        $coursedata2['fullname'] = 'SECOND COURSE';
+        $course2  = self::getDataGenerator()->create_course($coursedata2);
+        // Search by name.
+        $results = core_course_external::search_courses('search', 'FIRST');
+        $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results);
+        $this->assertEquals($coursedata1['fullname'], $results['courses'][0]['fullname']);
+        $this->assertCount(1, $results['courses']);
+
+        // Create the forum.
+        $record = new stdClass();
+        $record->introformat = FORMAT_HTML;
+        $record->course = $course2->id;
+        // Set Aggregate type = Average of ratings.
+        $forum = self::getDataGenerator()->create_module('forum', $record);
+
+        // Search by module.
+        $results = core_course_external::search_courses('modulelist', 'forum');
+        $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results);
+        $this->assertEquals(1, $results['total']);
+
+        // Enable coursetag option.
+        set_config('block_tags_showcoursetags', true);
+        // Add tag 'TAG-LABEL ON SECOND COURSE' to Course2.
+        tag_set('course', $course2->id, array('TAG-LABEL ON SECOND COURSE'), 'core', context_course::instance($course2->id)->id);
+        $taginstance = $DB->get_record('tag_instance', array('itemtype' => 'course', 'itemid' => $course2->id), '*', MUST_EXIST);
+        // Search by tagid.
+        $results = core_course_external::search_courses('tagid', $taginstance->tagid);
+        $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results);
+        $this->assertEquals($coursedata2['fullname'], $results['courses'][0]['fullname']);
+
+        // Search by block (use news_items default block).
+        $blockid = $DB->get_field('block', 'id', array('name' => 'news_items'));
+        $results = core_course_external::search_courses('blocklist', $blockid);
+        $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results);
+        $this->assertEquals(2, $results['total']);
+
+        // Now as a normal user.
+        $user = self::getDataGenerator()->create_user();
+        $this->setUser($user);
+
+        $results = core_course_external::search_courses('search', 'FIRST');
+        $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results);
+        $this->assertCount(1, $results['courses']);
+        $this->assertEquals(1, $results['total']);
+        $this->assertEquals($coursedata1['fullname'], $results['courses'][0]['fullname']);
+
+        // Search by block (use news_items default block). Should fail (only admins allowed).
+        $this->setExpectedException('required_capability_exception');
+        $results = core_course_external::search_courses('blocklist', $blockid);
+
+    }
+
     /**
      * Create a course with contents
      * @return array A list with the course object and course modules objects
@@ -1457,4 +1522,66 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         $this->assertEmpty($event->other);
 
     }
+
+    /**
+     * Test get_course_module
+     */
+    public function test_get_course_module() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $this->setAdminUser();
+        $course = self::getDataGenerator()->create_course();
+        $record = array(
+            'course' => $course->id,
+            'name' => 'First Chat'
+        );
+        $options = array(
+            'idnumber' => 'ABC',
+            'visible' => 0
+        );
+        // Hidden activity.
+        $chat = self::getDataGenerator()->create_module('chat', $record, $options);
+
+        // Test admin user can see the complete hidden activity.
+        $result = core_course_external::get_course_module($chat->cmid);
+        $result = external_api::clean_returnvalue(core_course_external::get_course_module_returns(), $result);
+
+        $this->assertCount(0, $result['warnings']);
+        // Test we retrieve all the fields.
+        $this->assertCount(22, $result['cm']);
+        $this->assertEquals($record['name'], $result['cm']['name']);
+        $this->assertEquals($options['idnumber'], $result['cm']['idnumber']);
+
+        $student = $this->getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+
+        self::getDataGenerator()->enrol_user($student->id,  $course->id, $studentrole->id);
+        $this->setUser($student);
+
+        // The user shouldn't be able to see the activity.
+        try {
+            core_course_external::get_course_module($chat->cmid);
+            $this->fail('Exception expected due to invalid permissions.');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('requireloginerror', $e->errorcode);
+        }
+
+        // Make module visible.
+        set_coursemodule_visible($chat->cmid, 1);
+
+        // Test student user.
+        $result = core_course_external::get_course_module($chat->cmid);
+        $result = external_api::clean_returnvalue(core_course_external::get_course_module_returns(), $result);
+
+        $this->assertCount(0, $result['warnings']);
+        // Test we retrieve only the few files we can see.
+        $this->assertCount(11, $result['cm']);
+        $this->assertEquals($chat->cmid, $result['cm']['id']);
+        $this->assertEquals($course->id, $result['cm']['course']);
+        $this->assertEquals('chat', $result['cm']['modname']);
+        $this->assertEquals($chat->id, $result['cm']['instance']);
+
+    }
 }
diff --git a/enrol/flatfile/classes/task/flatfile_sync_task.php b/enrol/flatfile/classes/task/flatfile_sync_task.php
new file mode 100644 (file)
index 0000000..a34da88
--- /dev/null
@@ -0,0 +1,66 @@
+<?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/>.
+
+/**
+ * Scheduled task for processing flatfile enrolments.
+ *
+ * @package    enrol_flatfile
+ * @copyright  2014 Troy Williams
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace enrol_flatfile\task;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Simple task to run sync enrolments.
+ *
+ * @copyright  2014 Troy Williams
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class flatfile_sync_task extends \core\task\scheduled_task {
+
+    /**
+     * Get a descriptive name for this task (shown to admins).
+     *
+     * @return string
+     */
+    public function get_name() {
+        return get_string('flatfilesync', 'enrol_flatfile');
+    }
+
+    /**
+     * Do the job.
+     * Throw exceptions on errors (the job will be retried).
+     */
+    public function execute() {
+        global $CFG;
+
+        require_once($CFG->dirroot . '/enrol/flatfile/lib.php');
+
+        if (!enrol_is_enabled('flatfile')) {
+            return;
+        }
+
+        // Instance of enrol_flatfile_plugin.
+        $plugin = enrol_get_plugin('flatfile');
+        $result = $plugin->sync(new \null_progress_trace());
+        return $result;
+
+    }
+
+}
index 59df7e0..2b248c1 100644 (file)
  *   - you need to change the "www-data" to match the apache user account
  *   - use "su" if "sudo" not available
  *
+ * Update
+ *
+ * This plugin now has a enrolment sync scheduled task. Scheduled tasks were
+ * introduced in Moodle 2.7.  It is possible to override the scheduled tasks
+ * configuration and run a single scheduled task immediately using the
+ * admin/tool/task/cli/schedule_task.php script. This is the recommended
+ * method to use for immediate enrollment synchronisation.
+ *
+ * Usage help:
+ * $ php admin/tool/task/cli/schedule_task.php -h
+ *
+ * Execute task:
+ * $ sudo -u www-data /usr/bin/php admin/tool/task/cli/schedule_task.php /
+ * --execute=\\enrol_flatfile\\task\\flatfile_sync_task
+ *
  * @package    enrol_flatfile
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
diff --git a/enrol/flatfile/db/tasks.php b/enrol/flatfile/db/tasks.php
new file mode 100644 (file)
index 0000000..a78fd91
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Definition of flatfile enrolment scheduled tasks.
+ *
+ * @package    enrol_flatfile
+ * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$tasks = array(
+    array(
+        'classname' => '\enrol_flatfile\task\flatfile_sync_task',
+        'blocking' => 0,
+        'minute' => '15',
+        'hour' => '*',
+        'day' => '*',
+        'dayofweek' => '*',
+        'month' => '*'
+    )
+);
index 7fd1901..a1c9922 100644 (file)
@@ -29,6 +29,7 @@ $string['filelockedmail'] = 'The text file you are using for file-based enrolmen
 $string['filelockedmailsubject'] = 'Important error: Enrolment file';
 $string['flatfile:manage'] = 'Manage user enrolments manually';
 $string['flatfile:unenrol'] = 'Unenrol users from the course manually';
+$string['flatfilesync'] = 'Flat file enrolment sync';
 $string['location'] = 'File location';
 $string['location_desc'] = 'Specify full path to the enrolment file. The file is automatically deleted after processing.';
 $string['notifyadmin'] = 'Notify administrator';
index 41e0577..57438fb 100644 (file)
@@ -161,11 +161,6 @@ class enrol_flatfile_plugin extends enrol_plugin {
         }
     }
 
-    public function cron() {
-        $trace = new text_progress_trace();
-        $this->sync($trace);
-    }
-
     /**
      * Execute synchronisation.
      * @param progress_trace
@@ -446,7 +441,7 @@ class enrol_flatfile_plugin extends enrol_plugin {
             $notify = false;
             if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$user->id))) {
                 // Update only.
-                $this->update_user_enrol($instance, $user->id, ENROL_USER_ACTIVE, $roleid, $timestart, $timeend);
+                $this->update_user_enrol($instance, $user->id, ENROL_USER_ACTIVE, $timestart, $timeend);
                 if (!$DB->record_exists('role_assignments', array('contextid'=>$context->id, 'roleid'=>$roleid, 'userid'=>$user->id, 'component'=>'enrol_flatfile', 'itemid'=>$instance->id))) {
                     role_assign($roleid, $user->id, $context->id, 'enrol_flatfile', $instance->id);
                 }
index 93ed0ef..0ab21c9 100644 (file)
@@ -467,4 +467,35 @@ class enrol_flatfile_testcase extends advanced_testcase {
         $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
         $this->assertEquals(0, $DB->count_records('role_assignments', array('roleid'=>$managerrole->id)));
     }
+
+    /**
+     * Flatfile enrolment sync task test.
+     */
+    public function test_flatfile_sync_task() {
+        global $CFG, $DB;
+        $this->resetAfterTest();
+
+        $flatfileplugin = enrol_get_plugin('flatfile');
+
+        $trace = new null_progress_trace();
+        $this->enable_plugin();
+        $file = "$CFG->dataroot/enrol.txt";
+        $flatfileplugin->set_config('location', $file);
+
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $this->assertNotEmpty($studentrole);
+
+        $user1 = $this->getDataGenerator()->create_user(array('idnumber' => 'u1'));
+        $course1 = $this->getDataGenerator()->create_course(array('idnumber' => 'c1'));
+        $context1 = context_course::instance($course1->id);
+
+        $data =
+            "add,student,u1,c1";
+        file_put_contents($file, $data);
+
+        $task = new enrol_flatfile\task\flatfile_sync_task;
+        $task->execute();
+
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid' => $studentrole->id)));
+    }
 }
index efc367c..e7e8b86 100644 (file)
@@ -25,7 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2015051100;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2015090700;        // The current plugin version (Date: YYYYMMDDRR)
 $plugin->requires  = 2015050500;        // Requires this Moodle version
 $plugin->component = 'enrol_flatfile';  // Full name of the plugin (used for diagnostics)
-$plugin->cron      = 60;
index 86bc7a0..94ccd7b 100644 (file)
@@ -128,10 +128,22 @@ switch ($action) {
             $roleid = null;
         }
 
+        if (empty($startdate)) {
+            if (!$startdate = get_config('enrol_manual', 'enrolstart')) {
+                // Default to now if there is no system setting.
+                $startdate = 4;
+            }
+        }
+
         switch($startdate) {
             case 2:
                 $timestart = $course->startdate;
                 break;
+            case 4:
+                // We mimic get_enrolled_sql round(time(), -2) but always floor as we want users to always access their
+                // courses once they are enrolled.
+                $timestart = intval(substr(time(), 0, 8) . '00') - 1;
+                break;
             case 3:
             default:
                 $today = time();
index 3e58900..db7d182 100644 (file)
@@ -66,6 +66,12 @@ function xmldb_enrol_manual_upgrade($oldversion) {
     // Moodle v2.9.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2015091500) {
+        // We keep today as default enrolment start time on upgrades.
+        set_config('enrolstart', 3, 'enrol_manual');
+        upgrade_plugin_savepoint(true, 2015091500, 'enrol', 'manual');
+    }
+
     return true;
 }
 
index 7c5f941..b325f4a 100644 (file)
@@ -29,6 +29,7 @@ $string['assignrole'] = 'Assign role';
 $string['browseusers'] = 'Browse users';
 $string['browsecohorts'] = 'Browse cohorts';
 $string['confirmbulkdeleteenrolment'] = 'Are you sure you want to delete these users enrolments?';
+$string['defaultstart'] = 'Default enrolment start';
 $string['defaultperiod'] = 'Default enrolment duration';
 $string['defaultperiod_desc'] = 'Default length of time that the enrolment is valid. If set to zero, the enrolment duration will be unlimited by default.';
 $string['defaultperiod_help'] = 'Default length of time that the enrolment is valid, starting with the moment the user is enrolled. If disabled, the enrolment duration will be unlimited by default.';
@@ -56,6 +57,7 @@ $string['manual:manage'] = 'Manage user enrolments';
 $string['manual:unenrol'] = 'Unenrol users from the course';
 $string['manual:unenrolself'] = 'Unenrol self from the course';
 $string['messageprovider:expiry_notification'] = 'Manual enrolment expiry notifications';
+$string['now'] = 'Now';
 $string['pluginname'] = 'Manual enrolments';
 $string['pluginname_desc'] = 'The manual enrolments plugin allows users to be enrolled manually via a link in the course administration settings, by a user with appropriate permissions such as a teacher. The plugin should normally be enabled, since certain other enrolment plugins, such as self enrolment, require it.';
 $string['status'] = 'Enable manual enrolments';
@@ -71,4 +73,4 @@ $string['unenrolusers'] = 'Unenrol users';
 $string['wscannotenrol'] = 'Plugin instance cannot manually enrol a user in the course id = {$a->courseid}';
 $string['wsnoinstance'] = 'Manual enrolment plugin instance doesn\'t exist or is disabled for the course (id = {$a->courseid})';
 $string['wsusercannotassign'] = 'You don\'t have the permission to assign this role ({$a->roleid}) to this user ({$a->userid}) in this course({$a->courseid}).';
-$string['manualpluginnotinstalled'] = 'The "Manual" plugin has not yet been installed';
\ No newline at end of file
+$string['manualpluginnotinstalled'] = 'The "Manual" plugin has not yet been installed';
index 464d76b..b50e3cc 100644 (file)
@@ -226,14 +226,19 @@ class enrol_manual_plugin extends enrol_plugin {
         $button->class .= ' enrol_manual_plugin';
 
         $startdate = $manager->get_course()->startdate;
+        if (!$defaultstart = get_config('enrol_manual', 'enrolstart')) {
+            // Default to now if there is no system setting.
+            $defaultstart = 4;
+        }
         $startdateoptions = array();
-        $timeformat = get_string('strftimedatefullshort');
+        $dateformat = get_string('strftimedatefullshort');
         if ($startdate > 0) {
-            $startdateoptions[2] = get_string('coursestart') . ' (' . userdate($startdate, $timeformat) . ')';
+            $startdateoptions[2] = get_string('coursestart') . ' (' . userdate($startdate, $dateformat) . ')';
         }
-        $today = time();
-        $today = make_timestamp(date('Y', $today), date('m', $today), date('d', $today), 0, 0, 0);
-        $startdateoptions[3] = get_string('today') . ' (' . userdate($today, $timeformat) . ')' ;
+        $now = time();
+        $today = make_timestamp(date('Y', $now), date('m', $now), date('d', $now), 0, 0, 0);
+        $startdateoptions[3] = get_string('today') . ' (' . userdate($today, $dateformat) . ')';
+        $startdateoptions[4] = get_string('now', 'enrol_manual') . ' (' . userdate($now, get_string('strftimedatetimeshort')) . ')';
         $defaultduration = $instance->enrolperiod > 0 ? $instance->enrolperiod / 86400 : '';
 
         $modules = array('moodle-enrol_manual-quickenrolment', 'moodle-enrol_manual-quickenrolment-skin');
@@ -245,6 +250,7 @@ class enrol_manual_plugin extends enrol_plugin {
             'optionsStartDate'    => $startdateoptions,
             'defaultRole'         => $instance->roleid,
             'defaultDuration'     => $defaultduration,
+            'defaultStartDate'    => (int)$defaultstart,
             'disableGradeHistory' => $CFG->disablegradehistory,
             'recoverGradesDefault'=> '',
             'cohortsAvailable'    => cohort_get_available_cohorts($manager->get_context(), COHORT_WITH_NOTENROLLED_MEMBERS_ONLY, 0, 1) ? true : false
index 6c86e6f..0b9ca4c 100644 (file)
@@ -28,7 +28,7 @@ require_once($CFG->dirroot.'/enrol/manual/locallib.php');
 $enrolid      = required_param('enrolid', PARAM_INT);
 $roleid       = optional_param('roleid', -1, PARAM_INT);
 $extendperiod = optional_param('extendperiod', 0, PARAM_INT);
-$extendbase   = optional_param('extendbase', 3, PARAM_INT);
+$extendbase   = optional_param('extendbase', 0, PARAM_INT);
 
 $instance = $DB->get_record('enrol', array('id'=>$enrolid, 'enrol'=>'manual'), '*', MUST_EXIST);
 $course = $DB->get_record('course', array('id'=>$instance->courseid), '*', MUST_EXIST);
@@ -83,24 +83,31 @@ for ($i=1; $i<=365; $i++) {
     $seconds = $i * 86400;
     $periodmenu[$seconds] = get_string('numdays', '', $i);
 }
-// Work out the apropriate default setting.
+// Work out the apropriate default settings.
 if ($extendperiod) {
     $defaultperiod = $extendperiod;
 } else {
     $defaultperiod = $instance->enrolperiod;
 }
+if (empty($extendbase)) {
+    if (!$extendbase = get_config('enrol_manual', 'enrolstart')) {
+        // Default to now if there is no system setting.
+        $extendbase = 4;
+    }
+}
 
 // Build the list of options for the starting from dropdown.
-$timeformat = get_string('strftimedatefullshort');
-$today = time();
-$today = make_timestamp(date('Y', $today), date('m', $today), date('d', $today), 0, 0, 0);
+$now = time();
+$today = make_timestamp(date('Y', $now), date('m', $now), date('d', $now), 0, 0, 0);
+$dateformat = get_string('strftimedatefullshort');
 
 // Enrolment start.
 $basemenu = array();
 if ($course->startdate > 0) {
-    $basemenu[2] = get_string('coursestart') . ' (' . userdate($course->startdate, $timeformat) . ')';
+    $basemenu[2] = get_string('coursestart') . ' (' . userdate($course->startdate, $dateformat) . ')';
 }
-$basemenu[3] = get_string('today') . ' (' . userdate($today, $timeformat) . ')' ;
+$basemenu[3] = get_string('today') . ' (' . userdate($today, $dateformat) . ')';
+$basemenu[4] = get_string('now', 'enrol_manual') . ' (' . userdate($now, get_string('strftimedatetimeshort')) . ')';
 
 // Process add and removes.
 if ($canenrol && optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
@@ -111,6 +118,11 @@ if ($canenrol && optional_param('add', false, PARAM_BOOL) && confirm_sesskey())
                 case 2:
                     $timestart = $course->startdate;
                     break;
+                case 4:
+                    // We mimic get_enrolled_sql round(time(), -2) but always floor as we want users to always access their
+                    // courses once they are enrolled.
+                    $timestart = intval(substr($now, 0, 8) . '00') - 1;
+                    break;
                 case 3:
                 default:
                     $timestart = $today;
index dbfc766..2056c19 100644 (file)
@@ -66,6 +66,11 @@ if ($ADMIN->fulltree) {
             get_string('defaultrole', 'role'), '', $student->id, $options));
     }
 
+    $options = array(2 => get_string('coursestart'), 3 => get_string('today'), 4 => get_string('now', 'enrol_manual'));
+    $settings->add(
+        new admin_setting_configselect('enrol_manual/enrolstart', get_string('defaultstart', 'enrol_manual'), '', 4, $options)
+    );
+
     $settings->add(new admin_setting_configduration('enrol_manual/enrolperiod',
         get_string('defaultperiod', 'enrol_manual'), get_string('defaultperiod_desc', 'enrol_manual'), 0));
 
index bb592b3..ebb6db1 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2015051100;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2015091500;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2015050500;        // Requires this Moodle version
 $plugin->component = 'enrol_manual';    // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 600;
index f3de1bf..d05b1cb 100644 (file)
@@ -215,12 +215,12 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
             var options = this.get(UEP.OPTIONSTARTDATE);
             var index = 0, count = 0;
             for (var i in options) {
-                count++;
                 var option = create('<option value="'+i+'">'+options[i]+'</option>');
                 if (i == defaultvalue) {
                     index = count;
                 }
                 select.append(option);
+                count++;
             }
             select.set('selectedIndex', index);
         },
@@ -608,7 +608,7 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
                 value : 0
             },
             defaultStartDate : {
-                value : 2,
+                value : 4,
                 validator : Y.Lang.isNumber
             },
             defaultDuration : {
diff --git a/enrol/self/classes/empty_form.php b/enrol/self/classes/empty_form.php
new file mode 100644 (file)
index 0000000..a54d70d
--- /dev/null
@@ -0,0 +1,41 @@
+<?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/>.
+
+/**
+ * Empty enrol_self form.
+ *
+ * Useful to mimic valid enrol instances UI when the enrolment instance is not available.
+ *
+ * @package enrol_self
+ * @copyright 2015 David Monllaó
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/formslib.php');
+
+class enrol_self_empty_form extends moodleform {
+
+    /**
+     * Form definition.
+     * @return void
+     */
+    public function definition() {
+        $this->_form->addElement('header', 'selfheader', $this->_customdata->header);
+        $this->_form->addElement('static', 'info', '', $this->_customdata->info);
+    }
+}
index 5c668ef..6c1364b 100644 (file)
@@ -234,8 +234,8 @@ class enrol_self_plugin extends enrol_plugin {
 
         $enrolstatus = $this->can_self_enrol($instance);
 
-        // Don't show enrolment instance form, if user can't enrol using it.
         if (true === $enrolstatus) {
+            // This user can self enrol using this instance.
             $form = new enrol_self_enrol_form(NULL, $instance);
             $instanceid = optional_param('instance', 0, PARAM_INT);
             if ($instance->id == $instanceid) {
@@ -243,14 +243,23 @@ class enrol_self_plugin extends enrol_plugin {
                     $this->enrol_self($instance, $data);
                 }
             }
-
-            ob_start();
-            $form->display();
-            $output = ob_get_clean();
-            return $OUTPUT->box($output);
         } else {
-            return $OUTPUT->box($enrolstatus);
-        }
+            // This user can not self enrol using this instance. Using an empty form to keep
+            // the UI consistent with other enrolment plugins that returns a form.
+            $data = new stdClass();
+            $data->header = $this->get_instance_name($instance);
+            $data->info = $enrolstatus;
+
+            // The can_self_enrol call returns a button to the login page if the user is a
+            // guest, setting the login url to the form if that is the case.
+            $url = isguestuser() ? get_login_url() : null;
+            $form = new enrol_self_empty_form($url, $data);
+        }
+
+        ob_start();
+        $form->display();
+        $output = ob_get_clean();
+        return $OUTPUT->box($output);
     }
 
     /**
index b932ef7..ecffc3c 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js differ
index 34d316d..46b748d 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js differ
index b932ef7..ecffc3c 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js differ
index 564f348..3689b4a 100644 (file)
@@ -20,46 +20,53 @@ Y.extend(AUTOLINKER, Y.Base, {
     alertpanels: {},
     initializer : function() {
         var self = this;
-        Y.delegate('click', function(e){
-            e.preventDefault();
+        require(['core/event'], function(event) {
+            Y.delegate('click', function(e){
+                e.preventDefault();
 
-            //display a progress indicator
-            var title = '',
-                content = Y.Node.create('<div id="glossaryfilteroverlayprogress">' +
-                                        '<img src="' + M.cfg.loadingicon + '" class="spinner" />' +
-                                        '</div>'),
-                o = new Y.Overlay({
-                    headerContent :  title,
-                    bodyContent : content
-                }),
-                fullurl,
-                cfg;
-            self.overlay = o;
-            o.render(Y.one(document.body));
+                //display a progress indicator
+                var title = '',
+                    content = Y.Node.create('<div id="glossaryfilteroverlayprogress">' +
+                                            '<img src="' + M.cfg.loadingicon + '" class="spinner" />' +
+                                            '</div>'),
+                    o = new Y.Overlay({
+                        headerContent :  title,
+                        bodyContent : content
+                    }),
+                    fullurl,
+                    cfg;
+                self.overlay = o;
+                o.render(Y.one(document.body));
 
-            //Switch over to the ajax url and fetch the glossary item
-            fullurl = this.getAttribute('href').replace('showentry.php','showentry_ajax.php');
-            cfg = {
-                method: 'get',
-                context : self,
-                on: {
-                    success: function(id, o) {
-                        this.display_callback(o.responseText);
-                    },
-                    failure: function(id, o) {
-                        var debuginfo = o.statusText;
-                        if (M.cfg.developerdebug) {
-                            o.statusText += ' (' + fullurl + ')';
+                //Switch over to the ajax url and fetch the glossary item
+                fullurl = this.getAttribute('href').replace('showentry.php','showentry_ajax.php');
+                cfg = {
+                    method: 'get',
+                    context : self,
+                    on: {
+                        success: function(id, o) {
+                            this.display_callback(o.responseText, event);
+                        },
+                        failure: function(id, o) {
+                            var debuginfo = o.statusText;
+                            if (M.cfg.developerdebug) {
+                                o.statusText += ' (' + fullurl + ')';
+                            }
+                            new M.core.exception({ message: debuginfo });
                         }
-                        this.display_callback('bodyContent',debuginfo);
                     }
-                }
-            };
-            Y.io(fullurl, cfg);
+                };
+                Y.io(fullurl, cfg);
 
-        }, Y.one(document.body), 'a.glossary.autolink.concept');
+            }, Y.one(document.body), 'a.glossary.autolink.concept');
+        });
     },
-    display_callback : function(content) {
+    /**
+     * @method display_callback
+     * @param {String} content - Content to display
+     * @param {Object} event The amd event module used to fire events for jquery and yui.
+     */
+    display_callback : function(content, event) {
         var data,
             key,
             alertpanel,
@@ -76,7 +83,8 @@ Y.extend(AUTOLINKER, Y.Base, {
                     definition = data.entries[key].definition + data.entries[key].attachments;
                     alertpanel = new M.core.alert({title:data.entries[key].concept, draggable: true,
                         message:definition, modal:false, yesLabel: M.util.get_string('ok', 'moodle')});
-                    Y.fire(M.core.event.FILTER_CONTENT_UPDATED, {nodes: (new Y.NodeList(alertpanel.get('boundingBox')))});
+                    // Notify the filters about the modified nodes.
+                    event.notifyFilterContentUpdated(alertpanel.get('boundingBox').getDOMNode());
                     Y.Node.one('#id_yuialertconfirm-' + alertpanel.get('COUNT')).focus();
 
                     // Register alertpanel for stacking.
index e73bc93..672888a 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Edit and review page for grade categories and items
+ * The Gradebook setup page.
  *
  * @package   core_grades
  * @copyright 2008 Nicolas Connault
@@ -244,7 +244,7 @@ if ($data = data_submitted() and confirm_sesskey()) {
     }
 }
 
-print_grade_page_head($courseid, 'settings', 'setup', get_string('categoriesanditems', 'grades'));
+print_grade_page_head($courseid, 'settings', 'setup', get_string('gradebooksetup', 'grades'));
 
 // Print Table of categories and items
 echo $OUTPUT->box_start('gradetreebox generalbox');
index 0efde54..b6dc052 100644 (file)
@@ -435,9 +435,9 @@ class grade_edit_tree {
         return $str;
     }
 
-    //Trims trailing zeros
-    //Used on the 'categories and items' page for grade items settings like aggregation co-efficient
-    //Grader report has its own decimal place settings so they are handled elsewhere
+    // Trims trailing zeros.
+    // Used on the 'Gradebook setup' page for grade items settings like aggregation co-efficient.
+    // Grader report has its own decimal place settings so they are handled elsewhere.
     static function format_number($number) {
         $formatted = rtrim(format_float($number, 4),'0');
         if (substr($formatted, -1)==get_string('decsep', 'langconfig')) { //if last char is the decimal point
index 8b25939..c91122b 100644 (file)
@@ -68,7 +68,6 @@ class grade_export_xml extends grade_export {
             foreach ($userdata->grades as $itemid => $grade) {
                 $grade_item = $this->grade_items[$itemid];
                 $grade->grade_item =& $grade_item;
-                $gradestr = $this->format_grade($grade, $this->displaytype); // no formating for now
 
                 // MDL-11669, skip exported grades or bad grades (if setting says so)
                 if ($export_tracking) {
@@ -88,7 +87,19 @@ class grade_export_xml extends grade_export {
                 fwrite($handle,  "\t\t<assignment>{$grade_item->idnumber}</assignment>\n");
                 // this column should be customizable to use either student id, idnumber, uesrname or email.
                 fwrite($handle,  "\t\t<student>{$user->idnumber}</student>\n");
-                fwrite($handle,  "\t\t<score>$gradestr</score>\n");
+                // Format and display the grade in the selected display type (real, letter, percentage).
+                if (is_array($this->displaytype)) {
+                    // Grades display type came from the return of export_bulk_export_data() on grade publishing.
+                    foreach ($this->displaytype as $gradedisplayconst) {
+                        $gradestr = $this->format_grade($grade, $gradedisplayconst);
+                        fwrite($handle,  "\t\t<score>$gradestr</score>\n");
+                    }
+                } else {
+                    // Grade display type submitted directly from the grade export form.
+                    $gradestr = $this->format_grade($grade, $this->displaytype);
+                    fwrite($handle,  "\t\t<score>$gradestr</score>\n");
+                }
+
                 if ($this->export_feedback) {
                     $feedbackstr = $this->format_feedback($userdata->feedbacks[$itemid]);
                     fwrite($handle,  "\t\t<feedback>$feedbackstr</feedback>\n");
index 215d346..46fd52a 100644 (file)
@@ -185,7 +185,7 @@ class grading_manager {
         } else if ($this->get_context()->contextlevel >= CONTEXT_COURSE) {
             list($context, $course, $cm) = get_context_info_array($this->get_context()->id);
 
-            if (!empty($cm->name)) {
+            if (strval($cm->name) !== '') {
                 $title = $cm->name;
             } else {
                 debugging('Gradable areas are currently supported at the course module level only', DEBUG_DEVELOPER);
index fbfe359..2216d57 100644 (file)
@@ -2832,9 +2832,9 @@ abstract class grade_helper {
         $context = context_course::instance($courseid);
         self::$managesetting = array();
         if ($courseid != SITEID && has_capability('moodle/grade:manage', $context)) {
-            self::$managesetting['categoriesanditems'] = new grade_plugin_info('setup',
+            self::$managesetting['gradebooksetup'] = new grade_plugin_info('setup',
                 new moodle_url('/grade/edit/tree/index.php', array('id' => $courseid)),
-                get_string('categoriesanditems', 'grades'));
+                get_string('gradebooksetup', 'grades'));
             self::$managesetting['coursesettings'] = new grade_plugin_info('coursesettings',
                 new moodle_url('/grade/edit/settings/index.php', array('id'=>$courseid)),
                 get_string('coursegradesettings', 'grades'));
index 3b04d7e..923fc2f 100644 (file)
@@ -350,10 +350,10 @@ function grade_report_overview_settings_definition(&$mform) {
                       GRADE_REPORT_SHOW_TOTAL_IF_CONTAINS_HIDDEN => get_string('hidetotalshowexhiddenitems', 'grades'),
                       GRADE_REPORT_SHOW_REAL_TOTAL_IF_CONTAINS_HIDDEN => get_string('hidetotalshowinchiddenitems', 'grades') );
 
-    if (empty($CFG->grade_report_overview_showtotalsifcontainhidden)) {
+    if (!array_key_exists($CFG->grade_report_overview_showtotalsifcontainhidden, $options)) {
         $options[-1] = get_string('defaultprev', 'grades', $options[0]);
     } else {
-        $options[-1] = get_string('defaultprev', 'grades', $options[1]);
+        $options[-1] = get_string('defaultprev', 'grades', $options[$CFG->grade_report_overview_showtotalsifcontainhidden]);
     }
 
     $mform->addElement('select', 'report_overview_showtotalsifcontainhidden', get_string('hidetotalifhiddenitems', 'grades'), $options);
index e9cbcbd..727aeb0 100644 (file)
@@ -80,7 +80,8 @@ class behat_grade extends behat_base {
     }
 
     /**
-     * Sets a calculated manual grade item. Needs a table with item name - idnumber relation. The step requires you to be in categories and items page.
+     * Sets a calculated manual grade item. Needs a table with item name - idnumber relation.
+     * The step requires you to be in the 'Gradebook setup' page.
      *
      * @Given /^I set "(?P<calculation_string>(?:[^"]|\\")*)" calculation for grade item "(?P<grade_item_string>(?:[^"]|\\")*)" with idnumbers:$/
      * @param string $calculation The calculation.
@@ -139,7 +140,7 @@ class behat_grade extends behat_base {
 
     /**
      * Sets a calculated manual grade category total. Needs a table with item name - idnumber relation.
-     * The step requires you to be in categories and items page.
+     * The step requires you to be in the 'Gradebook setup' page.
      *
      * @Given /^I set "(?P<calculation_string>(?:[^"]|\\")*)" calculation for grade category "(?P<grade_item_string>(?:[^"]|\\")*)" with idnumbers:$/
      * @param string $calculation The calculation.
index d10d14a..09f1b04 100644 (file)
@@ -235,7 +235,7 @@ Feature: We can use calculated grade totals
       | itemname              | course | outcome | gradetype | scale      |
       | Test outcome item one | C1     | OT1     | Scale     | Test Scale |
     And I expand "Setup" node
-    And I follow "Categories and items"
+    And I follow "Gradebook setup"
     And I set the following settings for grade item "Course 1":
       | Aggregation                     | Natural |
       | Include outcomes in aggregation | 1       |
@@ -258,7 +258,7 @@ Feature: We can use calculated grade totals
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I expand "Setup" node
-    And I follow "Categories and items"
+    And I follow "Gradebook setup"
     And I set the following settings for grade item "Test outcome item one":
      | Extra credit     | 1   |
     And I log out
@@ -272,7 +272,7 @@ Feature: We can use calculated grade totals
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I expand "Setup" node
-    And I follow "Categories and items"
+    And I follow "Gradebook setup"
     And I set the following settings for grade item "Course 1":
       | Aggregation                     | Natural |
       | Include outcomes in aggregation | 0       |
@@ -297,7 +297,7 @@ Feature: We can use calculated grade totals
       | Test outcome item one | C1     | OT1     | Scale     | Test Scale |
     And I navigate to "Grades" node in "Course administration"
     And I expand "Setup" node
-    And I follow "Categories and items"
+    And I follow "Gradebook setup"
     And I set the following settings for grade item "Course 1":
       | Aggregation                     | Natural |
       | Include outcomes in aggregation | 1       |
@@ -387,7 +387,7 @@ Feature: We can use calculated grade totals
     And I set the following settings for grade item "Course 1":
       | Aggregation          | Natural |
       | Exclude empty grades | 0       |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I press "Add category"
     And I set the following fields to these values:
       | Category name | Sub category 3 |
@@ -441,7 +441,7 @@ Feature: We can use calculated grade totals
     And I press "Save changes"
     And I turn editing mode off
     And I should see "250.00 (25.25 %)" in the ".course" "css_element"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I press "Add category"
     And I set the following fields to these values:
       | Category name | Sub sub category 1 |
@@ -452,7 +452,7 @@ Feature: We can use calculated grade totals
 
   @javascript
   Scenario: Natural aggregation from the setup screen
-    And I select "Categories and items" from the "Grade report" singleselect
+    And I select "Gradebook setup" from the "Grade report" singleselect
     And I set the following settings for grade item "Course 1":
       | Aggregation          | Natural |
     And I set the following settings for grade item "Sub category 1":
@@ -514,7 +514,7 @@ Feature: We can use calculated grade totals
       | Aggregation          | Natural |
       | Exclude empty grades | 0       |
     And I turn editing mode off
-    And I select "Categories and items" from the "Grade report" singleselect
+    And I select "Gradebook setup" from the "Grade report" singleselect
     And I set the field "Override weight of Test assignment one" to "1"
     And I set the field "Weight of Test assignment one" to "0"
     And I set the field "Override weight of Test assignment six" to "1"
index 7453a73..fd792e0 100644 (file)
@@ -336,7 +336,7 @@ Feature: Changing the aggregation of an item affects its weight and extra credit
   @javascript
   Scenario: Switching grade items between categories
     # Move to same aggregation (Natural).
-    Given I navigate to "Categories and items" node in "Grade administration > Setup"
+    Given I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Select Item a1" to "1"
     And I set the field "Select Item a2" to "1"
     And I set the field "Select Item a3" to "1"
@@ -362,7 +362,7 @@ Feature: Changing the aggregation of an item affects its weight and extra credit
     And the field "Extra credit" matches value "1"
     And I press "Cancel"
     # Move to Mean of grades (with extra credit).
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Select Item a1" to "1"
     And I set the field "Select Item a2" to "1"
     And I set the field "Select Item a3" to "1"
@@ -382,7 +382,7 @@ Feature: Changing the aggregation of an item affects its weight and extra credit
     And the field "Extra credit" matches value "1"
     And I press "Cancel"
     # Move to Simple weight mean of grades.
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Select Item a1" to "1"
     And I set the field "Select Item a2" to "1"
     And I set the field "Select Item a3" to "1"
@@ -402,7 +402,7 @@ Feature: Changing the aggregation of an item affects its weight and extra credit
     And the field "Extra credit" matches value "1"
     And I press "Cancel"
     # Move to Weighted mean of grades.
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Select Item a1" to "1"
     And I set the field "Select Item a2" to "1"
     And I set the field "Select Item a3" to "1"
@@ -426,7 +426,7 @@ Feature: Changing the aggregation of an item affects its weight and extra credit
     And I set the field "Item weight" to "11"
     And I press "Save changes"
     # Move to same (Weighted mean of grades).
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Select Item a1" to "1"
     And I set the field "Select Item a2" to "1"
     And I set the field "Select Item a3" to "1"
@@ -447,7 +447,7 @@ Feature: Changing the aggregation of an item affects its weight and extra credit
     And the field "Item weight" matches value "11"
     And I press "Save changes"
     # Move back to Natural.
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Select Item a1" to "1"
     And I set the field "Select Item a2" to "1"
     And I set the field "Select Item a3" to "1"
index 1899e4d..e52ab6a 100644 (file)
@@ -30,7 +30,7 @@ Feature: Average grades are displayed in the gradebook
       | Show average | Show |
     And I press "Save changes"
     # Add a manual grade item
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I press "Add grade item"
     And I set the following fields to these values:
       | Item name | Manual item 1 |
index e295c02..145a39e 100644 (file)
@@ -2,7 +2,7 @@
 Feature: Calculated grade items can be used in the gradebook
   In order to use calculated grade items in the gradebook
   As a teacher
-  I need setup calculated grade items in the categories and items page.
+  I need setup calculated grade items in the 'Gradebook setup' page.
 
   Background:
     Given the following "courses" exist:
@@ -22,7 +22,7 @@ Feature: Calculated grade items can be used in the gradebook
     And I am on site homepage
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
 
   @javascript
   Scenario: The max grade for a category item, with a calculation using Natural aggregation, can be changed
@@ -77,7 +77,7 @@ Feature: Calculated grade items can be used in the gradebook
       | grade item 1                        | -                 | 75.00  | 0–100 | 75.00 %    | -                            |
       | Calc cat totalInclude empty grades. | 100.00 %          | 37.50  | 0–50  | 75.00 %    | -                            |
       | Course total                        | -                 | 37.50  | 0–50  | 75.00 %    | -                            |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "Calc cat":
       | Maximum grade | 40 |
     And I follow "Grader report"
@@ -143,7 +143,7 @@ Feature: Calculated grade items can be used in the gradebook
       | grade item 1 | 66.67 %           | 75.00  | 0–100 | 75.00 %    | 50.00 %                      |
       | calc item    | 33.33 %           | 37.50  | 0–50  | 75.00 %    | 25.00 %                      |
       | Course total | -                 | 112.50 | 0–150 | 75.00 %    | -                            |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "calc item":
       | Maximum grade | 40 |
     And I follow "Grader report"
index 62d5e3e..cd8d791 100644 (file)
@@ -23,7 +23,7 @@ Feature: Gradebook calculations for calculated grade items before the fix 201506
     And I am on site homepage
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
 
   @javascript
   Scenario: The max grade for a category item, with a calculation using Natural aggregation, can be changed
@@ -78,7 +78,7 @@ Feature: Gradebook calculations for calculated grade items before the fix 201506
       | grade item 1                        | -                 | 75.00  | 0–100 | 75.00 %    | -                            |
       | Calc cat totalInclude empty grades. | 100.00 %          | 37.50  | 0–100 | 37.50 %    | -                            |
       | Course total                        | -                 | 37.50  | 0–100 | 37.50 %    | -                            |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "Calc cat":
       | Maximum grade | 40 |
     And I follow "Grader report"
@@ -144,7 +144,7 @@ Feature: Gradebook calculations for calculated grade items before the fix 201506
       | grade item 1 | 50.00 %           | 75.00  | 0–100 | 75.00 %    | 37.50 %                      |
       | calc item    | 50.00 %           | 37.50  | 0–100 | 37.50 %    | 18.75 %                      |
       | Course total | -                 | 112.50 | 0–200 | 56.25 %    | -                            |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "calc item":
       | Maximum grade | 40 |
     And I follow "Grader report"
index e914de5..e899835 100644 (file)
@@ -43,7 +43,7 @@ Feature: We can understand the gradebook user report
     And I set the field "Show weightings" to "Show"
     And I set the field "Show contribution to course total" to "Show"
     And I press "Save changes"
-    And I set the field "Grade report" to "Categories and items"
+    And I set the field "Grade report" to "Gradebook setup"
     And I press "Add category"
     And I set the field "Category name" to "Sub category"
     And I press "Save changes"
index 896c567..7f60a15 100644 (file)
@@ -22,7 +22,7 @@ Feature: Extra credit contributions are normalised when going out of bounds
     And I am on site homepage
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I press "Add grade item"
     And I set the following fields to these values:
       | Item name | Manual item 1 |
@@ -56,7 +56,7 @@ Feature: Extra credit contributions are normalised when going out of bounds
     And I press "Save changes"
 
   Scenario Outline: The contribution of extra credit items is normalised
-    Given I set the field "Grade report" to "Categories and items"
+    Given I set the field "Grade report" to "Gradebook setup"
     When I set the following settings for grade item "Course 1":
       | Aggregation | <aggregation> |
     And I set the following settings for grade item "Manual item 2":
index 919b745..a886c39 100644 (file)
@@ -28,7 +28,7 @@ Feature: We can use a minimum grade different than zero
     And I am on site homepage
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I press "Add grade item"
     And I set the following fields to these values:
       | Item name | Manual item 1 |
@@ -73,7 +73,7 @@ Feature: We can use a minimum grade different than zero
 
   @javascript
   Scenario: Natural aggregation with negative and positive grade
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "Sub category 1":
       | Aggregation          | Natural |
       | Exclude empty grades | 0       |
index b956a5a..cb1aa15 100644 (file)
@@ -27,7 +27,7 @@ Feature: We can choose what min or max grade to use when aggregating grades.
     And I am on site homepage
     And I follow "C1"
     And I navigate to "Grades" node in "Course administration"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I press "Add grade item"
     And I set the following fields to these values:
       | Item name | MI 1 |
@@ -62,7 +62,7 @@ Feature: We can choose what min or max grade to use when aggregating grades.
     And I set the field "Show weightings" to "Show"
     And I set the field "Show contribution to course total" to "Show"
     And I press "Save changes"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "CAT1":
       | Aggregation          | Natural |
     And I log out
@@ -98,7 +98,7 @@ Feature: We can choose what min or max grade to use when aggregating grades.
       | MI 5         | 33.33 %           | 30.00  | 0–100 | 30.00 %    | 10.00 %                      |
       | CAT1 total   | 33.33 %           | 10.00  | 0–100 | 10.00 %    | -                            |
       | Course total | -                 | 60.00  | 0–300 | 20.00 %    | -                            |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "MI 1":
       | Maximum grade          | 50.00 |
       | Minimum grade          | 5.00 |
@@ -126,7 +126,7 @@ Feature: We can choose what min or max grade to use when aggregating grades.
       | MI 5         | 50.00 %           | 30.00  | 0–100 | 30.00 %    | 15.00 %                      |
       | CAT1 total   | 25.00 %           | 10.00  | 0–50  | 20.00 %    | -                            |
       | Course total | -                 | 60.00  | 0–200 | 30.00 %    | -                            |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "MI 5":
       | Maximum grade          | 200.00 |
     And I follow "User report"
index de2f83a..22c35aa 100644 (file)
@@ -26,7 +26,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I log in as "teacher1"
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I set the field "Grade report" to "Categories and items"
+    And I set the field "Grade report" to "Gradebook setup"
     And I set the following settings for grade item "Test assignment four (extra)":
       | Extra credit | 1 |
     And I set the following settings for grade item "Test assignment five (extra)":
index b4b97cf..e3722f3 100644 (file)
@@ -27,7 +27,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I log in as "teacher1"
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I set the field "Grade report" to "Categories and items"
+    And I set the field "Grade report" to "Gradebook setup"
     And I set the following settings for grade item "Test assignment four (extra)":
       | Extra credit | 1 |
     And I set the following settings for grade item "Test assignment five (extra)":
index bd7ea43..6f8a16b 100644 (file)
@@ -33,7 +33,7 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
     And I log in as "teacher1"
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I set the field "Grade report" to "Categories and items"
+    And I set the field "Grade report" to "Gradebook setup"
 
   @javascript
   Scenario: Setting all weights in a category to exactly one hundred in total.
index b46f33f..6b488e1 100644 (file)
@@ -34,7 +34,7 @@ Feature: Gradebook calculations for natural weights normalisation before the fix
     And I log in as "teacher1"
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I set the field "Grade report" to "Categories and items"
+    And I set the field "Grade report" to "Gradebook setup"
 
   @javascript
   Scenario: Grade items weights are normalised when all grade item weights are overridden (sum exactly 100). Extra credit is set to zero (before the fix 20150619).
index 5cd48b0..ed42b97 100644 (file)
@@ -90,7 +90,7 @@ Feature: View gradebook when scales are used
       | Test assignment one | C     | F–A   | 50.00 %    | 60.00 %                      |
       | Sub category 1 total      | 3.00  | 0–5   | 60.00 %    | -                            |
       | Course total        | 3.00  | 0–5   | 60.00 %    | -                            |
-    And I select "Categories and items" from the "Grade report" singleselect
+    And I select "Gradebook setup" from the "Grade report" singleselect
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                | Max grade |
       | Test assignment one | 5.00      |
@@ -136,7 +136,7 @@ Feature: View gradebook when scales are used
       | Test assignment one          | C              | F–A   | 50.00 %       | <contrib3>                   |
       | Sub category (<aggregation>) total<aggregation>. | 3.00           | 1–5   | 50.00 %       | -                            |
       | Course total<aggregation>.   | <coursetotal3> | 0–100 | <courseperc3> | -                            |
-    And I select "Categories and items" from the "Grade report" singleselect
+    And I select "Gradebook setup" from the "Grade report" singleselect
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                | Max grade |
       | Test assignment one | A (5)     |
index b71b035..2fec297 100644 (file)
@@ -90,7 +90,7 @@ Feature: Control the aggregation of the scales
     And I turn editing mode on
     When I set the following settings for grade item "Course 1":
       | Aggregation | Natural |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Override weight of Grade me" to "1"
     Then the field "Override weight of Grade me" matches value "100.00"
     And I click on "Edit" "link" in the "Scale me" "table_row"
@@ -102,7 +102,7 @@ Feature: Control the aggregation of the scales
       | grade_includescalesinaggregation | 1 |
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Override weight of Grade me" to "1"
     And the field "Override weight of Grade me" matches value "95.238"
     And I set the field "Override weight of Scale me" to "1"
index 867c70c..190e1f7 100644 (file)
@@ -79,7 +79,7 @@ Feature: View gradebook when single item scales are used
       | Test assignment one | -     | Ace!–Ace! | -                            |
       | Sub category 1 total| -     | 0–1       | -                            |
       | Course total        | -     | 0–1       | -                            |
-    And I set the field "jump" to "Categories and items"
+    And I set the field "jump" to "Gradebook setup"
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                | Max grade |
       | Test assignment one | 1.00      |
@@ -115,7 +115,7 @@ Feature: View gradebook when single item scales are used
       | Test assignment one                              | Ace!           | Ace!–Ace!   | <contrib1>                   |
       | Sub category (<aggregation>) total<aggregation>. | <cattotal1>    | 0–100       | -                            |
       | Course total<aggregation>.                       | <coursetotal1> | 0–100       | -                            |
-    And I set the field "jump" to "Categories and items"
+    And I set the field "jump" to "Gradebook setup"
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                         | Max grade |
       | Test assignment one          | Ace! (1)  |
index 9aa8684..2632fed 100644 (file)
@@ -80,7 +80,7 @@ Feature: We can enter in grades and view reports from the gradebook
     And "Course 1" row "Grade" column of "overview-grade" table should not contain "90.00"
 
   Scenario: We can add a weighting to a grade item and it is displayed properly in the user report
-    When I select "Categories and items" from the "Grade report" singleselect
+    When I select "Gradebook setup" from the "Grade report" singleselect
     And I set the following settings for grade item "Course 1":
       | Aggregation | Weighted mean of grades |
     And I set the field "Extra credit value for Test assignment name" to "0.72"
index aa51b27..cb96fa0 100644 (file)
@@ -30,7 +30,7 @@ require_once($CFG->dirroot.'/grade/edit/tree/lib.php');
 
 
 /**
- * Tests grade_edit_tree (deals with the data on the categories and items page in the gradebook)
+ * Tests grade_edit_tree (deals with the data on the 'Gradebook setup' page in the gradebook)
  */
 class core_grade_edittreelib_testcase extends advanced_testcase {
     public function test_format_number() {
index 1f9b739..9c44f0a 100644 (file)
@@ -1254,7 +1254,8 @@ class core_group_external extends external_api {
                 list($group->description, $group->descriptionformat) =
                     external_format_text($group->description, $group->descriptionformat,
                             $context->id, 'group', 'description', $group->id);
-                $usergroups[] = (array)$group;
+                $group->courseid = $course->id;
+                $usergroups[] = $group;
             }
         }
 
@@ -1274,17 +1275,201 @@ class core_group_external extends external_api {
     public static function get_course_user_groups_returns() {
         return new external_single_structure(
             array(
-                'groups' => new external_multiple_structure(
-                    new external_single_structure(
-                        array(
-                            'id' => new external_value(PARAM_INT, 'group record id'),
-                            'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
-                            'description' => new external_value(PARAM_RAW, 'group description text'),
-                            'descriptionformat' => new external_format_value('description'),
-                            'idnumber' => new external_value(PARAM_RAW, 'id number')
-                        )
-                    )
-                ),
+                'groups' => new external_multiple_structure(self::group_description()),
+                'warnings' => new external_warnings(),
+            )
+        );
+    }
+
+    /**
+     * Create group return value description.
+     *
+     * @return external_single_structure The group description
+     */
+    public static function group_description() {
+        return new external_single_structure(
+            array(
+                'id' => new external_value(PARAM_INT, 'group record id'),
+                'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
+                'description' => new external_value(PARAM_RAW, 'group description text'),
+                'descriptionformat' => new external_format_value('description'),
+                'idnumber' => new external_value(PARAM_RAW, 'id number'),
+                'courseid' => new external_value(PARAM_INT, 'course id', VALUE_OPTIONAL),
+            )
+        );
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.0
+     */
+    public static function get_activity_allowed_groups_parameters() {
+        return new external_function_parameters(
+            array(
+                'cmid' => new external_value(PARAM_INT, 'course module id'),
+                'userid' => new external_value(PARAM_INT, 'id of user, empty for current user', VALUE_OPTIONAL, 0)
+            )
+        );
+    }
+
+    /**
+     * Gets a list of groups that the user is allowed to access within the specified activity.
+     *
+     * @throws moodle_exception
+     * @param int $cmid course module id
+     * @param int $userid id of user.
+     * @return array of group objects (id, name, description, format) and possible warnings.
+     * @since Moodle 3.0
+     */
+    public static function get_activity_allowed_groups($cmid, $userid = 0) {
+        global $USER;
+
+        // Warnings array, it can be empty at the end but is mandatory.
+        $warnings = array();
+
+        $params = array(
+            'cmid' => $cmid,
+            'userid' => $userid
+        );
+        $params = self::validate_parameters(self::get_activity_allowed_groups_parameters(), $params);
+        $cmid = $params['cmid'];
+        $userid = $params['userid'];
+
+        $cm = get_coursemodule_from_id(null, $cmid, 0, false, MUST_EXIST);
+
+        // Security checks.
+        $context = context_module::instance($cm->id);
+        $coursecontext = context_course::instance($cm->course);
+        self::validate_context($context);
+
+        if (empty($userid)) {
+            $userid = $USER->id;
+        }
+
+        $user = core_user::get_user($userid, 'id, deleted', MUST_EXIST);
+        if ($user->deleted) {
+            throw new moodle_exception('userdeleted');
+        }
+        if (isguestuser($user)) {
+            throw new moodle_exception('invaliduserid');
+        }
+
+         // Check if we have permissions for retrieve the information.
+        if ($user->id != $USER->id) {
+            if (!has_capability('moodle/course:managegroups', $context)) {
+                throw new moodle_exception('accessdenied', 'admin');
+            }
+
+            // Validate if the user is enrolled in the course.
+            if (!is_enrolled($coursecontext, $user->id)) {
+                // We return a warning because the function does not fail for not enrolled users.
+                $warning = array();
+                $warning['item'] = 'course';
+                $warning['itemid'] = $cm->course;
+                $warning['warningcode'] = '1';
+                $warning['message'] = "User $user->id is not enrolled in course $cm->course";
+                $warnings[] = $warning;
+            }
+        }
+
+        $usergroups = array();
+        if (empty($warnings)) {
+            $groups = groups_get_activity_allowed_groups($cm, $user->id);
+
+            foreach ($groups as $group) {
+                list($group->description, $group->descriptionformat) =
+                    external_format_text($group->description, $group->descriptionformat,
+                            $coursecontext->id, 'group', 'description', $group->id);
+                $group->courseid = $cm->course;
+                $usergroups[] = $group;
+            }
+        }
+
+        $results = array(
+            'groups' => $usergroups,
+            'warnings' => $warnings
+        );
+        return $results;
+    }
+
+    /**
+     * Returns description of method result value.
+     *
+     * @return external_description A single structure containing groups and possible warnings.
+     * @since Moodle 3.0
+     */
+    public static function get_activity_allowed_groups_returns() {
+        return new external_single_structure(
+            array(
+                'groups' => new external_multiple_structure(self::group_description()),
+                'warnings' => new external_warnings(),
+            )
+        );
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.0
+     */
+    public static function get_activity_groupmode_parameters() {
+        return new external_function_parameters(
+            array(
+                'cmid' => new external_value(PARAM_INT, 'course module id')
+            )
+        );
+    }
+
+    /**
+     * Returns effective groupmode used in a given activity.
+     *
+     * @throws moodle_exception
+     * @param int $cmid course module id.
+     * @return array containing the group mode and possible warnings.
+     * @since Moodle 3.0
+     * @throws moodle_exception
+     */
+    public static function get_activity_groupmode($cmid) {
+        global $USER;
+
+        // Warnings array, it can be empty at the end but is mandatory.
+        $warnings = array();
+
+        $params = array(
+            'cmid' => $cmid
+        );
+        $params = self::validate_parameters(self::get_activity_groupmode_parameters(), $params);
+        $cmid = $params['cmid'];
+
+        $cm = get_coursemodule_from_id(null, $cmid, 0, false, MUST_EXIST);
+
+        // Security checks.
+        $context = context_module::instance($cm->id);
+        self::validate_context($context);
+
+        $groupmode = groups_get_activity_groupmode($cm);
+
+        $results = array(
+            'groupmode' => $groupmode,
+            'warnings' => $warnings
+        );
+        return $results;
+    }
+
+    /**
+     * Returns description of method result value.
+     *
+     * @return external_description
+     * @since Moodle 3.0
+     */
+    public static function get_activity_groupmode_returns() {
+        return new external_single_structure(
+            array(
+                'groupmode' => new external_value(PARAM_INT, 'group mode:
+                                                    0 for no groups, 1 for separate groups, 2 for visible groups'),
                 'warnings' => new external_warnings(),
             )
         );
index 7dc9c65..5474379 100644 (file)
@@ -453,4 +453,140 @@ class core_group_externallib_testcase extends externallib_advanced_testcase {
         $this->assertCount(1, $groups['warnings']);
 
     }
+
+    /**
+     * Test get_activity_allowed_groups
+     */
+    public function test_get_activity_allowed_groups() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $generator = self::getDataGenerator();
+
+        $student = $generator->create_user();
+        $otherstudent = $generator->create_user();
+        $teacher = $generator->create_user();
+        $course = $generator->create_course();
+        $othercourse = $generator->create_course();
+
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
+        $generator->enrol_user($student->id, $course->id, $studentrole->id);
+        $generator->enrol_user($otherstudent->id, $othercourse->id, $studentrole->id);
+        $generator->enrol_user($teacher->id, $course->id, $teacherrole->id);
+
+        $forum1 = $generator->create_module("forum", array('course' => $course->id), array('groupmode' => VISIBLEGROUPS));
+        $forum2 = $generator->create_module("forum", array('course' => $othercourse->id));
+        $forum3 = $generator->create_module("forum", array('course' => $course->id), array('visible' => 0));
+
+        // Request data for tests.
+        $cm1 = get_coursemodule_from_instance("forum", $forum1->id);
+        $cm2 = get_coursemodule_from_instance("forum", $forum2->id);
+        $cm3 = get_coursemodule_from_instance("forum", $forum3->id);
+
+        $group1data = array();
+        $group1data['courseid'] = $course->id;
+        $group1data['name'] = 'Group Test 1';
+        $group1data['description'] = 'Group Test 1 description';
+        $group1data['idnumber'] = 'TEST1';
+        $group2data = array();
+        $group2data['courseid'] = $course->id;
+        $group2data['name'] = 'Group Test 2';
+        $group2data['description'] = 'Group Test 2 description';
+        $group2data['idnumber'] = 'TEST2';
+        $group1 = $generator->create_group($group1data);
+        $group2 = $generator->create_group($group2data);
+
+        groups_add_member($group1->id, $student->id);
+        groups_add_member($group2->id, $student->id);
+
+        $this->setUser($student);
+
+        // First try possible errors.
+        try {
+            $data = core_group_external::get_activity_allowed_groups($cm2->id);
+        } catch (moodle_exception $e) {
+            $this->assertEquals('requireloginerror', $e->errorcode);
+        }
+
+        try {
+            $data = core_group_external::get_activity_allowed_groups($cm3->id);
+        } catch (moodle_exception $e) {
+            $this->assertEquals('requireloginerror', $e->errorcode);
+        }
+
+        // Retrieve my groups.
+        $groups = core_group_external::get_activity_allowed_groups($cm1->id);
+        $groups = external_api::clean_returnvalue(core_group_external::get_activity_allowed_groups_returns(), $groups);
+        $this->assertCount(2, $groups['groups']);
+
+        foreach ($groups['groups'] as $group) {
+            if ($group['name'] == $group1data['name']) {
+                $this->assertEquals($group1data['description'], $group['description']);
+                $this->assertEquals($group1data['idnumber'], $group['idnumber']);
+            } else {
+                $this->assertEquals($group2data['description'], $group['description']);
+                $this->assertEquals($group2data['idnumber'], $group['idnumber']);
+            }
+        }
+
+        $this->setUser($teacher);
+        // Retrieve other users groups.
+        $groups = core_group_external::get_activity_allowed_groups($cm1->id, $student->id);
+        $groups = external_api::clean_returnvalue(core_group_external::get_activity_allowed_groups_returns(), $groups);
+        $this->assertCount(2, $groups['groups']);
+
+        // Check warnings. Trying to get groups for a user not enrolled in course.
+        $groups = core_group_external::get_activity_allowed_groups($cm1->id, $otherstudent->id);
+        $groups = external_api::clean_returnvalue(core_group_external::get_activity_allowed_groups_returns(), $groups);
+        $this->assertCount(1, $groups['warnings']);
+
+    }
+
+    /**
+     * Test get_activity_groupmode
+     */
+    public function test_get_activity_groupmode() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $generator = self::getDataGenerator();
+
+        $student = $generator->create_user();
+        $course = $generator->create_course();
+        $othercourse = $generator->create_course();
+
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $generator->enrol_user($student->id, $course->id, $studentrole->id);
+
+        $forum1 = $generator->create_module("forum", array('course' => $course->id), array('groupmode' => VISIBLEGROUPS));
+        $forum2 = $generator->create_module("forum", array('course' => $othercourse->id));
+        $forum3 = $generator->create_module("forum", array('course' => $course->id), array('visible' => 0));
+
+        // Request data for tests.
+        $cm1 = get_coursemodule_from_instance("forum", $forum1->id);
+        $cm2 = get_coursemodule_from_instance("forum", $forum2->id);
+        $cm3 = get_coursemodule_from_instance("forum", $forum3->id);
+
+        $this->setUser($student);
+
+        $data = core_group_external::get_activity_groupmode($cm1->id);
+        $data = external_api::clean_returnvalue(core_group_external::get_activity_groupmode_returns(), $data);
+        $this->assertEquals(VISIBLEGROUPS, $data['groupmode']);
+
+        try {
+            $data = core_group_external::get_activity_groupmode($cm2->id);
+        } catch (moodle_exception $e) {
+            $this->assertEquals('requireloginerror', $e->errorcode);
+        }
+
+        try {
+            $data = core_group_external::get_activity_groupmode($cm3->id);
+        } catch (moodle_exception $e) {
+            $this->assertEquals('requireloginerror', $e->errorcode);
+        }
+
+    }
 }
diff --git a/install/lang/xct/langconfig.php b/install/lang/xct/langconfig.php
new file mode 100644 (file)
index 0000000..5cc0b59
--- /dev/null
@@ -0,0 +1,33 @@
+<?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/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['thislanguage'] = 'བོད་ཡིག';
index f1ba2de..a37b84a 100644 (file)
@@ -68,6 +68,7 @@ $string['courseblogdisable'] = 'Course blogs are not enabled';
 $string['courseblogs'] = 'Users can only see blogs for people who share a course';
 $string['deleteblogassociations'] = 'Delete blog associations';
 $string['deleteblogassociations_help'] = 'If ticked then blog entries will no longer be associated with this course or any course activities or resources.  The blog entries themselves will not be deleted.';
+$string['deleteentry'] = 'Delete entry';
 $string['deleteexternalblog'] = 'Unregister this external blog';
 $string['deleteotagswarn'] = 'Are you sure you want to remove these tags from all blog posts and remove it from the system?';
 $string['description'] = 'Description';
index f1de91d..ab59bdb 100644 (file)
@@ -19,3 +19,4 @@ thistaghasnodesc,core_tag
 updated,core_tag
 withselectedtags,core_tag
 tag:create,core_role
+categoriesanditems,core_grades
index 1840aa1..da10569 100644 (file)
@@ -66,7 +66,7 @@ $string['aggregationhintexcluded'] = '( Excluded )';
 $string['aggregationhintextra'] = '( Extra credit )';
 $string['aggregation_link'] = 'grade/aggregation';
 $string['aggregationcoef'] = 'Aggregation coefficient';
-$string['aggregationcoefextra'] = 'Extra credit'; // for the header of the table at Edit categories and items page
+$string['aggregationcoefextra'] = 'Extra credit'; // For the header of the table on the 'Gradebook setup' page.
 $string['aggregationcoefextra_help'] = 'If the aggregation is \'Natural\' or \'Simple weighted mean\' and the extra credit checkbox is ticked, the grade item\'s maximum grade is not added to the category\'s maximum grade. This will result in the possibility of achieving the maximum grade in the category without having the maximum grade in all the grade items. If the site administrator has enabled grades over the maximum, there might be grades over the maximum.
 
 If the aggregation is \'Mean of grades (with extra credits)\' and the extra credit is set to a value greater than zero, the extra credit is the factor by which the grade is multiplied before adding it to the total after the computation of the mean.';
@@ -112,7 +112,6 @@ $string['calculationsaved'] = 'Calculation saved';
 $string['calculationview'] = 'View calculation';
 $string['cannotaccessgroup'] = 'Can not access grades of selected group, sorry.';
 $string['categories'] = 'Categories';
-$string['categoriesanditems'] = 'Categories and items';
 $string['category'] = 'Category';
 $string['categoryedit'] = 'Edit category';
 $string['categoryname'] = 'Category name';
@@ -244,6 +243,7 @@ $string['gradebookcalculationsfixbutton'] = 'Accept grade changes and fix calcul
 $string['gradebookcalculationswarning'] = 'Note: Some errors have been detected in calculating the grades displayed in the gradebook. It is recommended that the errors are fixed by clicking the button below, though this will result in some grades being changed. For details, see the changes between versions {$a->gradebookversion} and {$a->currentversion} in <a href="{$a->url}">Gradebook calculation changes</a>.';
 $string['gradebookhiddenerror'] = 'The gradebook is currently set to hide everything from students.';
 $string['gradebookhistories'] = 'Grade histories';
+$string['gradebooksetup'] = 'Gradebook setup';
 $string['gradeboundary'] = 'Letter grade boundary';
 $string['gradeboundary_help'] = 'This setting determines the minimum percentage over which grades will be assigned the grade letter.';
 $string['gradecategories'] = 'Grade categories';
@@ -789,3 +789,6 @@ $string['writinggradebookinfo'] = 'Writing gradebook settings';
 $string['xml'] = 'XML';
 $string['yes'] = 'Yes';
 $string['yourgrade'] = 'Your grade';
+
+// Deprecated since 3.0.
+$string['categoriesanditems'] = 'Categories and items';
index 2acbcfa..a618353 100644 (file)
@@ -60,6 +60,7 @@ $string['eventmessagecontactadded'] = 'Message contact added';
 $string['eventmessagecontactblocked'] = 'Message contact blocked';
 $string['eventmessagecontactremoved'] = 'Message contact removed';
 $string['eventmessagecontactunblocked'] = 'Message contact unblocked';
+$string['eventmessagedeleted'] = 'Message deleted';
 $string['eventmessageviewed'] = 'Message viewed';
 $string['eventmessagesent'] = 'Message sent';
 $string['forced'] = 'Forced';
index b1c8f86..ea1e734 100644 (file)
@@ -30,7 +30,11 @@ $string['pinblocksexplan'] = 'Any block settings you configure here will be visi
 $string['defaultpage'] = 'Default My Moodle page';
 $string['defaultprofilepage'] = 'Default profile page';
 $string['addpage'] = 'Add page';
+$string['alldashboardswerereset'] = 'All Dashboard pages have been reset to default.';
+$string['allprofileswerereset'] = 'All profile pages have been reset to default.';
 $string['delpage'] = 'Delete page';
 $string['managepages'] = 'Manage pages';
+$string['reseteveryonesdashboard'] = 'Reset Dashboard for all users';
+$string['reseteveryonesprofile'] = 'Reset profile for all users';
 $string['resetpage'] = 'Reset page to default';
 $string['reseterror'] = 'There was an error resetting your page';
index 36079b6..82d7588 100644 (file)
@@ -117,6 +117,7 @@ $string['iprestriction'] = 'IP restriction';
 $string['iprestriction_help'] = 'The user will need to call the web service from the listed IPs (separated by commas).';
 $string['key'] = 'Key';
 $string['keyshelp'] = 'The keys are used to access your Moodle account from external applications.';
+$string['loginrequired'] = 'Restricted to logged in users';
 $string['manageprotocols'] = 'Manage protocols';
 $string['managetokens'] = 'Manage tokens';
 $string['missingcaps'] = 'Missing capabilities';
index 3563fde..debcc5f 100644 (file)
@@ -4001,10 +4001,15 @@ class admin_setting_sitesettext extends admin_setting_configtext {
      * @return mixed true or message string
      */
     public function validate($data) {
+        global $DB, $SITE;
         $cleaned = clean_param($data, PARAM_TEXT);
         if ($cleaned === '') {
             return get_string('required');
         }
+        if ($this->name ==='shortname' &&
+                $DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data, $SITE->id))) {
+            return get_string('shortnametaken', 'error', $data);
+        }
         if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
             return true;
         } else {
diff --git a/lib/ajax/service-nologin.php b/lib/ajax/service-nologin.php
new file mode 100644 (file)
index 0000000..7120ade
--- /dev/null
@@ -0,0 +1,31 @@
+<?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 is used to call any registered externallib function in Moodle.
+ *
+ * It will process more than one request and return more than one response if required.
+ * It is recommended to add webservice functions and re-use this script instead of
+ * writing any new custom ajax scripts.
+ *
+ * @since Moodle 2.9
+ * @package core
+ * @copyright 2015 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('NO_MOODLE_COOKIES', true);
+require_once('service.php');
index 938ffa8..ce58f01 100644 (file)
@@ -32,9 +32,6 @@ define('AJAX_SCRIPT', true);
 require_once(dirname(__FILE__) . '/../../config.php');
 require_once($CFG->libdir . '/externallib.php');
 
-require_login(null, true, null, true, true);
-require_sesskey();
-
 $rawjson = file_get_contents('php://input');
 
 $requests = json_decode($rawjson, true);
@@ -55,9 +52,25 @@ foreach ($requests as $request) {
         $externalfunctioninfo = external_function_info($methodname);
 
         if (!$externalfunctioninfo->allowed_from_ajax) {
+            error_log('This external function is not available to ajax. Failed to call "' . $methodname . '"');
             throw new moodle_exception('servicenotavailable', 'webservice');
         }
 
+        // Do not allow access to write or delete webservices as a public user.
+        if ($externalfunctioninfo->loginrequired) {
+            if (defined('NO_MOODLE_COOKIES') && NO_MOODLE_COOKIES) {
+                error_log('Set "loginrequired" to false in db/service.php when calling entry point service-nologin.php. ' .
+                          'Failed to call "' . $methodname . '"');
+                throw new moodle_exception('servicenotavailable', 'webservice');
+            }
+            if (!isloggedin()) {
+                error_log('This external function is not available to public users. Failed to call "' . $methodname . '"');
+                throw new moodle_exception('servicenotavailable', 'webservice');
+            } else {
+                require_sesskey();
+            }
+        }
+
         // Validate params, this also sorts the params properly, we need the correct order in the next part.
         $callable = array($externalfunctioninfo->classname, 'validate_parameters');
         $params = call_user_func($callable,
index 2a80dfe..c7c80e1 100644 (file)
Binary files a/lib/amd/build/ajax.min.js and b/lib/amd/build/ajax.min.js differ
diff --git a/lib/amd/build/event.min.js b/lib/amd/build/event.min.js
new file mode 100644 (file)
index 0000000..86a6407
Binary files /dev/null and b/lib/amd/build/event.min.js differ
index 9dc3a61..7d7c42d 100644 (file)
Binary files a/lib/amd/build/first.min.js and b/lib/amd/build/first.min.js differ
index ace0e93..33d26c9 100644 (file)
Binary files a/lib/amd/build/str.min.js and b/lib/amd/build/str.min.js differ
index ed4aa32..71f74a7 100644 (file)
Binary files a/lib/amd/build/templates.min.js and b/lib/amd/build/templates.min.js differ
index 2966cb4..38a393b 100644 (file)
@@ -104,13 +104,20 @@ define(['jquery', 'core/config'], function($, config) {
          *                   can be attached to the promises returned by this function.
          * @param {Boolean} async Optional, defaults to true.
          *                  If false - this function will not return until the promises are resolved.
+         * @param {Boolean} loginrequired Optional, defaults to true.
+         *                  If false - this function will call the faster nologin ajax script - but
+         *                  will fail unless all functions have been marked as 'loginrequired' => false
+         *                  in services.php
          * @return {Promise[]} Array of promises that will be resolved when the ajax call returns.
          */
-        call: function(requests, async) {
+        call: function(requests, async, loginrequired) {
             var ajaxRequestData = [],
                 i,
                 promises = [];
 
+            if (typeof loginrequired === "undefined") {
+                loginrequired = true;
+            }
             if (typeof async === "undefined") {
                 async = true;
             }
@@ -144,15 +151,20 @@ define(['jquery', 'core/config'], function($, config) {
                 async: async
             };
 
+            var script = config.wwwroot + '/lib/ajax/service.php?sesskey=' + config.sesskey;
+            if (!loginrequired) {
+                script = config.wwwroot + '/lib/ajax/service-nologin.php?sesskey=' + config.sesskey;
+            }
+
             // Jquery deprecated done and fail with async=false so we need to do this 2 ways.
             if (async) {
-                $.ajax(config.wwwroot + '/lib/ajax/service.php?sesskey=' + config.sesskey, settings)
+                $.ajax(script, settings)
                     .done(requestSuccess)
                     .fail(requestFail);
             } else {
                 settings.success = requestSuccess;
                 settings.error = requestFail;
-                $.ajax(config.wwwroot + '/lib/ajax/service.php?sesskey=' + config.sesskey, settings);
+                $.ajax(script, settings);
             }
 
             return promises;
diff --git a/lib/amd/src/event.js b/lib/amd/src/event.js
new file mode 100644 (file)
index 0000000..ce20865
--- /dev/null
@@ -0,0 +1,52 @@
+// 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/>.
+
+/**
+ * Global registry of core events that can be triggered/listened for.
+ *
+ * @module     core/event
+ * @package    core
+ * @class      event
+ * @copyright  2015 Damyon Wiese <damyon@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      3.0
+ */
+define([ 'jquery', 'core/yui' ],
+       function($, Y) {
+
+    return /** @alias module:core/event */ {
+        // Public variables and functions.
+        /**
+         * Trigger an event using both JQuery and YUI
+         *
+         * @method notifyFilterContentUpdated
+         * @param {string}|{JQuery} nodes - Selector or list of elements that were inserted.
+         */
+        notifyFilterContentUpdated: function(nodes) {
+            nodes = $(nodes);
+            Y.use('event', 'moodle-core-event', function(Y) {
+                // Trigger it the JQuery way.
+                $('document').trigger(M.core.event.FILTER_CONTENT_UPDATED, nodes);
+
+                // Create a YUI NodeList from our JQuery Object.
+                var yuiNodes = new Y.NodeList(nodes.get());
+
+                // And again for YUI.
+                Y.fire(M.core.event.FILTER_CONTENT_UPDATED, { nodes: yuiNodes });
+            });
+        },
+
+    };
+});
index 704c0c5..72646f0 100644 (file)
  * Because every module is returned from a request for any other module, this
  * forces the loading of all modules with a single request.
  *
+ * This function also sets up the listeners for ajax requests so we can tell
+ * if any requests are still in progress.
+ *
  * @module     core/first
  * @package    core
  * @copyright  2015 Damyon Wiese <damyon@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  * @since      2.9
  */
-define(function() { });
+define(['jquery'], function($) {
+    $(document).bind("ajaxStart", function(){
+        M.util.js_pending('jq');
+    }).bind("ajaxStop", function(){
+        M.util.js_complete('jq');
+    });
+});
index d66c164..76830d3 100644 (file)
@@ -140,7 +140,7 @@ define(['jquery', 'core/ajax', 'core/localstorage'], function($, ajax, storage)
                     });
                 }
 
-                var deferreds = ajax.call(ajaxrequests);
+                var deferreds = ajax.call(ajaxrequests, true, false);
                 $.when.apply(null, deferreds).done(
                     function() {
                         // Turn the list of arguments (unknown length) into a real array.
index 93ed7d1..6e4bc0c 100644 (file)
@@ -30,9 +30,10 @@ define([ 'core/mustache',
          'core/notification',
          'core/url',
          'core/config',
-         'core/localstorage'
+         'core/localstorage',
+         'core/event'
        ],
-       function(mustache, $, ajax, str, notification, coreurl, config, storage) {
+       function(mustache, $, ajax, str, notification, coreurl, config, storage, event) {
 
     // Private variables and functions.
 
@@ -310,7 +311,7 @@ define([ 'core/mustache',
                 template: name,
                 themename: currentThemeName
             }
-        }], async);
+        }], async, false);
 
         promises[0].done(
             function (templateSource) {
@@ -326,6 +327,50 @@ define([ 'core/mustache',
         return deferred.promise();
     };
 
+    /**
+     * Execute a block of JS returned from a template.
+     * Call this AFTER adding the template HTML into the DOM so the nodes can be found.
+     *
+     * @method runTemplateJS
+     * @param {string} source - A block of javascript.
+     */
+    var runTemplateJS = function(source) {
+        if (source.trim() !== '') {
+            var newscript = $('<script>').attr('type','text/javascript').html(source);
+            $('head').append(newscript);
+        }
+    };
+
+    /**
+     * Do some DOM replacement and trigger correct events and fire javascript.
+     *
+     * @method domReplace
+     * @private
+     * @param {JQuery} element - Element or selector to replace.
+     * @param {String} newHTML - HTML to insert / replace.
+     * @param {String} newJS - Javascript to run after the insertion.
+     * @param {Boolean} replaceChildNodes - Replace only the childnodes, alternative is to replace the entire node.
+     */
+    var domReplace = function(element, newHTML, newJS, replaceChildNodes) {
+        var replaceNode = $(element);
+        if (replaceNode.length) {
+            // First create the dom nodes so we have a reference to them.
+            var newNodes = $(newHTML);
+            // Do the replacement in the page.
+            if (replaceChildNodes) {
+                replaceNode.empty();
+                replaceNode.append(newNodes);
+            } else {
+                replaceNode.replaceWith(newNodes);
+            }
+            // Run any javascript associated with the new HTML.
+            runTemplateJS(newJS);
+            // Notify all filters about the new content.
+            event.notifyFilterContentUpdated(newNodes);
+        }
+    };
+
+
     return /** @alias module:core/templates */ {
         // Public variables and functions.
         /**
@@ -379,12 +424,28 @@ define([ 'core/mustache',
          * Call this AFTER adding the template HTML into the DOM so the nodes can be found.
          *
          * @method runTemplateJS
-         * @private
          * @param {string} source - A block of javascript.
          */
-        runTemplateJS: function(source) {
-            var newscript = $('<script>').attr('type','text/javascript').html(source);
-            $('head').append(newscript);
+        runTemplateJS: runTemplateJS,
+
+        /**
+         * Replace a node in the page with some new HTML and run the JS.
+         *
+         * @method replaceNodeContents
+         * @param {string} source - A block of javascript.
+         */
+        replaceNodeContents: function(element, newHTML, newJS) {
+            return domReplace(element, newHTML, newJS, true);
+        },
+
+        /**
+         * Insert a node in the page with some new HTML and run the JS.
+         *
+         * @method replaceNode
+         * @param {string} source - A block of javascript.
+         */
+        replaceNode: function(element, newHTML, newJS) {
+            return domReplace(element, newHTML, newJS, false);
         }
     };
 });
index 5f6ba12..9c17d27 100644 (file)
@@ -536,7 +536,7 @@ class auth_plugin_base {
     }
 
     /**
-     * Returns whether or not the captcha element is enabled, and the admin settings fulfil its requirements.
+     * Returns whether or not the captcha element is enabled.
      *
      * @abstract Implement in child classes
      * @return bool
index 8e3d7e5..d88de86 100644 (file)
@@ -96,7 +96,7 @@ define('BADGE_MESSAGE_MONTHLY', 4);
 /*
  * URL of backpack. Currently only the Open Badges backpack is supported.
  */
-define('BADGE_BACKPACKURL', 'backpack.openbadges.org');
+define('BADGE_BACKPACKURL', 'https://backpack.openbadges.org');
 
 /**
  * Class that represents badge.
@@ -1160,7 +1160,7 @@ function badges_check_backpack_accessibility() {
         'HEADER' => 0,
         'CONNECTTIMEOUT' => 2,
     );
-    $location = 'http://' . BADGE_BACKPACKURL . '/baker';
+    $location = BADGE_BACKPACKURL . '/baker';
     $out = $curl->get($location, array('assertion' => $fakeassertion->out(false)), $options);
 
     $data = json_decode($out);
@@ -1228,8 +1228,7 @@ function badges_setup_backpack_js() {
     global $CFG, $PAGE;
     if (!empty($CFG->badges_allowexternalbackpack)) {
         $PAGE->requires->string_for_js('error:backpackproblem', 'badges');
-        $protocol = (is_https()) ? 'https://' : 'http://';
-        $PAGE->requires->js(new moodle_url($protocol . BADGE_BACKPACKURL . '/issuer.js'), true);
+        $PAGE->requires->js(new moodle_url(BADGE_BACKPACKURL . '/issuer.js'), true);
         $PAGE->requires->js('/badges/backpack.js', true);
     }
 }
index b278c14..d5c7f41 100644 (file)
@@ -8,7 +8,7 @@
  *
  *  See http://bennu.sourceforge.net/ for more information and downloads.
  *
- * @author Ioannis Papaioannou 
+ * @author Ioannis Papaioannou
  * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  */
 
@@ -20,37 +20,44 @@ class Bennu {
         return gmstrftime('%Y%m%dT%H%M%SZ', $t);
     }
 
+    static function timestamp_to_date($t = NULL) {
+        if ($t === NULL) {
+            $t = time();
+        }
+        return gmstrftime('%Y%m%d', $t);
+    }
+
     static function generate_guid() {
         // Implemented as per the Network Working Group draft on UUIDs and GUIDs
-    
+
         // These two octets get special treatment
         $time_hi_and_version       = sprintf('%02x', (1 << 6) + mt_rand(0, 15)); // 0100 plus 4 random bits
         $clock_seq_hi_and_reserved = sprintf('%02x', (1 << 7) + mt_rand(0, 63)); // 10 plus 6 random bits
-    
+
         // Need another 14 random octects
         $pool = '';
         for($i = 0; $i < 7; ++$i) {
             $pool .= sprintf('%04x', mt_rand(0, 65535));
         }
-    
+
         // time_low = 4 octets
         $random  = substr($pool, 0, 8).'-';
-    
+
         // time_mid = 2 octets
         $random .= substr($pool, 8, 4).'-';
-    
+
         // time_high_and_version = 2 octets
         $random .= $time_hi_and_version.substr($pool, 12, 2).'-';
-    
+
         // clock_seq_high_and_reserved = 1 octet
         $random .= $clock_seq_hi_and_reserved;
-    
+
         // clock_seq_low = 1 octet
         $random .= substr($pool, 13, 2).'-';
-    
+
         // node = 6 octets
         $random .= substr($pool, 14, 12);
-    
+
         return $random;
     }
 }
index 53fdd8a..8392eed 100644 (file)
@@ -79,7 +79,7 @@ function rfc2445_fold($string) {
         /* Add the portion to the return value, terminating with CRLF.HTAB
            As per RFC 2445, CRLF.HTAB will be replaced by the processor of the 
            data */
-        $retval .= $section.RFC2445_CRLF.RFC2445_WSP;
+        $retval .= $section . RFC2445_CRLF . substr(RFC2445_WSP, 0, 1);
         
         $i++;
     }
index c27a172..91fcf4c 100644 (file)
@@ -7,3 +7,5 @@ modifications:
 4/ updated rfc2445_is_valid_value() to accept single part rrule as a valid value (16 Jun 2014)
 5/ updated DTEND;TZID and DTSTAR;TZID values to support quotations (7 Nov 2014)
 6/ added calendar_normalize_tz function to convert region timezone to php supported timezone (7 Nov 2014)
+7/ MDL-49032: fixed rfc2445_fold() to fix incorrect RFC2445_WSP definition (16 Sep 2015)
+8/ added timestamp_to_date function to support zero duration events (16 Sept 2015)
index 4866dc2..56208b6 100644 (file)
@@ -2055,6 +2055,31 @@ function blocks_delete_instance($instance, $nolongerused = false, $skipblockstab
     }
 }
 
+/**
+ * Delete multiple blocks at once.
+ *
+ * @param array $instanceids A list of block instance ID.
+ */
+function blocks_delete_instances($instanceids) {
+    global $DB;
+
+    $instances = $DB->get_recordset_list('block_instances', 'id', $instanceids);
+    foreach ($instances as $instance) {
+        blocks_delete_instance($instance, false, true);
+    }
+    $instances->close();
+
+    $DB->delete_records_list('block_positions', 'blockinstanceid', $instanceids);
+    $DB->delete_records_list('block_instances', 'id', $instanceids);
+
+    $preferences = array();
+    foreach ($instanceids as $instanceid) {
+        $preferences[] = 'block' . $instanceid . 'hidden';
+        $preferences[] = 'docked_block_instance_' . $instanceid;
+    }
+    $DB->delete_records_list('user_preferences', 'name', $preferences);
+}
+
 /**
  * Delete all the blocks that belong to a particular context.
  *
index 28c47a0..98fa0f4 100644 (file)
@@ -104,7 +104,7 @@ class calendar_event_created extends base {
         if (!isset($this->other['repeatid'])) {
             throw new \coding_exception('The \'repeatid\' value must be set in other.');
         }
-        if (empty($this->other['name'])) {
+        if (!isset($this->other['name'])) {
             throw new \coding_exception('The \'name\' value must be set in other.');
         }
         if (!isset($this->other['timestart'])) {
index 933f0b0..cdd013b 100644 (file)
@@ -85,7 +85,7 @@ class calendar_event_deleted extends base {
         if (!isset($this->other['repeatid'])) {
             throw new \coding_exception('The \'repeatid\' value must be set in other.');
         }
-        if (empty($this->other['name'])) {
+        if (!isset($this->other['name'])) {
             throw new \coding_exception('The \'name\' value must be set in other.');
         }
         if (!isset($this->other['timestart'])) {
index 0a810c2..8697141 100644 (file)
@@ -103,7 +103,7 @@ class calendar_event_updated extends base {
         if (!isset($this->other['repeatid'])) {
             throw new \coding_exception('The \'repeatid\' value must be set in other.');
         }
-        if (empty($this->other['name'])) {
+        if (!isset($this->other['name'])) {
             throw new \coding_exception('The \'name\' value must be set in other.');
         }
         if (!isset($this->other['timestart'])) {
diff --git a/lib/classes/event/message_deleted.php b/lib/classes/event/message_deleted.php
new file mode 100644 (file)
index 0000000..5d3f71b
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Message deleted event.
+ *
+ * @package    core
+ * @copyright  2015 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Message deleted event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - string $messagetable: the table we marked the message as deleted from (message/message_read).
+ *      - int messageid: the id of the message.
+ *      - int useridfrom: the id of the user who received the message.
+ *      - int useridto: the id of the user who sent the message.
+ * }
+ *
+ * @package    core
+ * @since      Moodle 3.0
+ * @copyright  2015 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class message_deleted extends base {
+
+    /**
+     * Create event using ids.
+     *
+     * @param int $userfromid the user who the message was from.
+     * @param int $usertoid the user who the message was sent to.
+     * @param int $userdeleted the user who deleted it.
+     * @param string $messagetable the table we are marking the message as deleted in.
+     * @param int $messageid the id of the message that was deleted.
+     * @return message_deleted
+     */
+    public static function create_from_ids($userfromid, $usertoid, $userdeleted, $messagetable, $messageid) {
+        // Check who was deleting the message.
+        if ($userdeleted == $userfromid) {
+            $relateduserid = $usertoid;
+        } else {
+            $relateduserid = $userfromid;
+        }
+
+        // We set the userid to the user who deleted the message, nothing to do
+        // with whether or not they sent or received the message.
+        $event = self::create(array(
+            'userid' => $userdeleted,
+            'context' => \context_system::instance(),
+            'relateduserid' => $relateduserid,
+            'other' => array(
+                'messagetable'