Merge branch 'MDL-8307-master' of git://github.com/FMCorz/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 19 Sep 2012 09:33:24 +0000 (11:33 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 19 Sep 2012 09:33:24 +0000 (11:33 +0200)
404 files changed:
admin/settings/appearance.php
admin/settings/courses.php
auth/db/auth.php
auth/db/lang/en/auth_db.php
auth/shibboleth/index.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_course_task.class.php
backup/moodle2/restore_qtype_plugin.class.php
backup/moodle2/restore_root_task.class.php
backup/moodle2/restore_stepslib.php
backup/util/plan/restore_structure_step.class.php
blocks/calendar_month/block_calendar_month.php
blocks/community/yui/comments/comments.js
blocks/dock.js
blocks/navigation/renderer.php
blocks/navigation/yui/navigation/navigation.js
blocks/rss_client/block_rss_client.php
blocks/settings/renderer.php
calendar/lib.php
calendar/renderer.php
calendar/view.php
cohort/assign.php
cohort/edit.php
cohort/edit_form.php
cohort/index.php
cohort/lib.php
cohort/locallib.php [new file with mode: 0644]
cohort/tests/cohortlib_test.php [new file with mode: 0644]
course/category.php
course/edit_form.php
course/format/weeks/format.js
course/tests/courselib_test.php
course/tests/courserequest_test.php [new file with mode: 0644]
course/tests/externallib_test.php
enrol/category/lib.php
enrol/cohort/addinstance.php
enrol/cohort/addinstance_form.php
enrol/cohort/ajax.php
enrol/cohort/cli/sync.php
enrol/cohort/db/access.php
enrol/cohort/db/events.php
enrol/cohort/db/uninstall.php
enrol/cohort/lang/en/enrol_cohort.php
enrol/cohort/lib.php
enrol/cohort/locallib.php
enrol/cohort/settings.php
enrol/cohort/version.php
enrol/cohort/yui/quickenrolment/assets/skins/sam/quickenrolment.css
enrol/database/lib.php
enrol/guest/lib.php
enrol/manual/cli/sync.php [new file with mode: 0644]
enrol/manual/lang/en/enrol_manual.php
enrol/manual/lib.php
enrol/manual/settings.php
enrol/manual/tests/externallib_test.php [new file with mode: 0644]
enrol/manual/tests/lib_test.php
enrol/manual/version.php
enrol/self/cli/sync.php [new file with mode: 0644]
enrol/self/lang/en/enrol_self.php
enrol/self/lib.php
enrol/self/settings.php
enrol/self/tests/self_test.php [new file with mode: 0644]
enrol/self/version.php
enrol/tests/externallib_test.php [new file with mode: 0644]
enrol/upgrade.txt
files/renderer.php
group/tests/externallib_test.php [new file with mode: 0644]
index.php
install/lang/eu/admin.php
install/lang/ga/error.php [new file with mode: 0644]
install/lang/pl/install.php
install/lang/ru/install.php
lang/en/admin.php
lang/en/backup.php
lang/en/calendar.php
lang/en/moodle.php
lang/en/table.php
lib/adminlib.php
lib/blocklib.php
lib/datalib.php
lib/db/upgrade.php
lib/ddl/tests/ddl_test.php
lib/dml/moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/dtl/database_exporter.php
lib/editor/tinymce/all_strings.php [moved from lib/editor/tinymce/extra/strings.php with 67% similarity]
lib/editor/tinymce/cli/update_lang_files.php [new file with mode: 0644]
lib/editor/tinymce/extra/tools/download_langs.sh [deleted file]
lib/editor/tinymce/extra/tools/update_lang_files.php [deleted file]
lib/editor/tinymce/lang/en/editor_tinymce.php
lib/editor/tinymce/lib.php
lib/editor/tinymce/module.js
lib/editor/tinymce/plugins/moodleimage/lang/en/tinymce_moodleimage.php
lib/editor/tinymce/plugins/moodleimage/tinymce/image.htm
lib/editor/tinymce/plugins/moodlemedia/lang/en/tinymce_moodlemedia.php
lib/editor/tinymce/plugins/moodlemedia/tinymce/moodlemedia.htm
lib/editor/tinymce/readme_moodle.txt
lib/editor/tinymce/tiny_mce/3.6.0/jquery.tinymce.js [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/license.txt
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advhr/css/advhr.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advhr/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advhr/js/rule.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advhr/rule.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advimage/css/advimage.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advimage/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advimage/image.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advimage/js/image.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advlink/css/advlink.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advlink/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advlink/js/advlink.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advlink/link.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/advlist/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/autosave/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/autosave/langs/en.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/bbcode/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/contextmenu/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/directionality/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/emotions/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/emotions/emotions.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/emotions/js/emotions.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/example/dialog.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/example/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/example/js/dialog.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/example/langs/en.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/example/langs/en_dlg.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/fullpage/css/fullpage.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/fullpage/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/fullpage/fullpage.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/fullpage/js/fullpage.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/fullscreen/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/fullscreen/fullscreen.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/iespell/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/inlinepopups/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/inlinepopups/template.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/insertdatetime/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/layer/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/legacyoutput/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/media/css/media.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/media/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/media/js/embed.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/media/js/media.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/media/media.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/nonbreaking/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/noneditable/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/pagebreak/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/paste/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/paste/js/pastetext.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/paste/js/pasteword.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/paste/pastetext.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/paste/pasteword.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/preview/example.html
lib/editor/tinymce/tiny_mce/3.6.0/plugins/preview/jscripts/embed.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/preview/preview.html
lib/editor/tinymce/tiny_mce/3.6.0/plugins/print/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/save/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/searchreplace/css/searchreplace.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/searchreplace/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/searchreplace/js/searchreplace.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/searchreplace/searchreplace.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/spellchecker/css/content.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/spellchecker/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/style/css/props.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/style/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/style/js/props.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/style/props.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/table/cell.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/table/css/cell.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/table/css/row.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/table/css/table.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/table/js/cell.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/table/js/merge_cells.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/table/js/row.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/table/js/table.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/table/merge_cells.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/table/row.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/table/table.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/template/blank.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/template/css/template.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/template/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/template/js/template.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/template/template.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/css/visualblocks.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/address.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/article.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/aside.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/blockquote.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/div.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/figure.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/h1.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/h2.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/h3.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/h4.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/h5.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/h6.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/hgroup.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/p.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/pre.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualblocks/img/section.gif [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/plugins/visualchars/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/wordcount/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/abbr.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/acronym.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/attributes.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/cite.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/css/attributes.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/css/popup.css
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/del.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/editor_plugin_src.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/ins.htm
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/js/abbr.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/js/acronym.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/js/attributes.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/js/cite.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/js/del.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/js/element_common.js
lib/editor/tinymce/tiny_mce/3.6.0/plugins/xhtmlxtras/js/ins.js
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/about.htm
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/anchor.htm
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/charmap.htm
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/color_picker.htm
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/editor_template_src.js
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/image.htm
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/js/about.js
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/js/anchor.js
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/js/charmap.js
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/js/image.js
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/js/link.js
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/js/source_editor.js
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/link.htm
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/shortcuts.htm
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/skins/default/content.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/skins/default/dialog.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/skins/default/ui.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/skins/highcontrast/content.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/skins/highcontrast/dialog.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/skins/highcontrast/ui.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/skins/o2k7/content.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/skins/o2k7/dialog.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/skins/o2k7/ui.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/skins/o2k7/ui_black.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/skins/o2k7/ui_silver.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/advanced/source_editor.htm
lib/editor/tinymce/tiny_mce/3.6.0/themes/simple/editor_template_src.js
lib/editor/tinymce/tiny_mce/3.6.0/themes/simple/skins/default/content.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/simple/skins/default/ui.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/simple/skins/o2k7/content.css
lib/editor/tinymce/tiny_mce/3.6.0/themes/simple/skins/o2k7/ui.css
lib/editor/tinymce/tiny_mce/3.6.0/tiny_mce.js
lib/editor/tinymce/tiny_mce/3.6.0/tiny_mce_dev.js [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/tiny_mce_jquery.js [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/tiny_mce_jquery_src.js [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/tiny_mce_popup.js
lib/editor/tinymce/tiny_mce/3.6.0/tiny_mce_popup_src.js [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/tiny_mce_prototype.js [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/tiny_mce_prototype_src.js [deleted file]
lib/editor/tinymce/tiny_mce/3.6.0/tiny_mce_src.js
lib/editor/tinymce/tiny_mce/3.6.0/utils/editable_selects.js
lib/editor/tinymce/tiny_mce/3.6.0/utils/form_utils.js
lib/editor/tinymce/tiny_mce/3.6.0/utils/mctabs.js
lib/editor/tinymce/tiny_mce/3.6.0/utils/validate.js
lib/editor/tinymce/upgrade.txt
lib/editor/tinymce/version.php
lib/enrollib.php
lib/filelib.php
lib/filestorage/file_storage.php
lib/filestorage/tests/zip_packer_test.php
lib/formslib.php
lib/javascript-static.js
lib/moodlelib.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/phpunit/classes/advanced_testcase.php
lib/phpunit/classes/data_generator.php
lib/phpunit/classes/database_driver_testcase.php
lib/phpunit/classes/util.php
lib/phpunit/tests/advanced_test.php
lib/phpunit/tests/generator_test.php
lib/pluginlib.php
lib/tablelib.php
lib/tests/accesslib_test.php
lib/tests/moodlelib_test.php
lib/tests/outputlib_test.php
lib/tests/textlib_test.php
lib/upgrade.txt
lib/weblib.php
message/tests/externallib_test.php [new file with mode: 0644]
mod/assign/assignmentplugin.php
mod/assign/backup/moodle2/backup_assign_stepslib.php
mod/assign/db/events.php
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/file/batchuploadfilesform.php [new file with mode: 0644]
mod/assign/feedback/file/importzipform.php [new file with mode: 0644]
mod/assign/feedback/file/importziplib.php [new file with mode: 0644]
mod/assign/feedback/file/lang/en/assignfeedback_file.php
mod/assign/feedback/file/locallib.php
mod/assign/feedback/file/renderable.php [new file with mode: 0644]
mod/assign/feedback/file/renderer.php [new file with mode: 0644]
mod/assign/feedback/file/uploadzipform.php [new file with mode: 0644]
mod/assign/feedback/offline/db/access.php [new file with mode: 0644]
mod/assign/feedback/offline/importgradesform.php [new file with mode: 0644]
mod/assign/feedback/offline/importgradeslib.php [new file with mode: 0644]
mod/assign/feedback/offline/lang/en/assignfeedback_offline.php [new file with mode: 0644]
mod/assign/feedback/offline/locallib.php [new file with mode: 0644]
mod/assign/feedback/offline/settings.php [new file with mode: 0644]
mod/assign/feedback/offline/uploadgradesform.php [new file with mode: 0644]
mod/assign/feedback/offline/version.php [new file with mode: 0644]
mod/assign/feedbackplugin.php
mod/assign/gradingbatchoperationsform.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/module.js
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/submission/comments/locallib.php
mod/assign/submission/onlinetext/locallib.php
mod/book/backup/moodle2/restore_book_activity_task.class.php
mod/book/lib.php
mod/book/tool/print/index.php
mod/book/tool/print/locallib.php
mod/book/version.php
mod/book/view.php
mod/chat/chatd.php
mod/chat/gui_sockets/chatinput.php
mod/chat/gui_sockets/index.php
mod/choice/backup/moodle2/backup_choice_stepslib.php
mod/folder/renderer.php
mod/forum/db/access.php
mod/forum/db/events.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/post.php
mod/forum/version.php
mod/lesson/continue.php
mod/lesson/edit.php
mod/lesson/editpage.php
mod/lesson/essay.php
mod/lesson/format.php
mod/lesson/highscores.php
mod/lesson/lang/en/lesson.php
mod/lesson/lesson.php
mod/lesson/renderer.php
mod/lesson/report.php
mod/lesson/view.php
mod/quiz/editlib.php
mod/quiz/lang/en/quiz.php
mod/quiz/report/attemptsreport_table.php
mod/quiz/report/statistics/lang/en/quiz_statistics.php
mod/quiz/report/statistics/report.php
mod/scorm/lib.php
mod/scorm/locallib.php
mod/scorm/player.php
mod/scorm/styles.css
mod/scorm/view.js
mod/scorm/view.php
mod/upgrade.txt
my/index.php
notes/tests/externallib_test.php [new file with mode: 0644]
phpunit.xml.dist
question/behaviour/behaviourbase.php
question/engine/lib.php
question/engine/questionattempt.php
question/engine/tests/questionutils_test.php
question/format.php
question/format/aiken/format.php
question/format/blackboard/format.php
question/format/blackboard_six/formatpool.php
question/format/blackboard_six/formatqti.php
question/format/blackboard_six/lang/en/qformat_blackboard_six.php
question/format/examview/format.php
question/format/gift/format.php
question/format/learnwise/format.php
question/format/missingword/format.php
question/format/multianswer/format.php
question/format/multianswer/tests/multianswerformat_test.php
question/format/webct/format.php
question/format/xml/format.php
question/tests/importexport_test.php
question/type/calculatedmulti/styles.css
question/type/match/styles.css
question/type/multianswer/styles.css
question/type/multichoice/styles.css
report/log/index.php
report/log/lang/en/report_log.php
repository/dropbox/lang/en/repository_dropbox.php
repository/dropbox/lib.php
repository/filepicker.js
theme/afterburner/style/afterburner_calendar.css
theme/base/style/admin.css
theme/base/style/calendar.css
theme/base/style/core.css
theme/base/style/course.css
theme/formal_white/layout/frontpage.php
theme/formal_white/layout/general.php
theme/formal_white/layout/report.php
theme/formal_white/settings.php
theme/standard/style/calendar.css
user/editadvanced_form.php
version.php
webservice/externallib.php
webservice/tests/externallib_test.php [new file with mode: 0644]

index 8130da4..acd62df 100644 (file)
@@ -97,6 +97,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
         HOMEPAGE_USER => new lang_string('userpreference', 'admin')
     );
     $temp->add(new admin_setting_configselect('defaulthomepage', new lang_string('defaulthomepage', 'admin'), new lang_string('configdefaulthomepage', 'admin'), HOMEPAGE_SITE, $choices));
+    $temp->add(new admin_setting_configcheckbox('allowguestmymoodle', new lang_string('allowguestmymoodle', 'admin'), new lang_string('configallowguestmymoodle', 'admin'), 1));
     $temp->add(new admin_setting_configcheckbox('navshowcategories', new lang_string('navshowcategories', 'admin'), new lang_string('confignavshowcategories', 'admin'), 1));
     $temp->add(new admin_setting_configcheckbox('navshowmycoursecategories', new lang_string('navshowmycoursecategories', 'admin'), new lang_string('navshowmycoursecategories_help', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('navshowallcourses', new lang_string('navshowallcourses', 'admin'), new lang_string('confignavshowallcourses', 'admin'), 0));
index 04f7975..25614ab 100644 (file)
@@ -35,10 +35,11 @@ if ($hassiteconfig
     $temp->add(new admin_setting_configselect('moodlecourse/showgrades', new lang_string('showgrades'), new lang_string('coursehelpshowgrades'), 1,array(0 => new lang_string('no'), 1 => new lang_string('yes'))));
     $temp->add(new admin_setting_configselect('moodlecourse/showreports', new lang_string('showreports'), '', 0,array(0 => new lang_string('no'), 1 => new lang_string('yes'))));
 
+    $currentmaxbytes = get_config('moodlecourse', 'maxbytes');
     if (isset($CFG->maxbytes)) {
-        $choices = get_max_upload_sizes($CFG->maxbytes);
+        $choices = get_max_upload_sizes($CFG->maxbytes, 0, 0, $currentmaxbytes);
     } else {
-        $choices = get_max_upload_sizes();
+        $choices = get_max_upload_sizes(0, 0, 0, $currentmaxbytes);
     }
     $temp->add(new admin_setting_configselect('moodlecourse/maxbytes', new lang_string('maximumupload'), new lang_string('coursehelpmaximumupload'), key($choices), $choices));
 
index f72b318..60babc8 100644 (file)
@@ -65,7 +65,7 @@ class auth_plugin_db extends auth_plugin_base {
                 $authdb->Close();
                 // user exists externally
                 // check username/password internally
-                if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id))) {
+                if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
                     return validate_internal_user_password($user, $password);
                 }
             } else {
@@ -191,8 +191,16 @@ class auth_plugin_db extends auth_plugin_base {
      * @return bool                  True on success
      */
     function user_update_password($user, $newpassword) {
+        global $DB;
+
         if ($this->is_internal()) {
-            return update_internal_user_password($user, $newpassword);
+            $puser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
+            if (update_internal_user_password($puser, $newpassword)) {
+                $user->password = $puser->password;
+                return true;
+            } else {
+                return false;
+            }
         } else {
             // we should have never been called!
             return false;
@@ -356,7 +364,7 @@ class auth_plugin_db extends auth_plugin_base {
             if ($verbose) {
                 mtrace(get_string('auth_dbuserstoadd','auth_db',count($add_users)));
             }
-            $transaction = $DB->start_delegated_transaction();
+            // Do not use transactions around this foreach, we want to skip problematic users, not revert everything.
             foreach($add_users as $user) {
                 $username = $user;
                 if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
@@ -382,6 +390,12 @@ class auth_plugin_db extends auth_plugin_base {
                 }
                 $user->timecreated = time();
                 $user->timemodified = $user->timecreated;
+                if ($collision = $DB->get_record_select('user', "username = :username AND mnethostid = :mnethostid AND auth <> :auth", array('username'=>$user->username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype), 'id,username,auth')) {
+                    if ($verbose) {
+                        mtrace("\t".get_string('auth_dbinsertuserduplicate', 'auth_db', array('username'=>$user->username, 'auth'=>$collision->auth)));
+                    }
+                    continue;
+                }
                 try {
                     $id = $DB->insert_record ('user', $user); // it is truly a new user
                     if ($verbose) {
@@ -391,14 +405,16 @@ class auth_plugin_db extends auth_plugin_base {
                     if ($verbose) {
                         mtrace("\t".get_string('auth_dbinsertusererror', 'auth_db', $user->username));
                     }
+                    continue;
                 }
                 // if relevant, tag for password generation
                 if ($this->is_internal()) {
                     set_user_preference('auth_forcepasswordchange', 1, $id);
                     set_user_preference('create_password',          1, $id);
                 }
+                // Make sure user context is present.
+                context_user::instance($id);
             }
-            $transaction->allow_commit();
             unset($add_users); // free mem
         }
         return 0;
index 6f42064..76967ea 100644 (file)
@@ -40,6 +40,7 @@ $string['auth_dbhost'] = 'The computer hosting the database server.';
 $string['auth_dbhost_key'] = 'Host';
 $string['auth_dbchangepasswordurl_key'] = 'Password-change URL';
 $string['auth_dbinsertuser'] = 'Inserted user {$a->name} id {$a->id}';
+$string['auth_dbinsertuserduplicate'] = 'Error inserting user {$a->username} - user with this username was already created through \'{$a->auth}\' plugin.';
 $string['auth_dbinsertusererror'] = 'Error inserting user {$a}';
 $string['auth_dbname'] = 'Name of the database itself';
 $string['auth_dbname_key'] = 'DB name';
index bd4eddb..051ac4f 100644 (file)
@@ -6,6 +6,9 @@
 
     $PAGE->set_url('/auth/shibboleth/index.php');
 
+    // Support for WAYFless URLs.
+    $SESSION->wantsurl = optional_param('target', $SESSION->wantsurl, PARAM_LOCALURL);
+
     if (isloggedin() && !isguestuser()) {      // Nothing to do
         if (isset($SESSION->wantsurl) and (strpos($SESSION->wantsurl, $CFG->wwwroot) === 0)) {
             $urltogo = $SESSION->wantsurl;    /// Because it's an address in this site
index 850bb2e..3578063 100644 (file)
@@ -520,7 +520,7 @@ class backup_enrolments_structure_step extends backup_structure_step {
         $enrols = new backup_nested_element('enrols');
 
         $enrol = new backup_nested_element('enrol', array('id'), array(
-            'enrol', 'status', 'sortorder', 'name', 'enrolperiod', 'enrolstartdate',
+            'enrol', 'status', 'name', 'enrolperiod', 'enrolstartdate',
             'enrolenddate', 'expirynotify', 'expirytreshold', 'notifyall',
             'password', 'cost', 'currency', 'roleid',
             'customint1', 'customint2', 'customint3', 'customint4', 'customint5', 'customint6', 'customint7', 'customint8',
@@ -541,9 +541,8 @@ class backup_enrolments_structure_step extends backup_structure_step {
         $enrol->add_child($userenrolments);
         $userenrolments->add_child($enrolment);
 
-        // Define sources
-
-        $enrol->set_source_table('enrol', array('courseid' => backup::VAR_COURSEID));
+        // Define sources - the instances are restored using the same sortorder, we do not need to store it in xml and deal with it afterwards.
+        $enrol->set_source_sql("SELECT * FROM {enrol} WHERE courseid = :courseid ORDER BY sortorder", array('courseid' => backup::VAR_COURSEID));
 
         // User enrolments only added only if users included
         if ($users) {
index f39e6c6..a43909b 100644 (file)
@@ -71,14 +71,15 @@ class restore_course_task extends restore_task {
             $this->add_step(new restore_course_structure_step('course_info', 'course.xml'));
         }
 
-        // Restore course role assignments and overrides (internally will observe the role_assignments setting)
-        $this->add_step(new restore_ras_and_caps_structure_step('course_ras_and_caps', 'roles.xml'));
-
         // Restore course enrolments (plugins and membership). Conditionally prevented for any IMPORT/HUB operation
         if ($this->plan->get_mode() != backup::MODE_IMPORT && $this->plan->get_mode() != backup::MODE_HUB) {
             $this->add_step(new restore_enrolments_structure_step('course_enrolments', 'enrolments.xml'));
         }
 
+        // Restore course role assignments and overrides (internally will observe the role_assignments setting),
+        // this must be done after all users are enrolled.
+        $this->add_step(new restore_ras_and_caps_structure_step('course_ras_and_caps', 'roles.xml'));
+
         // Restore course filters (conditionally)
         if ($this->get_setting_value('filters')) {
             $this->add_step(new restore_filters_structure_step('course_filters', 'filters.xml'));
index 40b06ac..99365ba 100644 (file)
@@ -154,6 +154,22 @@ abstract class restore_qtype_plugin extends restore_plugin {
                        AND ' . $DB->sql_compare_text('answer', 255) . ' = ' . $DB->sql_compare_text('?', 255);
             $params = array($newquestionid, $data->answertext);
             $newitemid = $DB->get_field_sql($sql, $params);
+
+            // Not able to find the answer, let's try cleaning the answertext
+            // of all the question answers in DB as slower fallback. MDL-30018.
+            if (!$newitemid) {
+                $params = array('question' => $newquestionid);
+                $answers = $DB->get_records('question_answers', $params, '', 'id, answer');
+                foreach ($answers as $answer) {
+                    // Clean in the same way than {@link xml_writer::xml_safe_utf8()}.
+                    $clean = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is','', $answer->answer); // Clean CTRL chars.
+                    $clean = preg_replace("/\r\n|\r/", "\n", $clean); // Normalize line ending.
+                    if ($clean === $data->answertext) {
+                        $newitemid = $data->id;
+                    }
+                }
+            }
+
             // If we haven't found the newitemid, something has gone really wrong, question in DB
             // is missing answers, exception
             if (!$newitemid) {
index 6917f1c..f95a5b9 100644 (file)
@@ -112,6 +112,12 @@ class restore_root_task extends restore_task {
         $users->get_ui()->set_changeable($changeable);
         $this->add_setting($users);
 
+        $rootenrolmanual = new restore_users_setting('enrol_migratetomanual', base_setting::IS_BOOLEAN, false);
+        $rootenrolmanual->set_ui(new backup_setting_ui_checkbox($rootenrolmanual, get_string('rootenrolmanual', 'backup')));
+        $rootenrolmanual->get_ui()->set_changeable(enrol_is_enabled('manual'));
+        $this->add_setting($rootenrolmanual);
+        $users->add_dependency($rootenrolmanual);
+
         // Define role_assignments (dependent of users)
         $defaultvalue = false;                      // Safer default
         $changeable = false;
index 8742f0a..77f82ae 100644 (file)
@@ -1407,13 +1407,14 @@ class restore_course_structure_step extends restore_structure_step {
 
 /*
  * Structure step that will read the roles.xml file (at course/activity/block levels)
- * containig all the role_assignments and overrides for that context. If corresponding to
+ * containing all the role_assignments and overrides for that context. If corresponding to
  * one mapped role, they will be applied to target context. Will observe the role_assignments
  * setting to decide if ras are restored.
- * Note: only ras with component == null are restored as far as the any ra with component
- * is handled by one enrolment plugin, hence it will createt the ras later
+ *
+ * Note: this needs to be executed after all users are enrolled.
  */
 class restore_ras_and_caps_structure_step extends restore_structure_step {
+    protected $plugins = null;
 
     protected function define_structure() {
 
@@ -1462,15 +1463,15 @@ class restore_ras_and_caps_structure_step extends restore_structure_step {
             role_assign($newroleid, $newuserid, $contextid);
 
         } else if ((strpos($data->component, 'enrol_') === 0)) {
-            // Deal with enrolment roles
+            // Deal with enrolment roles - ignore the component and just find out the instance via new id,
+            // it is possible that enrolment was restored using different plugin type.
+            if (!isset($this->plugins)) {
+                $this->plugins = enrol_get_plugins(true);
+            }
             if ($enrolid = $this->get_mappingid('enrol', $data->itemid)) {
-                if ($component = $DB->get_field('enrol', 'component', array('id'=>$enrolid))) {
-                    //note: we have to verify component because it might have changed
-                    if ($component === 'enrol_manual') {
-                        // manual is a special case, we do not use components - this owudl happen when converting from other plugin
-                        role_assign($newroleid, $newuserid, $contextid); //TODO: do we need modifierid?
-                    } else {
-                        role_assign($newroleid, $newuserid, $contextid, $component, $enrolid); //TODO: do we need modifierid?
+                if ($instance = $DB->get_record('enrol', array('id'=>$enrolid))) {
+                    if (isset($this->plugins[$instance->enrol])) {
+                        $this->plugins[$instance->enrol]->restore_role_assignment($instance, $newroleid, $newuserid, $contextid);
                     }
                 }
             }
@@ -1496,6 +1497,9 @@ class restore_ras_and_caps_structure_step extends restore_structure_step {
  * enrolments, performing all the mappings and/or movements required
  */
 class restore_enrolments_structure_step extends restore_structure_step {
+    protected $enrolsynced = false;
+    protected $plugins = null;
+    protected $originalstatus = array();
 
     /**
      * Conditionally decide if this step should be executed.
@@ -1542,82 +1546,103 @@ class restore_enrolments_structure_step extends restore_structure_step {
         global $DB;
 
         $data = (object)$data;
-        $oldid = $data->id; // We'll need this later
+        $oldid = $data->id; // We'll need this later.
+        unset($data->id);
 
-        $restoretype = plugin_supports('enrol', $data->enrol, ENROL_RESTORE_TYPE, null);
+        $this->originalstatus[$oldid] = $data->status;
 
-        if ($restoretype !== ENROL_RESTORE_EXACT and $restoretype !== ENROL_RESTORE_NOUSERS) {
-            // TODO: add complex restore support via custom class
-            debugging("Skipping '{$data->enrol}' enrolment plugin. Will be implemented before 2.0 release", DEBUG_DEVELOPER);
+        if (!$courserec = $DB->get_record('course', array('id' => $this->get_courseid()))) {
             $this->set_mapping('enrol', $oldid, 0);
             return;
         }
 
-        // Perform various checks to decide what to do with the enrol plugin
-        if (!array_key_exists($data->enrol, enrol_get_plugins(false))) {
-            // TODO: decide if we want to switch to manual enrol - we need UI for this
-            debugging("Enrol plugin data can not be restored because it is not installed");
-            $this->set_mapping('enrol', $oldid, 0);
-            return;
-
+        if (!isset($this->plugins)) {
+            $this->plugins = enrol_get_plugins(true);
         }
-        if (!enrol_is_enabled($data->enrol)) {
-            // TODO: decide if we want to switch to manual enrol - we need UI for this
-            debugging("Enrol plugin data can not be restored because it is not enabled");
-            $this->set_mapping('enrol', $oldid, 0);
-            return;
+
+        if (!$this->enrolsynced) {
+            // Make sure that all plugin may create instances and enrolments automatically
+            // before the first instance restore - this is suitable especially for plugins
+            // that synchronise data automatically using course->idnumber or by course categories.
+            foreach ($this->plugins as $plugin) {
+                $plugin->restore_sync_course($courserec);
+            }
+            $this->enrolsynced = true;
         }
 
-        // map standard fields - plugin has to process custom fields from own restore class
-        $data->roleid = $this->get_mappingid('role', $data->roleid);
-        //TODO: should we move the enrol start and end date here?
+        // Map standard fields - plugin has to process custom fields manually.
+        $data->roleid   = $this->get_mappingid('role', $data->roleid);
+        $data->courseid = $courserec->id;
 
-        // always add instance, if the course does not support multiple instances it just returns NULL
-        $enrol = enrol_get_plugin($data->enrol);
-        $courserec = $DB->get_record('course', array('id' => $this->get_courseid())); // Requires object, uses only id!!
-        if ($newitemid = $enrol->add_instance($courserec, (array)$data)) {
-            // ok
-        } else {
-            if ($instances = $DB->get_records('enrol', array('courseid'=>$courserec->id, 'enrol'=>$data->enrol))) {
-                // most probably plugin that supports only one instance
-                $newitemid = key($instances);
+        if ($this->get_setting_value('enrol_migratetomanual')) {
+            unset($data->sortorder); // Remove useless sortorder from <2.4 backups.
+            if (!enrol_is_enabled('manual')) {
+                $this->set_mapping('enrol', $oldid, 0);
+                return;
+            }
+            if ($instances = $DB->get_records('enrol', array('courseid'=>$data->courseid, 'enrol'=>'manual'), 'id')) {
+                $instance = reset($instances);
+                $this->set_mapping('enrol', $oldid, $instance->id);
             } else {
-                debugging('Can not create new enrol instance or reuse existing');
-                $newitemid = 0;
+                if ($data->enrol === 'manual') {
+                    $instanceid = $this->plugins['manual']->add_instance($courserec, (array)$data);
+                } else {
+                    $instanceid = $this->plugins['manual']->add_default_instance($courserec);
+                }
+                $this->set_mapping('enrol', $oldid, $instanceid);
             }
-        }
 
-        if ($restoretype === ENROL_RESTORE_NOUSERS) {
-            // plugin requests to prevent restore of any users
-            $newitemid = 0;
+        } else {
+            if (!enrol_is_enabled($data->enrol) or !isset($this->plugins[$data->enrol])) {
+                debugging("Enrol plugin data can not be restored because it is not enabled, use migration to manual enrolments");
+                $this->set_mapping('enrol', $oldid, 0);
+                return;
+            }
+            if ($task = $this->get_task() and $task->get_target() == backup::TARGET_NEW_COURSE) {
+                // Let's keep the sortorder in old backups.
+            } else {
+                // Prevent problems with colliding sortorders in old backups,
+                // new 2.4 backups do not need sortorder because xml elements are ordered properly.
+                unset($data->sortorder);
+            }
+            // Note: plugin is responsible for setting up the mapping, it may also decide to migrate to different type.
+            $this->plugins[$data->enrol]->restore_instance($this, $data, $courserec, $oldid);
         }
-
-        $this->set_mapping('enrol', $oldid, $newitemid);
     }
 
     /**
-     * Create user enrolments
+     * Create user enrolments.
      *
      * This has to be called after creation of enrolment instances
      * and before adding of role assignments.
      *
+     * Roles are assigned in restore_ras_and_caps_structure_step::process_assignment() processing afterwards.
+     *
      * @param mixed $data
      * @return void
      */
     public function process_enrolment($data) {
         global $DB;
 
+        if (!isset($this->plugins)) {
+            $this->plugins = enrol_get_plugins(true);
+        }
+
         $data = (object)$data;
 
-        // Process only if parent instance have been mapped
+        // Process only if parent instance have been mapped.
         if ($enrolid = $this->get_new_parentid('enrol')) {
+            $oldinstancestatus = ENROL_INSTANCE_ENABLED;
+            $oldenrolid = $this->get_old_parentid('enrol');
+            if (isset($this->originalstatus[$oldenrolid])) {
+                $oldinstancestatus = $this->originalstatus[$oldenrolid];
+            }
             if ($instance = $DB->get_record('enrol', array('id'=>$enrolid))) {
-                // And only if user is a mapped one
+                // And only if user is a mapped one.
                 if ($userid = $this->get_mappingid('user', $data->userid)) {
-                    $enrol = enrol_get_plugin($instance->enrol);
-                    //TODO: do we need specify modifierid?
-                    $enrol->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, $data->status);
-                    //note: roles are assigned in restore_ras_and_caps_structure_step::process_assignment() processing above
+                    if (isset($this->plugins[$instance->enrol])) {
+                        $this->plugins[$instance->enrol]->restore_user_enrolment($this, $data, $instance, $userid, $oldinstancestatus);
+                    }
                 }
             }
         }
index 7a1cfa2..da34487 100644 (file)
@@ -278,6 +278,7 @@ abstract class restore_structure_step extends restore_step {
     /**
      * As far as restore structure steps are implementing restore_plugin stuff, they need to
      * have the parent task available for wrapping purposes (get course/context....)
+     * @return restore_task|null
      */
     public function get_task() {
         return $this->task;
index b578d4d..2e395ce 100644 (file)
@@ -50,7 +50,7 @@ class block_calendar_month extends block_base {
             $this->content->text .= calendar_top_controls('course', array('id' => $courseid, 'm' => $cal_m, 'y' => $cal_y));
             $this->content->text .= calendar_get_mini($courses, $group, $user, $cal_m, $cal_y);
             $this->content->text .= '<h3 class="eventskey">'.get_string('eventskey', 'calendar').'</h3>';
-            $this->content->text .= '<div class="filters">'.calendar_filter_controls($this->page->url).'</div>';
+            $this->content->text .= '<div class="filters calendar_filters">'.calendar_filter_controls($this->page->url).'</div>';
         }
 
         return $this->content;
index 817e3ac..c32b75e 100644 (file)
@@ -23,8 +23,7 @@ YUI.add('moodle-block_community-comments', function(Y) {
                     bodyContent:Y.one('#commentoverlay-'+commentid).get('innerHTML'),
                     visible: false, //by default it is not displayed
                     lightbox : false,
-                    zIndex:100,
-                    height: '350px'
+                    zIndex:100
                 });
 
                 this.overlays[commentid].get('contentBox').one('.commenttitle').remove();
index 5560a32..4eff68c 100644 (file)
@@ -164,7 +164,7 @@ M.core_dock.init = function(Y) {
     var dock = Y.one('#dock');
     if (!dock) {
         // Start the construction of the dock
-        dock = Y.Node.create('<div id="dock" class="'+css.dock+' '+css.dock+'_'+this.cfg.position+'_'+this.cfg.orientation+'"></div>')
+        dock = Y.Node.create('<div id="dock" role="menubar" class="'+css.dock+' '+css.dock+'_'+this.cfg.position+'_'+this.cfg.orientation+'"></div>')
                     .append(Y.Node.create('<div class="'+css.buttonscontainer+'"></div>')
                         .append(Y.Node.create('<div class="'+css.dockeditemcontainer+'"></div>')));
         this.nodes.body.append(dock);
@@ -863,6 +863,12 @@ M.core_dock.genericblock.prototype = {
             return;
         }
 
+        // Disable the skip anchor when docking
+        var skipanchor = node.previous();
+        if (skipanchor.hasClass('skip-block')) {
+            skipanchor.hide();
+        }
+
         var blockclass = (function(classes){
             var r = /(^|\s)(block_[a-zA-Z0-9_]+)(\s|$)/;
             var m = r.exec(classes);
@@ -937,6 +943,12 @@ M.core_dock.genericblock.prototype = {
     return_to_block : function(dockitem) {
         var placeholder = this.Y.one('#content_placeholder_'+this.id);
 
+        // Enable the skip anchor when going back to block mode
+        var skipanchor = placeholder.previous();
+        if (skipanchor.hasClass('skip-block')) {
+            skipanchor.show();
+        }
+
         if (this.cachedcontentnode.one('.header')) {
             this.cachedcontentnode.one('.header').insert(dockitem.contents, 'after');
         } else {
@@ -1024,7 +1036,7 @@ M.core_dock.item.prototype = {
         var Y = this.Y;
         var css = M.core_dock.css;
 
-        this.nodes.docktitle = Y.Node.create('<div id="dock_item_'+this.id+'_title" class="'+css.dockedtitle+'"></div>');
+        this.nodes.docktitle = Y.Node.create('<div id="dock_item_'+this.id+'_title" role="menu" aria-haspopup="true" class="'+css.dockedtitle+'"></div>');
         this.nodes.docktitle.append(this.title);
         this.nodes.dockitem = Y.Node.create('<div id="dock_item_'+this.id+'" class="'+css.dockeditem+'" tabindex="0"></div>');
         this.nodes.dockitem.on('dock:actionkey', this.toggle, this);
@@ -1053,6 +1065,8 @@ M.core_dock.item.prototype = {
         this.active = true;
         // Add active item class first up
         this.nodes.docktitle.addClass(css.activeitem);
+        // Set aria-exapanded property to true.
+        this.nodes.docktitle.set('aria-expanded', "true");
         this.fire('dockeditem:showcomplete');
         M.core_dock.resize();
         return true;
@@ -1069,6 +1083,8 @@ M.core_dock.item.prototype = {
         this.nodes.docktitle.removeClass(css.activeitem);
         // Hide the panel
         M.core_dock.getPanel().hide();
+        // Set aria-exapanded property to false
+        this.nodes.docktitle.set('aria-expanded', "false");
         this.fire('dockeditem:hidecomplete');
     },
     /**
index 4c3d28c..92e2831 100644 (file)
@@ -121,18 +121,20 @@ class block_navigation_renderer extends plugin_renderer_base {
 
             // this applies to the li item which contains all child lists too
             $liclasses = array($item->get_css_type(), 'depth_'.$depth);
+            $liexpandable = array();
             if ($item->has_children() && (!$item->forceopen || $item->collapse)) {
                 $liclasses[] = 'collapsed';
             }
             if ($isbranch) {
                 $liclasses[] = 'contains_branch';
+                $liexpandable = array('aria-expanded' => in_array('collapsed', $liclasses) ? "false" : "true");
             } else if ($hasicon) {
                 $liclasses[] = 'item_with_icon';
             }
             if ($item->isactive === true) {
                 $liclasses[] = 'current_branch';
             }
-            $liattr = array('class'=>join(' ',$liclasses));
+            $liattr = array('class' => join(' ',$liclasses)) + $liexpandable;
             // class attribute on the div item which only contains the item content
             $divclasses = array('tree_item');
             if ($isbranch) {
index 180977d..2189d51 100644 (file)
@@ -162,16 +162,20 @@ TREE.prototype = {
                 switch (e.action) {
                     case 'expand' :
                         target.removeClass('collapsed');
+                        target.set('aria-expanded', true);
                         break;
                     case 'collapse' :
                         target.addClass('collapsed');
+                        target.set('aria-expanded', false);
                         break;
                     default :
                         target.toggleClass('collapsed');
+                        target.set('aria-expanded', !target.hasClass('collapsed'));
                 }
                 e.halt();
             } else {
                 target.toggleClass('collapsed');
+                target.set('aria-expanded', !target.hasClass('collapsed'));
             }
         }
 
@@ -180,6 +184,7 @@ TREE.prototype = {
             target.siblings('li').each(function(){
                 if (this.get('id') !== target.get('id') && !this.hasClass('collapsed')) {
                     this.addClass('collapsed');
+                    this.set('aria-expanded', false);
                 }
             });
         }
@@ -287,6 +292,7 @@ BRANCH.prototype = {
         }
         if (isbranch) {
             branchli.addClass('collapsed').addClass('contains_branch');
+            branchli.set('aria-expanded', false);
             branchp.addClass('branch');
         }
 
index 086983d..38f8856 100644 (file)
 
         $r = html_writer::start_tag('li');
             $r.= html_writer::start_tag('div',array('class'=>'link'));
-                $r.= html_writer::link(clean_param($link,PARAM_URL), s($title), array('onclick'=>'this.target="_blank"'));
+                $r.= html_writer::link($link, s($title), array('onclick'=>'this.target="_blank"'));
             $r.= html_writer::end_tag('div');
 
             if($this->config->display_description && !empty($description)){
index 41cea87..a19d41e 100644 (file)
@@ -40,18 +40,20 @@ class block_settings_renderer extends plugin_renderer_base {
 
             // this applies to the li item which contains all child lists too
             $liclasses = array($item->get_css_type());
+            $liexpandable = array();
             if (!$item->forceopen || (!$item->forceopen && $item->collapse) || ($item->children->count()==0  && $item->nodetype==navigation_node::NODETYPE_BRANCH)) {
                 $liclasses[] = 'collapsed';
             }
             if ($isbranch) {
                 $liclasses[] = 'contains_branch';
+                $liexpandable = array('aria-expanded' => in_array('collapsed', $liclasses) ? "false" : "true");
             } else if ($hasicon) {
                 $liclasses[] = 'item_with_icon';
             }
             if ($item->isactive === true) {
                 $liclasses[] = 'current_branch';
             }
-            $liattr = array('class'=>join(' ',$liclasses));
+            $liattr = array('class' => join(' ',$liclasses)) + $liexpandable;
             // class attribute on the div item which only contains the item content
             $divclasses = array('tree_item');
             if ($isbranch) {
index 8812c63..53c2bbc 100644 (file)
@@ -871,6 +871,49 @@ function calendar_top_controls($type, $data) {
     return $content;
 }
 
+/**
+ * Formats a filter control element.
+ *
+ * @param moodle_url $url of the filter
+ * @param int $type constant defining the type filter
+ * @return string html content of the element
+ */
+function calendar_filter_controls_element(moodle_url $url, $type) {
+    global $OUTPUT;
+    switch ($type) {
+        case CALENDAR_EVENT_GLOBAL:
+            $typeforhumans = 'global';
+            $class = 'calendar_event_global';
+            break;
+        case CALENDAR_EVENT_COURSE:
+            $typeforhumans = 'course';
+            $class = 'calendar_event_course';
+            break;
+        case CALENDAR_EVENT_GROUP:
+            $typeforhumans = 'groups';
+            $class = 'calendar_event_group';
+            break;
+        case CALENDAR_EVENT_USER:
+            $typeforhumans = 'user';
+            $class = 'calendar_event_user';
+            break;
+    }
+    if (calendar_show_event_type($type)) {
+        $icon = $OUTPUT->pix_icon('t/hide', get_string('hide'));
+        $str = get_string('hide'.$typeforhumans.'events', 'calendar');
+    } else {
+        $icon = $OUTPUT->pix_icon('t/show', get_string('show'));
+        $str = get_string('show'.$typeforhumans.'events', 'calendar');
+    }
+    $content = html_writer::start_tag('li', array('class' => 'calendar_event'));
+    $content .= html_writer::start_tag('a', array('href' => $url));
+    $content .= html_writer::tag('span', $icon, array('class' => $class));
+    $content .= html_writer::tag('span', $str, array('class' => 'eventname'));
+    $content .= html_writer::end_tag('a');
+    $content .= html_writer::end_tag('li');
+    return $content;
+}
+
 /**
  * Get the controls filter for calendar.
  *
@@ -883,60 +926,28 @@ function calendar_filter_controls(moodle_url $returnurl) {
     global $CFG, $USER, $OUTPUT;
 
     $groupevents = true;
-
     $id = optional_param( 'id',0,PARAM_INT );
-
     $seturl = new moodle_url('/calendar/set.php', array('return' => base64_encode($returnurl->out(false)), 'sesskey'=>sesskey()));
-
-    $content = '<table>';
-    $content .= '<tr>';
+    $content = html_writer::start_tag('ul');
 
     $seturl->param('var', 'showglobal');
-    if (calendar_show_event_type(CALENDAR_EVENT_GLOBAL)) {
-        $content .= '<td class="eventskey calendar_event_global" style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/hide') . '" class="iconsmall" alt="'.get_string('hide').'" title="'.get_string('tt_hideglobal', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
-        $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_hideglobal', 'calendar').'">'.get_string('global', 'calendar').'</a></td>'."\n";
-    } else {
-        $content .= '<td style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/show') . '" class="iconsmall" alt="'.get_string('show').'" title="'.get_string('tt_showglobal', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
-        $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_showglobal', 'calendar').'">'.get_string('global', 'calendar').'</a></td>'."\n";
-    }
+    $content .= calendar_filter_controls_element($seturl, CALENDAR_EVENT_GLOBAL);
 
     $seturl->param('var', 'showcourses');
-    if (calendar_show_event_type(CALENDAR_EVENT_COURSE)) {
-        $content .= '<td class="eventskey calendar_event_course" style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/hide') . '" class="iconsmall" alt="'.get_string('hide').'" title="'.get_string('tt_hidecourse', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
-        $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_hidecourse', 'calendar').'">'.get_string('course', 'calendar').'</a></td>'."\n";
-    } else {
-        $content .= '<td style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/show') . '" class="iconsmall" alt="'.get_string('hide').'" title="'.get_string('tt_showcourse', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
-        $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_showcourse', 'calendar').'">'.get_string('course', 'calendar').'</a></td>'."\n";
-    }
+    $content .= calendar_filter_controls_element($seturl, CALENDAR_EVENT_COURSE);
 
     if (isloggedin() && !isguestuser()) {
-        $content .= "</tr>\n<tr>";
-
         if ($groupevents) {
             // This course MIGHT have group events defined, so show the filter
             $seturl->param('var', 'showgroups');
-            if (calendar_show_event_type(CALENDAR_EVENT_GROUP)) {
-                $content .= '<td class="eventskey calendar_event_group" style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/hide') . '" class="iconsmall" alt="'.get_string('hide').'" title="'.get_string('tt_hidegroups', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
-                $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_hidegroups', 'calendar').'">'.get_string('group', 'calendar').'</a></td>'."\n";
-            } else {
-                $content .= '<td style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/show') . '" class="iconsmall" alt="'.get_string('show').'" title="'.get_string('tt_showgroups', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
-                $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_showgroups', 'calendar').'">'.get_string('group', 'calendar').'</a></td>'."\n";
-            }
+            $content .= calendar_filter_controls_element($seturl, CALENDAR_EVENT_GROUP);
         } else {
             // This course CANNOT have group events, so lose the filter
-            $content .= '<td style="width: 11px;"></td><td>&nbsp;</td>'."\n";
         }
-
         $seturl->param('var', 'showuser');
-        if (calendar_show_event_type(CALENDAR_EVENT_USER)) {
-            $content .= '<td class="eventskey calendar_event_user" style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/hide') . '" class="iconsmall" alt="'.get_string('hide').'" title="'.get_string('tt_hideuser', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
-            $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_hideuser', 'calendar').'">'.get_string('user', 'calendar').'</a></td>'."\n";
-        } else {
-            $content .= '<td style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/show') . '" class="iconsmall" alt="'.get_string('show').'" title="'.get_string('tt_showuser', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
-            $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_showuser', 'calendar').'">'.get_string('user', 'calendar').'</a></td>'."\n";
-        }
+        $content .= calendar_filter_controls_element($seturl, CALENDAR_EVENT_USER);
     }
-    $content .= "</tr>\n</table>\n";
+    $content .= html_writer::end_tag('ul');
 
     return $content;
 }
index 45a6404..51ea81c 100644 (file)
@@ -559,8 +559,6 @@ class core_calendar_renderer extends plugin_renderer_base {
         $table->data[] = $row;
         $output .= html_writer::table($table);
 
-        // OK, now for the filtering display
-        $output .= $this->filter_selection_table($calendar);
         return $output;
     }
 
@@ -569,9 +567,13 @@ class core_calendar_renderer extends plugin_renderer_base {
      *
      * @param calendar_information $calendar
      * @return string
+     * @deprecated since Moodle 2.4 MDL-32309
+     * @see calendar_filter_controls()
      */
     protected function filter_selection_table(calendar_information $calendar, moodle_url $returnurl = null) {
         global $SESSION;
+        debugging('Method core_calendar_renderer::filter_selection_table() is deprecated, please use '.
+                'calendar_filter_controls() instead', DEBUG_DEVELOPER);
 
         if ($returnurl === null) {
             $returnurl = $this->page->url;
index fbad9e0..8f56956 100644 (file)
@@ -98,11 +98,11 @@ $time = make_timestamp($yr, $mon, $day);
 switch($view) {
     case 'day':
         $PAGE->navbar->add(userdate($time, get_string('strftimedate')));
-        $pagetitle = get_string('dayview', 'calendar');
+        $pagetitle = get_string('dayviewtitle', 'calendar', userdate($time, get_string('strftimedaydate')));
     break;
     case 'month':
         $PAGE->navbar->add(userdate($time, get_string('strftimemonthyear')));
-        $pagetitle = get_string('detailedmonthview', 'calendar');
+        $pagetitle = get_string('detailedmonthviewtitle', 'calendar', userdate($time, get_string('strftimemonthyear')));
     break;
     case 'upcoming':
         $pagetitle = get_string('upcomingevents', 'calendar');
index 5eaa2a7..0d6c83a 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 /**
  * Cohort related management functions, this file needs to be included manually.
  *
- * @package    core
- * @subpackage cohort
+ * @package    core_cohort
  * @copyright  2010 Petr Skoda  {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-require_once('../config.php');
-require_once($CFG->dirroot.'/cohort/lib.php');
+require('../config.php');
+require_once($CFG->dirroot.'/cohort/locallib.php');
 
 $id = required_param('id', PARAM_INT);
 
@@ -42,7 +40,7 @@ $PAGE->set_url('/cohort/assign.php', array('id'=>$id));
 $returnurl = new moodle_url('/cohort/index.php', array('contextid'=>$cohort->contextid));
 
 if (!empty($cohort->component)) {
-    // we can not manually edit cohorts that were created by external systems, sorry
+    // We can not manually edit cohorts that were created by external systems, sorry.
     redirect($returnurl);
 }
 
@@ -80,10 +78,7 @@ if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
     if (!empty($userstoassign)) {
 
         foreach ($userstoassign as $adduser) {
-            // no duplicates please
-            if (!$DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$adduser->id))) {
-                cohort_add_member($cohort->id, $adduser->id);
-            }
+            cohort_add_member($cohort->id, $adduser->id);
         }
 
         $potentialuserselector->invalidate_selected_users();
index 721d790..3a18ddb 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
-
 /**
  * Cohort related management functions, this file needs to be included manually.
  *
- * @package    core
- * @subpackage cohort
+ * @package    core_cohort
  * @copyright  2010 Petr Skoda  {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -58,7 +55,7 @@ require_capability('moodle/cohort:manage', $context);
 $returnurl = new moodle_url('/cohort/index.php', array('contextid'=>$context->id));
 
 if (!empty($cohort->component)) {
-    // we can not manually edit cohorts that were created by external systems, sorry
+    // We can not manually edit cohorts that were created by external systems, sorry.
     redirect($returnurl);
 }
 
@@ -97,12 +94,12 @@ if ($delete and $cohort->id) {
 
 $editoroptions = array('maxfiles'=>0, 'context'=>$context);
 if ($cohort->id) {
-    // edit existing
+    // Edit existing.
     $cohort = file_prepare_standard_editor($cohort, 'description', $editoroptions, $context);
     $strheading = get_string('editcohort', 'cohort');
 
 } else {
-    // add new
+    // Add new.
     $cohort = file_prepare_standard_editor($cohort, 'description', $editoroptions, $context);
     $strheading = get_string('addcohort', 'cohort');
 }
@@ -125,7 +122,7 @@ if ($editform->is_cancelled()) {
         cohort_add_cohort($data);
     }
 
-    // use new context id, it could have been changed
+    // Use new context id, it could have been changed.
     redirect(new moodle_url('/cohort/index.php', array('contextid'=>$data->contextid)));
 }
 
index 81e7a7c..c23c249 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 /**
  * Cohort related management functions, this file needs to be included manually.
  *
- * @package    core
- * @subpackage cohort
+ * @package    core_cohort
  * @copyright  2010 Petr Skoda  {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-if (!defined('MOODLE_INTERNAL')) {
-    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
-}
+defined('MOODLE_INTERNAL') || die();
 
 require_once($CFG->dirroot . '/lib/formslib.php');
 
@@ -49,7 +45,7 @@ class cohort_edit_form extends moodleform {
         $mform->addElement('select', 'contextid', get_string('context', 'role'), $options);
 
         $mform->addElement('text', 'idnumber', get_string('idnumber', 'cohort'), 'maxlength="254" size="50"');
-        $mform->setType('idnumber', PARAM_RAW); // idnumbers are plain text, must not be changed
+        $mform->setType('idnumber', PARAM_RAW); // Idnumbers are plain text, must not be changed.
 
         $mform->addElement('editor', 'description_editor', get_string('description', 'cohort'), null, $editoroptions);
         $mform->setType('description_editor', PARAM_RAW);
@@ -69,7 +65,7 @@ class cohort_edit_form extends moodleform {
 
         $idnumber = trim($data['idnumber']);
         if ($idnumber === '') {
-            // fine, empty is ok
+            // Fine, empty is ok.
 
         } else if ($data['id']) {
             $current = $DB->get_record('cohort', array('id'=>$data['id']), '*', MUST_EXIST);
@@ -95,16 +91,16 @@ class cohort_edit_form extends moodleform {
         $options = array();
         $syscontext = context_system::instance();
         if (has_capability('moodle/cohort:manage', $syscontext)) {
-            $options[$syscontext->id] = print_context_name($syscontext);
+            $options[$syscontext->id] = $syscontext->get_context_name();
         }
         foreach ($displaylist as $cid=>$name) {
             $context = context_coursecat::instance($cid);
             $options[$context->id] = $name;
         }
-        // always add current - this is not likely, but if the logic gets changed it might be a problem
+        // Always add current - this is not likely, but if the logic gets changed it might be a problem.
         if (!isset($options[$currentcontextid])) {
             $context = context::instance_by_id($currentcontextid, MUST_EXIST);
-            $options[$context->id] = print_context_name($syscontext);
+            $options[$context->id] = $syscontext->get_context_name();
         }
         return $options;
     }
index 00aab66..35d794f 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -18,8 +17,7 @@
 /**
  * Cohort related management functions, this file needs to be included manually.
  *
- * @package    core
- * @subpackage cohort
+ * @package    core_cohort
  * @copyright  2010 Petr Skoda  {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -69,21 +67,31 @@ if ($category) {
 
 echo $OUTPUT->header();
 
-echo $OUTPUT->heading(get_string('cohortsin', 'cohort', print_context_name($context)));
+$cohorts = cohort_get_cohorts($context->id, $page, 25, $searchquery);
+
+$count = '';
+if ($cohorts['allcohorts'] > 0) {
+    if ($searchquery === '') {
+        $count = ' ('.$cohorts['allcohorts'].')';
+    } else {
+        $count = ' ('.$cohorts['totalcohorts'].'/'.$cohorts['allcohorts'].')';
+    }
+}
+
+echo $OUTPUT->heading(get_string('cohortsin', 'cohort', $context->get_context_name()).$count);
 
-// add search form
+// Add search form.
 $search  = html_writer::start_tag('form', array('id'=>'searchcohortquery', 'method'=>'get'));
 $search .= html_writer::start_tag('div');
-$search .= html_writer::label(get_string('searchcohort', 'cohort').':', 'cohort_search_q');
+$search .= html_writer::label(get_string('searchcohort', 'cohort'), 'cohort_search_q'); // No : in form labels!
 $search .= html_writer::empty_tag('input', array('id'=>'cohort_search_q', 'type'=>'text', 'name'=>'search', 'value'=>$searchquery));
 $search .= html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('search', 'cohort')));
 $search .= html_writer::end_tag('div');
 $search .= html_writer::end_tag('form');
 echo $search;
 
-$cohorts = cohort_get_cohorts($context->id, $page, 25, $searchquery);
 
-// output pagination bar
+// Output pagination bar.
 $params = array('page' => $page);
 if ($contextid) {
     $params['contextid'] = $contextid;
@@ -98,7 +106,7 @@ $data = array();
 foreach($cohorts['cohorts'] as $cohort) {
     $line = array();
     $line[] = format_string($cohort->name);
-    $line[] = s($cohort->idnumber); // plain text
+    $line[] = s($cohort->idnumber); // All idnumbers are plain text.
     $line[] = format_text($cohort->description, $cohort->descriptionformat);
 
     $line[] = $DB->count_records('cohort_members', array('cohortid'=>$cohort->id));
@@ -137,4 +145,4 @@ if ($manager) {
     echo $OUTPUT->single_button(new moodle_url('/cohort/edit.php', array('contextid'=>$context->id)), get_string('add'));
 }
 
-echo $OUTPUT->footer();
\ No newline at end of file
+echo $OUTPUT->footer();
index 67c2bf4..89a79e6 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 /**
  * Cohort related management functions, this file needs to be included manually.
  *
- * @package    core
- * @subpackage cohort
+ * @package    core_cohort
  * @copyright  2010 Petr Skoda  {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-require_once($CFG->dirroot . '/user/selector/lib.php');
+defined('MOODLE_INTERNAL') || die();
 
 /**
  * Add new cohort.
  *
- * @param  object $cohort
- * @return int
+ * @param  stdClass $cohort
+ * @return int new cohort id
  */
 function cohort_add_cohort($cohort) {
     global $DB;
@@ -42,7 +40,8 @@ function cohort_add_cohort($cohort) {
         $cohort->idnumber = NULL;
     }
     if (!isset($cohort->description)) {
-        $cohort->description = $DB->sql_empty();
+        // sql_empty() does not belong here, this crazy Oracle hack is implemented in insert_record()!
+        $cohort->description = '';
     }
     if (!isset($cohort->descriptionformat)) {
         $cohort->descriptionformat = FORMAT_HTML;
@@ -66,7 +65,7 @@ function cohort_add_cohort($cohort) {
 
 /**
  * Update existing cohort.
- * @param  object $cohort
+ * @param  stdClass $cohort
  * @return void
  */
 function cohort_update_cohort($cohort) {
@@ -83,7 +82,7 @@ function cohort_update_cohort($cohort) {
 
 /**
  * Delete cohort.
- * @param  object $cohort
+ * @param  stdClass $cohort
  * @return void
  */
 function cohort_delete_cohort($cohort) {
@@ -103,7 +102,7 @@ function cohort_delete_cohort($cohort) {
  * Somehow deal with cohorts when deleting course category,
  * we can not just delete them because they might be used in enrol
  * plugins or referenced in external systems.
- * @param  object $category
+ * @param  stdClass $category
  * @return void
  */
 function cohort_delete_category($category) {
@@ -133,6 +132,10 @@ function cohort_delete_category($category) {
  */
 function cohort_add_member($cohortid, $userid) {
     global $DB;
+    if ($DB->record_exists('cohort_members', array('cohortid'=>$cohortid, 'userid'=>$userid))) {
+        // No duplicates!
+        return;
+    }
     $record = new stdClass();
     $record->cohortid  = $cohortid;
     $record->userid    = $userid;
@@ -168,34 +171,46 @@ function cohort_is_member($cohortid, $userid) {
 }
 
 /**
- * Returns list of visible cohorts in course.
+ * Returns list of cohorts from course parent contexts.
+ *
+ * Note: this function does not implement any capability checks,
+ *       it means it may disclose existence of cohorts,
+ *       make sure it is displayed to users with appropriate rights only.
  *
- * @param  object $course
- * @param  bool $enrolled true means include only cohorts with enrolled users
- * @return array
+ * @param  stdClass $course
+ * @param  bool $onlyenrolled true means include only cohorts with enrolled users
+ * @return array of cohort names with number of enrolled users
  */
-function cohort_get_visible_list($course) {
-    global $DB, $USER;
+function cohort_get_visible_list($course, $onlyenrolled=true) {
+    global $DB;
 
     $context = context_course::instance($course->id);
     list($esql, $params) = get_enrolled_sql($context);
-    $parentsql = get_related_contexts_string($context);
+    list($parentsql, $params2) = $DB->get_in_or_equal($context->get_parent_context_ids(), SQL_PARAMS_NAMED);
+    $params = array_merge($params, $params2);
 
-    $sql = "SELECT c.id, c.name, c.idnumber, COUNT(u.id) AS cnt
+    if ($onlyenrolled) {
+        $left = "";
+        $having = "HAVING COUNT(u.id) > 0";
+    } else {
+        $left = "LEFT";
+        $having = "";
+    }
+
+    $sql = "SELECT c.id, c.name, c.contextid, c.idnumber, COUNT(u.id) AS cnt
               FROM {cohort} c
-              JOIN {cohort_members} cm ON cm.cohortid = c.id
-              JOIN ($esql) u ON u.id = cm.userid
+        $left JOIN ({cohort_members} cm
+                   JOIN ($esql) u ON u.id = cm.userid) ON cm.cohortid = c.id
              WHERE c.contextid $parentsql
-          GROUP BY c.id, c.name, c.idnumber
-            HAVING COUNT(u.id) > 0
+          GROUP BY c.id, c.name, c.contextid, c.idnumber
+           $having
           ORDER BY c.name, c.idnumber";
-    $params['ctx'] = $context->id;
 
     $cohorts = $DB->get_records_sql($sql, $params);
 
     foreach ($cohorts as $cid=>$cohort) {
-        $cohorts[$cid] = format_string($cohort->name);
-        if ($cohort->idnumber) {
+        $cohorts[$cid] = format_string($cohort->name, true, array('context'=>$cohort->contextid));
+        if ($cohort->cnt) {
             $cohorts[$cid] .= ' (' . $cohort->cnt . ')';
         }
     }
@@ -204,170 +219,40 @@ function cohort_get_visible_list($course) {
 }
 
 /**
- * Get all the cohorts.
+ * Get all the cohorts defined in given context.
  *
- * @global moodle_database $DB
  * @param int $contextid
  * @param int $page number of the current page
  * @param int $perpage items per page
  * @param string $search search string
- * @return array    Array(totalcohorts => int, cohorts => array)
+ * @return array    Array(totalcohorts => int, cohorts => array, allcohorts => int)
  */
 function cohort_get_cohorts($contextid, $page = 0, $perpage = 25, $search = '') {
     global $DB;
 
-    $cohorts = array();
-
     // Add some additional sensible conditions
     $tests = array('contextid = ?');
     $params = array($contextid);
 
     if (!empty($search)) {
-        $conditions = array(
-            'name',
-            'idnumber',
-            'description',
-        );
-        $searchparam = '%' . $search . '%';
+        $conditions = array('name', 'idnumber', 'description');
+        $searchparam = '%' . $DB->sql_like_escape($search) . '%';
         foreach ($conditions as $key=>$condition) {
-            $conditions[$key] = $DB->sql_like($condition,"?", false);
+            $conditions[$key] = $DB->sql_like($condition, "?", false);
             $params[] = $searchparam;
         }
         $tests[] = '(' . implode(' OR ', $conditions) . ')';
     }
     $wherecondition = implode(' AND ', $tests);
 
-    $fields = 'SELECT *';
-    $countfields = 'SELECT COUNT(1)';
+    $fields = "SELECT *";
+    $countfields = "SELECT COUNT(1)";
     $sql = " FROM {cohort}
              WHERE $wherecondition";
-    $order = ' ORDER BY name ASC';
+    $order = " ORDER BY name ASC, idnumber ASC";
+    $allcohorts = $DB->count_records('cohort', array('contextid'=>$contextid));
     $totalcohorts = $DB->count_records_sql($countfields . $sql, $params);
     $cohorts = $DB->get_records_sql($fields . $sql . $order, $params, $page*$perpage, $perpage);
 
-    return array('totalcohorts' => $totalcohorts, 'cohorts' => $cohorts);
-}
-
-/**
- * Cohort assignment candidates
- */
-class cohort_candidate_selector extends user_selector_base {
-    protected $cohortid;
-
-    public function __construct($name, $options) {
-        $this->cohortid = $options['cohortid'];
-        parent::__construct($name, $options);
-    }
-
-    /**
-     * Candidate users
-     * @param <type> $search
-     * @return array
-     */
-    public function find_users($search) {
-        global $DB;
-        //by default wherecondition retrieves all users except the deleted, not confirmed and guest
-        list($wherecondition, $params) = $this->search_sql($search, 'u');
-        $params['cohortid'] = $this->cohortid;
-
-        $fields      = 'SELECT ' . $this->required_fields_sql('u');
-        $countfields = 'SELECT COUNT(1)';
-
-        $sql = " FROM {user} u
-            LEFT JOIN {cohort_members} cm ON (cm.userid = u.id AND cm.cohortid = :cohortid)
-                WHERE cm.id IS NULL AND $wherecondition";
-
-        $order = ' ORDER BY u.lastname ASC, u.firstname ASC';
-
-        if (!$this->is_validating()) {
-            $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
-            if ($potentialmemberscount > 100) {
-                return $this->too_many_results($search, $potentialmemberscount);
-            }
-        }
-
-        $availableusers = $DB->get_records_sql($fields . $sql . $order, $params);
-
-        if (empty($availableusers)) {
-            return array();
-        }
-
-
-        if ($search) {
-            $groupname = get_string('potusersmatching', 'cohort', $search);
-        } else {
-            $groupname = get_string('potusers', 'cohort');
-        }
-
-        return array($groupname => $availableusers);
-    }
-
-    protected function get_options() {
-        $options = parent::get_options();
-        $options['cohortid'] = $this->cohortid;
-        $options['file'] = 'cohort/lib.php';
-        return $options;
-    }
-}
-
-/**
- * Cohort assignment candidates
- */
-class cohort_existing_selector extends user_selector_base {
-    protected $cohortid;
-
-    public function __construct($name, $options) {
-        $this->cohortid = $options['cohortid'];
-        parent::__construct($name, $options);
-    }
-
-    /**
-     * Candidate users
-     * @param <type> $search
-     * @return array
-     */
-    public function find_users($search) {
-        global $DB;
-        //by default wherecondition retrieves all users except the deleted, not confirmed and guest
-        list($wherecondition, $params) = $this->search_sql($search, 'u');
-        $params['cohortid'] = $this->cohortid;
-
-        $fields      = 'SELECT ' . $this->required_fields_sql('u');
-        $countfields = 'SELECT COUNT(1)';
-
-        $sql = " FROM {user} u
-                 JOIN {cohort_members} cm ON (cm.userid = u.id AND cm.cohortid = :cohortid)
-                WHERE $wherecondition";
-
-        $order = ' ORDER BY u.lastname ASC, u.firstname ASC';
-
-        if (!$this->is_validating()) {
-            $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
-            if ($potentialmemberscount > 100) {
-                return $this->too_many_results($search, $potentialmemberscount);
-            }
-        }
-
-        $availableusers = $DB->get_records_sql($fields . $sql . $order, $params);
-
-        if (empty($availableusers)) {
-            return array();
-        }
-
-
-        if ($search) {
-            $groupname = get_string('currentusersmatching', 'cohort', $search);
-        } else {
-            $groupname = get_string('currentusers', 'cohort');
-        }
-
-        return array($groupname => $availableusers);
-    }
-
-    protected function get_options() {
-        $options = parent::get_options();
-        $options['cohortid'] = $this->cohortid;
-        $options['file'] = 'cohort/lib.php';
-        return $options;
-    }
+    return array('totalcohorts' => $totalcohorts, 'cohorts' => $cohorts, 'allcohorts'=>$allcohorts);
 }
diff --git a/cohort/locallib.php b/cohort/locallib.php
new file mode 100644 (file)
index 0000000..b257223
--- /dev/null
@@ -0,0 +1,155 @@
+<?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/>.
+
+/**
+ * Cohort UI related functions and classes.
+ *
+ * @package    core_cohort
+ * @copyright  2012 Petr Skoda  {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/cohort/lib.php');
+require_once($CFG->dirroot . '/user/selector/lib.php');
+
+
+/**
+ * Cohort assignment candidates
+ */
+class cohort_candidate_selector extends user_selector_base {
+    protected $cohortid;
+
+    public function __construct($name, $options) {
+        $this->cohortid = $options['cohortid'];
+        parent::__construct($name, $options);
+    }
+
+    /**
+     * Candidate users
+     * @param string $search
+     * @return array
+     */
+    public function find_users($search) {
+        global $DB;
+        // By default wherecondition retrieves all users except the deleted, not confirmed and guest.
+        list($wherecondition, $params) = $this->search_sql($search, 'u');
+        $params['cohortid'] = $this->cohortid;
+
+        $fields      = 'SELECT ' . $this->required_fields_sql('u');
+        $countfields = 'SELECT COUNT(1)';
+
+        $sql = " FROM {user} u
+            LEFT JOIN {cohort_members} cm ON (cm.userid = u.id AND cm.cohortid = :cohortid)
+                WHERE cm.id IS NULL AND $wherecondition";
+
+        $order = ' ORDER BY u.lastname ASC, u.firstname ASC';
+
+        if (!$this->is_validating()) {
+            $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
+            if ($potentialmemberscount > 100) {
+                return $this->too_many_results($search, $potentialmemberscount);
+            }
+        }
+
+        $availableusers = $DB->get_records_sql($fields . $sql . $order, $params);
+
+        if (empty($availableusers)) {
+            return array();
+        }
+
+
+        if ($search) {
+            $groupname = get_string('potusersmatching', 'cohort', $search);
+        } else {
+            $groupname = get_string('potusers', 'cohort');
+        }
+
+        return array($groupname => $availableusers);
+    }
+
+    protected function get_options() {
+        $options = parent::get_options();
+        $options['cohortid'] = $this->cohortid;
+        $options['file'] = 'cohort/locallib.php';
+        return $options;
+    }
+}
+
+
+/**
+ * Cohort assignment candidates
+ */
+class cohort_existing_selector extends user_selector_base {
+    protected $cohortid;
+
+    public function __construct($name, $options) {
+        $this->cohortid = $options['cohortid'];
+        parent::__construct($name, $options);
+    }
+
+    /**
+     * Candidate users
+     * @param string $search
+     * @return array
+     */
+    public function find_users($search) {
+        global $DB;
+        // By default wherecondition retrieves all users except the deleted, not confirmed and guest.
+        list($wherecondition, $params) = $this->search_sql($search, 'u');
+        $params['cohortid'] = $this->cohortid;
+
+        $fields      = 'SELECT ' . $this->required_fields_sql('u');
+        $countfields = 'SELECT COUNT(1)';
+
+        $sql = " FROM {user} u
+                 JOIN {cohort_members} cm ON (cm.userid = u.id AND cm.cohortid = :cohortid)
+                WHERE $wherecondition";
+
+        $order = ' ORDER BY u.lastname ASC, u.firstname ASC';
+
+        if (!$this->is_validating()) {
+            $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
+            if ($potentialmemberscount > 100) {
+                return $this->too_many_results($search, $potentialmemberscount);
+            }
+        }
+
+        $availableusers = $DB->get_records_sql($fields . $sql . $order, $params);
+
+        if (empty($availableusers)) {
+            return array();
+        }
+
+
+        if ($search) {
+            $groupname = get_string('currentusersmatching', 'cohort', $search);
+        } else {
+            $groupname = get_string('currentusers', 'cohort');
+        }
+
+        return array($groupname => $availableusers);
+    }
+
+    protected function get_options() {
+        $options = parent::get_options();
+        $options['cohortid'] = $this->cohortid;
+        $options['file'] = 'cohort/locallib.php';
+        return $options;
+    }
+}
+
diff --git a/cohort/tests/cohortlib_test.php b/cohort/tests/cohortlib_test.php
new file mode 100644 (file)
index 0000000..db0b2f0
--- /dev/null
@@ -0,0 +1,313 @@
+<?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/>.
+
+/**
+ * Cohort library tests.
+ *
+ * @package    core_cohort
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once("$CFG->dirroot/cohort/lib.php");
+
+
+/**
+ * Cohort library tests.
+ *
+ * @package    core_cohort
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cohort_testcase extends advanced_testcase {
+
+    public function test_cohort_add_cohort() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $cohort = new stdClass();
+        $cohort->contextid = context_system::instance()->id;
+        $cohort->name = 'test cohort';
+        $cohort->idnumber = 'testid';
+        $cohort->description = 'test cohort desc';
+        $cohort->descriptionformat = FORMAT_HTML;
+
+        $id = cohort_add_cohort($cohort);
+        $this->assertNotEmpty($id);
+
+        $newcohort = $DB->get_record('cohort', array('id'=>$id));
+        $this->assertEquals($cohort->contextid, $newcohort->contextid);
+        $this->assertSame($cohort->name, $newcohort->name);
+        $this->assertSame($cohort->description, $newcohort->description);
+        $this->assertEquals($cohort->descriptionformat, $newcohort->descriptionformat);
+        $this->assertNotEmpty($newcohort->timecreated);
+        $this->assertSame($newcohort->component, '');
+        $this->assertSame($newcohort->timecreated, $newcohort->timemodified);
+
+        try {
+            $cohort = new stdClass();
+            $cohort->contextid = context_system::instance()->id;
+            $cohort->name = null;
+            $cohort->idnumber = 'testid';
+            $cohort->description = 'test cohort desc';
+            $cohort->descriptionformat = FORMAT_HTML;
+            cohort_add_cohort($cohort);
+
+            $this->fail('Exception expected when trying to add cohort without name');
+        } catch (Exception $e) {
+            $this->assertInstanceOf('coding_exception', $e);
+        }
+    }
+
+    public function test_cohort_update_cohort() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $cohort = new stdClass();
+        $cohort->contextid = context_system::instance()->id;
+        $cohort->name = 'test cohort';
+        $cohort->idnumber = 'testid';
+        $cohort->description = 'test cohort desc';
+        $cohort->descriptionformat = FORMAT_HTML;
+        $id = cohort_add_cohort($cohort);
+        $this->assertNotEmpty($id);
+        $DB->set_field('cohort', 'timecreated', $cohort->timecreated - 10, array('id'=>$id));
+        $DB->set_field('cohort', 'timemodified', $cohort->timemodified - 10, array('id'=>$id));
+        $cohort = $DB->get_record('cohort', array('id'=>$id));
+
+        $cohort->name = 'test cohort 2';
+        cohort_update_cohort($cohort);
+
+        $newcohort = $DB->get_record('cohort', array('id'=>$id));
+
+        $this->assertSame($cohort->contextid, $newcohort->contextid);
+        $this->assertSame($cohort->name, $newcohort->name);
+        $this->assertSame($cohort->description, $newcohort->description);
+        $this->assertSame($cohort->descriptionformat, $newcohort->descriptionformat);
+        $this->assertSame($cohort->timecreated, $newcohort->timecreated);
+        $this->assertSame($cohort->component, $newcohort->component);
+        $this->assertGreaterThan($newcohort->timecreated, $newcohort->timemodified);
+        $this->assertLessThanOrEqual(time(), $newcohort->timemodified);
+    }
+
+    public function test_cohort_delete_cohort() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $cohort = $this->getDataGenerator()->create_cohort();
+
+        cohort_delete_cohort($cohort);
+
+        $this->assertFalse($DB->record_exists('cohort', array('id'=>$cohort->id)));
+    }
+
+    public function test_cohort_delete_category() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $category = $this->getDataGenerator()->create_category();
+
+        $cohort = $this->getDataGenerator()->create_cohort(array('contextid'=>context_coursecat::instance($category->id)->id));
+
+        cohort_delete_category($category);
+
+        $this->assertTrue($DB->record_exists('cohort', array('id'=>$cohort->id)));
+        $newcohort = $DB->get_record('cohort', array('id'=>$cohort->id));
+        $this->assertEquals(context_system::instance()->id, $newcohort->contextid);
+    }
+
+    public function test_cohort_add_member() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $cohort = $this->getDataGenerator()->create_cohort();
+        $user = $this->getDataGenerator()->create_user();
+
+        $this->assertFalse($DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$user->id)));
+        cohort_add_member($cohort->id, $user->id);
+        $this->assertTrue($DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$user->id)));
+    }
+
+    public function test_cohort_remove_member() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $cohort = $this->getDataGenerator()->create_cohort();
+        $user = $this->getDataGenerator()->create_user();
+
+        cohort_add_member($cohort->id, $user->id);
+        $this->assertTrue($DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$user->id)));
+
+        cohort_remove_member($cohort->id, $user->id);
+        $this->assertFalse($DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$user->id)));
+    }
+
+    public function test_cohort_is_member() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $cohort = $this->getDataGenerator()->create_cohort();
+        $user = $this->getDataGenerator()->create_user();
+
+        $this->assertFalse(cohort_is_member($cohort->id, $user->id));
+        cohort_add_member($cohort->id, $user->id);
+        $this->assertTrue(cohort_is_member($cohort->id, $user->id));
+    }
+
+    public function test_cohort_get_visible_list() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $category1 = $this->getDataGenerator()->create_category();
+        $category2 = $this->getDataGenerator()->create_category();
+
+        $course1 = $this->getDataGenerator()->create_course(array('category'=>$category1->id));
+        $course2 = $this->getDataGenerator()->create_course(array('category'=>$category2->id));
+        $course3 = $this->getDataGenerator()->create_course();
+
+        $cohort1 = $this->getDataGenerator()->create_cohort(array('contextid'=>context_coursecat::instance($category1->id)->id));
+        $cohort2 = $this->getDataGenerator()->create_cohort(array('contextid'=>context_coursecat::instance($category2->id)->id));
+        $cohort3 = $this->getDataGenerator()->create_cohort(array('contextid'=>context_system::instance()->id));
+        $cohort4 = $this->getDataGenerator()->create_cohort(array('contextid'=>context_system::instance()->id));
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $user4 = $this->getDataGenerator()->create_user();
+        $user5 = $this->getDataGenerator()->create_user();
+
+        $manualenrol = enrol_get_plugin('manual');
+        $enrol1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'manual'));
+        $enrol2 = $DB->get_record('enrol', array('courseid'=>$course2->id, 'enrol'=>'manual'));
+
+        $manualenrol->enrol_user($enrol1, $user1->id);
+        $manualenrol->enrol_user($enrol1, $user3->id);
+        $manualenrol->enrol_user($enrol1, $user4->id);
+        $manualenrol->enrol_user($enrol2, $user2->id);
+
+        cohort_add_member($cohort1->id, $user1->id);
+        cohort_add_member($cohort3->id, $user1->id);
+        cohort_add_member($cohort1->id, $user3->id);
+        cohort_add_member($cohort2->id, $user2->id);
+
+        $list = cohort_get_visible_list($course1);
+        $this->assertEquals(2, count($list));
+        $this->assertNotEmpty($list[$cohort1->id]);
+        $this->assertRegExp('/\(2\)$/', $list[$cohort1->id]);
+        $this->assertNotEmpty($list[$cohort3->id]);
+        $this->assertRegExp('/\(1\)$/', $list[$cohort3->id]);
+
+        $list = cohort_get_visible_list($course1, false);
+        $this->assertEquals(3, count($list));
+        $this->assertNotEmpty($list[$cohort1->id]);
+        $this->assertRegExp('/\(2\)$/', $list[$cohort1->id]);
+        $this->assertNotEmpty($list[$cohort3->id]);
+        $this->assertRegExp('/\(1\)$/', $list[$cohort3->id]);
+        $this->assertNotEmpty($list[$cohort4->id]);
+        $this->assertRegExp('/[^\)]$/', $list[$cohort4->id]);
+
+        $list = cohort_get_visible_list($course2);
+        $this->assertEquals(1, count($list));
+        $this->assertNotEmpty($list[$cohort2->id]);
+        $this->assertRegExp('/\(1\)$/', $list[$cohort2->id]);
+
+        $list = cohort_get_visible_list($course2, false);
+        $this->assertEquals(3, count($list));
+        $this->assertNotEmpty($list[$cohort2->id]);
+        $this->assertRegExp('/\(1\)$/', $list[$cohort2->id]);
+        $this->assertNotEmpty($list[$cohort3->id]);
+        $this->assertRegExp('/[^\)]$/', $list[$cohort3->id]);
+        $this->assertNotEmpty($list[$cohort4->id]);
+        $this->assertRegExp('/[^\)]$/', $list[$cohort4->id]);
+
+        $list = cohort_get_visible_list($course3);
+        $this->assertEquals(0, count($list));
+
+        $list = cohort_get_visible_list($course3, false);
+        $this->assertEquals(2, count($list));
+        $this->assertNotEmpty($list[$cohort3->id]);
+        $this->assertRegExp('/[^\)]$/', $list[$cohort3->id]);
+        $this->assertNotEmpty($list[$cohort4->id]);
+        $this->assertRegExp('/[^\)]$/', $list[$cohort4->id]);
+    }
+
+    public function test_cohort_get_cohorts() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $category1 = $this->getDataGenerator()->create_category();
+        $category2 = $this->getDataGenerator()->create_category();
+
+        $cohort1 = $this->getDataGenerator()->create_cohort(array('contextid'=>context_coursecat::instance($category1->id)->id, 'name'=>'aaagrrryyy', 'idnumber'=>'','description'=>''));
+        $cohort2 = $this->getDataGenerator()->create_cohort(array('contextid'=>context_coursecat::instance($category1->id)->id, 'name'=>'bbb', 'idnumber'=>'', 'description'=>'yyybrrr'));
+        $cohort3 = $this->getDataGenerator()->create_cohort(array('contextid'=>context_coursecat::instance($category1->id)->id, 'name'=>'ccc', 'idnumber'=>'xxarrrghyyy', 'description'=>'po_us'));
+        $cohort4 = $this->getDataGenerator()->create_cohort(array('contextid'=>context_system::instance()->id));
+
+        $result = cohort_get_cohorts(context_coursecat::instance($category2->id)->id);
+        $this->assertEquals(0, $result['totalcohorts']);
+        $this->assertEquals(0, count($result['cohorts']));
+        $this->assertEquals(0, $result['allcohorts']);
+
+        $result = cohort_get_cohorts(context_coursecat::instance($category1->id)->id);
+        $this->assertEquals(3, $result['totalcohorts']);
+        $this->assertEquals(array($cohort1->id=>$cohort1, $cohort2->id=>$cohort2, $cohort3->id=>$cohort3), $result['cohorts']);
+        $this->assertEquals(3, $result['allcohorts']);
+
+        $result = cohort_get_cohorts(context_coursecat::instance($category1->id)->id, 0, 100, 'arrrgh');
+        $this->assertEquals(1, $result['totalcohorts']);
+        $this->assertEquals(array($cohort3->id=>$cohort3), $result['cohorts']);
+        $this->assertEquals(3, $result['allcohorts']);
+
+        $result = cohort_get_cohorts(context_coursecat::instance($category1->id)->id, 0, 100, 'brrr');
+        $this->assertEquals(1, $result['totalcohorts']);
+        $this->assertEquals(array($cohort2->id=>$cohort2), $result['cohorts']);
+        $this->assertEquals(3, $result['allcohorts']);
+
+        $result = cohort_get_cohorts(context_coursecat::instance($category1->id)->id, 0, 100, 'grrr');
+        $this->assertEquals(1, $result['totalcohorts']);
+        $this->assertEquals(array($cohort1->id=>$cohort1), $result['cohorts']);
+        $this->assertEquals(3, $result['allcohorts']);
+
+        $result = cohort_get_cohorts(context_coursecat::instance($category1->id)->id, 1, 1, 'yyy');
+        $this->assertEquals(3, $result['totalcohorts']);
+        $this->assertEquals(array($cohort2->id=>$cohort2), $result['cohorts']);
+        $this->assertEquals(3, $result['allcohorts']);
+
+        $result = cohort_get_cohorts(context_coursecat::instance($category1->id)->id, 0, 100, 'po_us');
+        $this->assertEquals(1, $result['totalcohorts']);
+        $this->assertEquals(array($cohort3->id=>$cohort3), $result['cohorts']);
+        $this->assertEquals(3, $result['allcohorts']);
+
+        $result = cohort_get_cohorts(context_coursecat::instance($category1->id)->id, 0, 100, 'pokus');
+        $this->assertEquals(0, $result['totalcohorts']);
+        $this->assertEquals(array(), $result['cohorts']);
+        $this->assertEquals(3, $result['allcohorts']);
+    }
+}
index f88d867..1b822cc 100644 (file)
@@ -27,6 +27,7 @@
 
 require_once("../config.php");
 require_once($CFG->dirroot.'/course/lib.php');
+require_once($CFG->libdir.'/textlib.class.php');
 
 $id = required_param('id', PARAM_INT); // Category id
 $page = optional_param('page', 0, PARAM_INT); // which page to show
@@ -75,7 +76,8 @@ $sesskeyprovided = !empty($sesskey) && confirm_sesskey($sesskey);
 // Process any category actions.
 if ($canmanage && $resort && $sesskeyprovided) {
     // Resort the category if requested
-    if ($courses = get_courses($category->id, "fullname ASC", 'c.id,c.fullname,c.sortorder')) {
+    if ($courses = get_courses($category->id, '', 'c.id,c.fullname,c.sortorder')) {
+        collatorlib::asort_objects_by_property($courses, 'fullname', collatorlib::SORT_NATURAL);
         $i = 1;
         foreach ($courses as $course) {
             $DB->set_field('course', 'sortorder', $category->sortorder+$i, array('id'=>$course->id));
index 355e790..5f94469 100644 (file)
@@ -156,7 +156,7 @@ class course_edit_form extends moodleform {
         $mform->addHelpButton('showreports', 'showreports');
         $mform->setDefault('showreports', $courseconfig->showreports);
 
-        $choices = get_max_upload_sizes($CFG->maxbytes);
+        $choices = get_max_upload_sizes($CFG->maxbytes, 0, 0, $course->maxbytes);
         $mform->addElement('select', 'maxbytes', get_string('maximumupload'), $choices);
         $mform->addHelpButton('maxbytes', 'maximumupload');
         $mform->setDefault('maxbytes', $courseconfig->maxbytes);
index f410e07..28ec82a 100644 (file)
@@ -36,7 +36,7 @@ M.course.format.get_config = function() {
 M.course.format.swap_sections = function(Y, node1, node2) {
     var CSS = {
         COURSECONTENT : 'course-content',
-        SECTIONADDMENUS : 'section_add_menus',
+        SECTIONADDMENUS : 'section_add_menus'
     };
 
     var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.course.format.get_section_selector(Y));
index 42b8057..0004f7d 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+global $CFG;
+require_once($CFG->dirroot.'/course/lib.php');
 
 class courselib_testcase extends advanced_testcase {
 
+    public function test_create_course() {
+        global $DB;
+        $this->resetAfterTest(true);
+        $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
+
+        $course = new stdClass();
+        $course->fullname = 'Apu loves Unit Təsts';
+        $course->shortname = 'Spread the lŭve';
+        $course->idnumber = '123';
+        $course->summary = 'Awesome!';
+        $course->summaryformat = FORMAT_PLAIN;
+        $course->format = 'topics';
+        $course->newsitems = 0;
+        $course->numsections = 5;
+        $course->category = $defaultcategory;
+
+        $created = create_course($course);
+        $context = context_course::instance($created->id);
+
+        // Compare original and created.
+        $original = (array) $course;
+        $this->assertEquals($original, array_intersect_key((array) $created, $original));
+
+        // Ensure default section is created.
+        $sectioncreated = $DB->record_exists('course_sections', array('course' => $created->id, 'section' => 0));
+        $this->assertTrue($sectioncreated);
+
+        // Ensure blocks have been associated to the course.
+        $blockcount = $DB->count_records('block_instances', array('parentcontextid' => $context->id));
+        $this->assertGreaterThan(0, $blockcount);
+    }
+
     public function test_reorder_sections() {
         global $DB;
         $this->resetAfterTest(true);
diff --git a/course/tests/courserequest_test.php b/course/tests/courserequest_test.php
new file mode 100644 (file)
index 0000000..d376772
--- /dev/null
@@ -0,0 +1,148 @@
+<?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/>.
+
+/**
+ * Course request related unit tests
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Frédéric Massart
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot.'/course/lib.php');
+
+class courserequest_testcase extends advanced_testcase {
+
+    public function test_create_request() {
+        global $DB, $USER;
+        $this->resetAfterTest(true);
+
+        $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
+        set_config('enablecourserequests', 1);
+        set_config('requestcategoryselection', 0);
+        set_config('defaultrequestcategory', $defaultcategory);
+
+        // Create some categories.
+        $cat1 = $this->getDataGenerator()->create_category();
+        $cat2 = $this->getDataGenerator()->create_category();
+        $cat3 = $this->getDataGenerator()->create_category();
+
+        // Basic course request.
+        $data = new stdClass();
+        $data->fullname = 'Həllo World!';
+        $data->shortname = 'Hi th€re!';
+        $data->summary_editor['text'] = 'Lorem Ipsum ©';
+        $data->summary_editor['format'] = FORMAT_HTML;
+        $data->reason = 'Because PHP Unit is cool.';
+        $cr = course_request::create($data);
+
+        $this->assertEquals($data->fullname, $cr->fullname);
+        $this->assertEquals($data->shortname, $cr->shortname);
+        $this->assertEquals($data->summary_editor['text'], $cr->summary);
+        $this->assertEquals($data->summary_editor['format'], $cr->summaryformat);
+        $this->assertEquals($data->reason, $cr->reason);
+        $this->assertEquals($USER->id, $cr->requester);
+        $this->assertEquals($defaultcategory, $cr->category);
+
+        // Request with category but category selection not allowed.
+        set_config('defaultrequestcategory', $cat2->id);
+        $data->category = $cat1->id;
+        $cr = course_request::create($data);
+        $this->assertEquals($cat2->id, $cr->category);
+
+        // Request with category different than default and category selection allowed.
+        set_config('defaultrequestcategory', $cat3->id);
+        set_config('requestcategoryselection', 1);
+        $data->category = $cat1->id;
+        $cr = course_request::create($data);
+        $this->assertEquals($cat1->id, $cr->category);
+    }
+
+    public function test_approve_request() {
+        global $DB;
+        $this->resetAfterTest(true);
+        $this->preventResetByRollback();
+
+        $this->setAdminUser();
+        $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
+        set_config('enablecourserequests', 1);
+        set_config('requestcategoryselection', 0);
+        set_config('defaultrequestcategory', $defaultcategory);
+
+        // Create some categories.
+        $cat1 = $this->getDataGenerator()->create_category();
+        $cat2 = $this->getDataGenerator()->create_category();
+
+        $data = new stdClass();
+        $data->fullname = 'Həllo World!';
+        $data->shortname = 'Hi th€re!';
+        $data->summary_editor['text'] = 'Lorem Ipsum ©';
+        $data->summary_editor['format'] = FORMAT_HTML;
+        $data->reason = 'Because PHP Unit is cool.';
+
+        // Test without category.
+        $cr = course_request::create($data);
+        $id = $cr->approve();
+        $this->assertDebuggingCalled(); // Caused by sending of message.
+        $course = $DB->get_record('course', array('id' => $id));
+        $this->assertEquals($data->fullname, $course->fullname);
+        $this->assertEquals($data->shortname, $course->shortname);
+        $this->assertEquals($data->summary_editor['text'], $course->summary);
+        $this->assertEquals($data->summary_editor['format'], $course->summaryformat);
+        $this->assertEquals(1, $course->requested);
+        $this->assertEquals($defaultcategory, $course->category);
+
+        // Test with category.
+        set_config('requestcategoryselection', 1);
+        set_config('defaultrequestcategory', $cat2->id);
+        $data->shortname .= ' 2nd';
+        $data->category = $cat1->id;
+        $cr = course_request::create($data);
+        $id = $cr->approve();
+        $this->assertDebuggingCalled(); // Caused by sending of message.
+        $course = $DB->get_record('course', array('id' => $id));
+        $this->assertEquals($data->category, $course->category);
+    }
+
+    public function test_reject_request() {
+        global $DB;
+        $this->resetAfterTest(true);
+        $this->preventResetByRollback();
+        $this->setAdminUser();
+        set_config('enablecourserequests', 1);
+        set_config('requestcategoryselection', 0);
+        set_config('defaultrequestcategory', $DB->get_field_select('course_categories', "MIN(id)", "parent=0"));
+
+        $data = new stdClass();
+        $data->fullname = 'Həllo World!';
+        $data->shortname = 'Hi th€re!';
+        $data->summary_editor['text'] = 'Lorem Ipsum ©';
+        $data->summary_editor['format'] = FORMAT_HTML;
+        $data->reason = 'Because PHP Unit is cool.';
+
+        $cr = course_request::create($data);
+        $this->assertTrue($DB->record_exists('course_request', array('id' => $cr->id)));
+        $cr->reject('Sorry!');
+        $this->assertDebuggingCalled(); // Caused by sending of message.
+        $this->assertFalse($DB->record_exists('course_request', array('id' => $cr->id)));
+    }
+
+}
index 08f8b4a..984b9ed 100644 (file)
@@ -165,20 +165,28 @@ class core_course_external_testcase extends externallib_advanced_testcase {
         global $DB;
 
         $this->resetAfterTest(true);
+
+        $generatedcats = array();
         $category1data['idnumber'] = 'idnumbercat1';
         $category1data['name'] = 'Category 1 for PHPunit test';
         $category1data['description'] = 'Category 1 description';
         $category1data['descriptionformat'] = FORMAT_MOODLE;
         $category1  = self::getDataGenerator()->create_category($category1data);
+        $generatedcats[$category1->id] = $category1;
         $category2  = self::getDataGenerator()->create_category(
                 array('parent' => $category1->id));
+        $generatedcats[$category2->id] = $category2;
         $category6  = self::getDataGenerator()->create_category(
                 array('parent' => $category1->id, 'visible' => 0));
+        $generatedcats[$category6->id] = $category6;
         $category3  = self::getDataGenerator()->create_category();
+        $generatedcats[$category3->id] = $category3;
         $category4  = self::getDataGenerator()->create_category(
                 array('parent' => $category3->id));
+        $generatedcats[$category4->id] = $category4;
         $category5  = self::getDataGenerator()->create_category(
                 array('parent' => $category4->id));
+        $generatedcats[$category5->id] = $category5;
 
         // Set the required capabilities by the external function.
         $context = context_system::instance();
@@ -193,11 +201,13 @@ class core_course_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals(2, count($categories));
 
         // Check the return values
-        $this->assertEquals($categories[0]['id'], $category1->id);
-        $this->assertEquals($categories[0]['idnumber'], $category1->idnumber);
-        $this->assertEquals($categories[0]['name'], $category1->name);
-        $this->assertEquals($categories[0]['description'], $category1->description);
-        $this->assertEquals($categories[0]['descriptionformat'], FORMAT_HTML);
+        foreach ($categories as $category) {
+            $generatedcat = $generatedcats[$category['id']];
+            $this->assertEquals($category['idnumber'], $generatedcat->idnumber);
+            $this->assertEquals($category['name'], $generatedcat->name);
+            $this->assertEquals($category['description'], $generatedcat->description);
+            $this->assertEquals($category['descriptionformat'], FORMAT_HTML);
+        }
 
         // Check different params.
         $categories = core_course_external::get_categories(array(
@@ -440,13 +450,17 @@ class core_course_external_testcase extends externallib_advanced_testcase {
 
         $this->resetAfterTest(true);
 
+        $generatedcourses = array();
         $coursedata['idnumber'] = 'idnumbercourse1';
         $coursedata['fullname'] = 'Course 1 for PHPunit test';
         $coursedata['summary'] = 'Course 1 description';
         $coursedata['summaryformat'] = FORMAT_MOODLE;
         $course1  = self::getDataGenerator()->create_course($coursedata);
+        $generatedcourses[$course1->id] = $course1;
         $course2  = self::getDataGenerator()->create_course();
+        $generatedcourses[$course2->id] = $course2;
         $course3  = self::getDataGenerator()->create_course();
+        $generatedcourses[$course3->id] = $course3;
 
         // Set the required capabilities by the external function.
         $context = context_system::instance();
@@ -464,33 +478,33 @@ class core_course_external_testcase extends externallib_advanced_testcase {
         // Check we retrieve the good total number of categories.
         $this->assertEquals(2, count($courses));
 
-        // Check the return values for course 1
-        $dbcourse = $DB->get_record('course', array('id' => $course1->id));
-        $this->assertEquals($courses[0]['id'], $dbcourse->id);
-        $this->assertEquals($courses[0]['idnumber'], $coursedata['idnumber']);
-        $this->assertEquals($courses[0]['fullname'], $coursedata['fullname']);
-        $this->assertEquals($courses[0]['summary'], $coursedata['summary']);
-        $this->assertEquals($courses[0]['summaryformat'], FORMAT_HTML);
-        $this->assertEquals($courses[0]['shortname'], $dbcourse->shortname);
-        $this->assertEquals($courses[0]['categoryid'], $dbcourse->category);
-        $this->assertEquals($courses[0]['format'], $dbcourse->format);
-        $this->assertEquals($courses[0]['showgrades'], $dbcourse->showgrades);
-        $this->assertEquals($courses[0]['newsitems'], $dbcourse->newsitems);
-        $this->assertEquals($courses[0]['startdate'], $dbcourse->startdate);
-        $this->assertEquals($courses[0]['numsections'], $dbcourse->numsections);
-        $this->assertEquals($courses[0]['maxbytes'], $dbcourse->maxbytes);
-        $this->assertEquals($courses[0]['showreports'], $dbcourse->showreports);
-        $this->assertEquals($courses[0]['visible'], $dbcourse->visible);
-        $this->assertEquals($courses[0]['hiddensections'], $dbcourse->hiddensections);
-        $this->assertEquals($courses[0]['groupmode'], $dbcourse->groupmode);
-        $this->assertEquals($courses[0]['groupmodeforce'], $dbcourse->groupmodeforce);
-        $this->assertEquals($courses[0]['defaultgroupingid'], $dbcourse->defaultgroupingid);
-        $this->assertEquals($courses[0]['completionnotify'], $dbcourse->completionnotify);
-        $this->assertEquals($courses[0]['lang'], $dbcourse->lang);
-        $this->assertEquals($courses[0]['forcetheme'], $dbcourse->theme);
-        $this->assertEquals($courses[0]['completionstartonenrol'], $dbcourse->completionstartonenrol);
-        $this->assertEquals($courses[0]['enablecompletion'], $dbcourse->enablecompletion);
-        $this->assertEquals($courses[0]['completionstartonenrol'], $dbcourse->completionstartonenrol);
+        foreach ($courses as $course) {
+            $dbcourse = $generatedcourses[$course['id']];
+            $this->assertEquals($course['idnumber'], $dbcourse->idnumber);
+            $this->assertEquals($course['fullname'], $dbcourse->fullname);
+            $this->assertEquals($course['summary'], $dbcourse->summary);
+            $this->assertEquals($course['summaryformat'], FORMAT_HTML);
+            $this->assertEquals($course['shortname'], $dbcourse->shortname);
+            $this->assertEquals($course['categoryid'], $dbcourse->category);
+            $this->assertEquals($course['format'], $dbcourse->format);
+            $this->assertEquals($course['showgrades'], $dbcourse->showgrades);
+            $this->assertEquals($course['newsitems'], $dbcourse->newsitems);
+            $this->assertEquals($course['startdate'], $dbcourse->startdate);
+            $this->assertEquals($course['numsections'], $dbcourse->numsections);
+            $this->assertEquals($course['maxbytes'], $dbcourse->maxbytes);
+            $this->assertEquals($course['showreports'], $dbcourse->showreports);
+            $this->assertEquals($course['visible'], $dbcourse->visible);
+            $this->assertEquals($course['hiddensections'], $dbcourse->hiddensections);
+            $this->assertEquals($course['groupmode'], $dbcourse->groupmode);
+            $this->assertEquals($course['groupmodeforce'], $dbcourse->groupmodeforce);
+            $this->assertEquals($course['defaultgroupingid'], $dbcourse->defaultgroupingid);
+            $this->assertEquals($course['completionnotify'], $dbcourse->completionnotify);
+            $this->assertEquals($course['lang'], $dbcourse->lang);
+            $this->assertEquals($course['forcetheme'], $dbcourse->theme);
+            $this->assertEquals($course['completionstartonenrol'], $dbcourse->completionstartonenrol);
+            $this->assertEquals($course['enablecompletion'], $dbcourse->enablecompletion);
+            $this->assertEquals($course['completionstartonenrol'], $dbcourse->completionstartonenrol);
+        }
 
         // Get all courses in the DB
         $courses = core_course_external::get_courses(array());
index e6525e3..4fed790 100644 (file)
@@ -92,4 +92,15 @@ class enrol_category_plugin extends enrol_plugin {
         require_once("$CFG->dirroot/enrol/category/locallib.php");
         enrol_category_sync_course($course);
     }
+
+    /**
+     * Automatic enrol sync executed during restore.
+     * Useful for automatic sync by course->idnumber or course category.
+     * @param stdClass $course course record
+     */
+    public function restore_sync_course($course) {
+        global $CFG;
+        require_once("$CFG->dirroot/enrol/category/locallib.php");
+        enrol_category_sync_course($course);
+    }
 }
index ebb06fc..eacef31 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -18,8 +17,7 @@
 /**
  * Adds new instance of enrol_cohort to specified course.
  *
- * @package    enrol
- * @subpackage cohort
+ * @package    enrol_cohort
  * @copyright  2010 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -28,7 +26,7 @@ require('../../config.php');
 require_once("$CFG->dirroot/enrol/cohort/addinstance_form.php");
 require_once("$CFG->dirroot/enrol/cohort/locallib.php");
 
-$id = required_param('id', PARAM_INT); // course id
+$id = required_param('id', PARAM_INT); // Course id.
 
 $course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
 $context = context_course::instance($course->id, MUST_EXIST);
@@ -42,7 +40,7 @@ $PAGE->set_pagelayout('admin');
 
 navigation_node::override_active_url(new moodle_url('/enrol/instances.php', array('id'=>$course->id)));
 
-// Try and make the manage instances node on the navigation active
+// Try and make the manage instances node on the navigation active.
 $courseadmin = $PAGE->settingsnav->get('courseadmin');
 if ($courseadmin && $courseadmin->get('users') && $courseadmin->get('users')->get('manageinstances')) {
     $courseadmin->get('users')->get('manageinstances')->make_active();
index c209493..9658b49 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -18,8 +17,7 @@
 /**
  * Adds instance form
  *
- * @package    enrol
- * @subpackage cohort
+ * @package    enrol_cohort
  * @copyright  2010 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -29,17 +27,20 @@ defined('MOODLE_INTERNAL') || die();
 require_once("$CFG->libdir/formslib.php");
 
 class enrol_cohort_addinstance_form extends moodleform {
+
+    protected $course;
+
     function definition() {
         global $CFG, $DB;
 
         $mform  = $this->_form;
-        $course = $this->_customdata;
-        $coursecontext = context_course::instance($course->id);
+        $this->course = $this->_customdata;
+        $coursecontext = context_course::instance($this->course->id);
 
         $enrol = enrol_get_plugin('cohort');
 
         $cohorts = array('' => get_string('choosedots'));
-        list($sqlparents, $params) = $DB->get_in_or_equal(get_parent_contexts($coursecontext));
+        list($sqlparents, $params) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids());
         $sql = "SELECT id, name, contextid
                   FROM {cohort}
                  WHERE contextid $sqlparents
@@ -56,7 +57,7 @@ class enrol_cohort_addinstance_form extends moodleform {
 
         $roles = get_assignable_roles($coursecontext);
         $roles[0] = get_string('none');
-        $roles = array_reverse($roles, true); // descending default sortorder
+        $roles = array_reverse($roles, true); // Descending default sortorder.
 
         $mform->addElement('header','general', get_string('pluginname', 'enrol_cohort'));
 
@@ -72,8 +73,18 @@ class enrol_cohort_addinstance_form extends moodleform {
 
         $this->add_action_buttons(true, get_string('addinstance', 'enrol'));
 
-        $this->set_data(array('id'=>$course->id));
+        $this->set_data(array('id'=>$this->course->id));
     }
 
-    //TODO: validate duplicate role-cohort does not exist
+    function validation($data, $files) {
+        global $DB;
+
+        $errors = parent::validation($data, $files);
+
+        if ($DB->record_exists('enrol', array('roleid'=>$data['roleid'], 'customint1'=>$data['cohortid'], 'courseid'=>$this->course->id, 'enrol'=>'cohort'))) {
+            $errors['cohortid'] = get_string('instanceexists', 'enrol_cohort');
+        }
+
+        return $errors;
+    }
 }
index b09fd4d..c831ecd 100644 (file)
@@ -20,8 +20,7 @@
  * The general idea behind this file is that any errors should throw exceptions
  * which will be returned and acted upon by the calling AJAX script.
  *
- * @package    enrol
- * @subpackage cohort
+ * @package    enrol_cohort
  * @copyright  2011 Sam Hemelryk
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -33,7 +32,7 @@ require_once($CFG->dirroot.'/enrol/locallib.php');
 require_once($CFG->dirroot.'/enrol/cohort/locallib.php');
 require_once($CFG->dirroot.'/group/lib.php');
 
-// Must have the sesskey
+// Must have the sesskey.
 $id      = required_param('id', PARAM_INT); // course id
 $action  = required_param('action', PARAM_ALPHANUMEXT);
 
@@ -50,7 +49,7 @@ require_login($course);
 require_capability('moodle/course:enrolreview', $context);
 require_sesskey();
 
-echo $OUTPUT->header(); // send headers
+echo $OUTPUT->header(); // Send headers.
 
 $manager = new course_enrolment_manager($PAGE, $course);
 
index 068c18b..4f1cd3b 100644 (file)
@@ -23,8 +23,7 @@
  *   - you need to change the "www-data" to match the apache user account
  *   - use "su" if "sudo" not available
  *
- * @package    enrol
- * @subpackage cohort
+ * @package    enrol_cohort
  * @copyright  2011 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -35,7 +34,7 @@ require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
 require_once($CFG->libdir.'/clilib.php');
 require_once("$CFG->dirroot/enrol/cohort/locallib.php");
 
-// now get cli options
+// Now get cli options.
 list($options, $unrecognized) = cli_get_params(array('verbose'=>false, 'help'=>false), array('v'=>'verbose', 'h'=>'help'));
 
 if ($unrecognized) {
index 98ce7b0..70c5528 100644 (file)
@@ -36,7 +36,7 @@ $capabilities = array(
         )
     ),
 
-    /* This is used only when sync suspends users instead of full unenrolment */
+    /* This is used only when sync suspends users instead of full unenrolment. */
     'enrol/cohort:unenrol' => array(
 
         'captype' => 'write',
@@ -47,8 +47,3 @@ $capabilities = array(
     ),
 
 );
-
-
-
-
-
index 4f26a20..9de7dcf 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-/* List of handlers */
+/* List of handlers. */
 $handlers = array (
     'cohort_member_added' => array (
         'handlerfile'      => '/enrol/cohort/locallib.php',
index 58b0c05..5da20bf 100644 (file)
@@ -17,8 +17,7 @@
 /**
  * Meta link enrolment plugin uninstallation.
  *
- * @package    enrol
- * @subpackage cohort
+ * @package    enrol_cohort
  * @copyright  2011 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -38,4 +37,4 @@ function xmldb_enrol_cohort_uninstall() {
     role_unassign_all(array('component'=>'enrol_cohort'));
 
     return true;
-}
\ No newline at end of file
+}
index 8196bb0..b198b76 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Strings for component 'enrol_cohort', language 'en', branch 'MOODLE_20_STABLE'
+ * Strings for component 'enrol_cohort', language 'en'
  *
- * @package    enrol
- * @subpackage cohort
+ * @package    enrol_cohort
  * @copyright  2010 Petr Skoda  {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -28,5 +26,6 @@ $string['ajaxmore'] = 'More...';
 $string['cohortsearch'] = 'Search';
 $string['cohort:config'] = 'Configure cohort instances';
 $string['cohort:unenrol'] = 'Unenrol suspended users';
+$string['instanceexists'] = 'Cohort is already synchronised with selected role';
 $string['pluginname'] = 'Cohort sync';
 $string['pluginname_desc'] = 'Cohort enrolment plugin synchronises cohort members with course participants.';
index 4ad304e..0621901 100644 (file)
@@ -17,8 +17,7 @@
 /**
  * Cohort enrolment plugin.
  *
- * @package    enrol
- * @subpackage cohort
+ * @package    enrol_cohort
  * @copyright  2010 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -32,9 +31,9 @@ defined('MOODLE_INTERNAL') || die();
  */
 class enrol_cohort_plugin extends enrol_plugin {
     /**
-     * Returns localised name of enrol instance
+     * Returns localised name of enrol instance.
      *
-     * @param object $instance (null is accepted too)
+     * @param stdClass $instance (null is accepted too)
      * @return string
      */
     public function get_instance_name($instance) {
@@ -67,12 +66,12 @@ class enrol_cohort_plugin extends enrol_plugin {
         if (!$this->can_add_new_instances($courseid)) {
             return NULL;
         }
-        // multiple instances supported - multiple parent courses linked
+        // Multiple instances supported - multiple parent courses linked.
         return new moodle_url('/enrol/cohort/addinstance.php', array('id'=>$courseid));
     }
 
     /**
-     * Given a courseid this function returns true if the user is able to enrol or configure cohorts
+     * Given a courseid this function returns true if the user is able to enrol or configure cohorts.
      * AND there are cohorts that the user can view.
      *
      * @param int $courseid
@@ -100,7 +99,6 @@ class enrol_cohort_plugin extends enrol_plugin {
         return false;
     }
 
-
     /**
      * Called for all enabled enrol plugins that returned true from is_cron_required().
      * @return void
@@ -116,8 +114,8 @@ class enrol_cohort_plugin extends enrol_plugin {
      * Called after updating/inserting course.
      *
      * @param bool $inserted true if course just inserted
-     * @param object $course
-     * @param object $data form data
+     * @param stdClass $course
+     * @param stdClass $data form data
      * @return void
      */
     public function course_updated($inserted, $course, $data) {
@@ -158,7 +156,7 @@ class enrol_cohort_plugin extends enrol_plugin {
     }
 
     /**
-     * Gets an array of the user enrolment actions
+     * Gets an array of the user enrolment actions.
      *
      * @param course_enrolment_manager $manager
      * @param stdClass $ue A user enrolment object
@@ -210,7 +208,7 @@ class enrol_cohort_plugin extends enrol_plugin {
         $button->strings_for_js('cohort', 'cohort');
         $button->strings_for_js('users', 'moodle');
 
-        // No point showing this at all if the user cant manually enrol users
+        // No point showing this at all if the user cant manually enrol users.
         $hasmanualinstance = has_capability('enrol/manual:enrol', $manager->get_context()) && $manager->has_instance('manual');
 
         $modules = array('moodle-enrol_cohort-quickenrolment', 'moodle-enrol_cohort-quickenrolment-skin');
@@ -224,6 +222,78 @@ class enrol_cohort_plugin extends enrol_plugin {
 
         return $button;
     }
-}
 
+    /**
+     * Restore instance and map settings.
+     *
+     * @param restore_enrolments_structure_step $step
+     * @param stdClass $data
+     * @param stdClass $course
+     * @param int $oldid
+     */
+    public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
+        global $DB, $CFG;
+
+        if (!$step->get_task()->is_samesite()) {
+            // No cohort restore from other sites.
+            $step->set_mapping('enrol', $oldid, 0);
+            return;
+        }
+
+        if ($data->roleid and $DB->record_exists('cohort', array('id'=>$data->customint1))) {
+            $instance = $DB->get_record('enrol', array('roleid'=>$data->roleid, 'customint1'=>$data->customint1, 'courseid'=>$course->id, 'enrol'=>$this->get_name()));
+            if ($instance) {
+                $instanceid = $instance->id;
+            } else {
+                $instanceid = $this->add_instance($course, (array)$data);
+            }
+            $step->set_mapping('enrol', $oldid, $instanceid);
+
+            require_once("$CFG->dirroot/enrol/cohort/locallib.php");
+            enrol_cohort_sync($course->id, false);
+
+        } else if ($this->get_config('unenrolaction') == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+            $data->customint1 = 0;
+            $instance = $DB->get_record('enrol', array('roleid'=>$data->roleid, 'customint1'=>$data->customint1, 'courseid'=>$course->id, 'enrol'=>$this->get_name()));
+
+            if ($instance) {
+                $instanceid = $instance->id;
+            } else {
+                $data->status = ENROL_INSTANCE_DISABLED;
+                $instanceid = $this->add_instance($course, (array)$data);
+            }
+            $step->set_mapping('enrol', $oldid, $instanceid);
+
+            require_once("$CFG->dirroot/enrol/cohort/locallib.php");
+            enrol_cohort_sync($course->id, false);
 
+        } else {
+            $step->set_mapping('enrol', $oldid, 0);
+        }
+    }
+
+    /**
+     * Restore user enrolment.
+     *
+     * @param restore_enrolments_structure_step $step
+     * @param stdClass $data
+     * @param stdClass $instance
+     * @param int $oldinstancestatus
+     * @param int $userid
+     */
+    public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) {
+        global $DB;
+
+        if ($this->get_config('unenrolaction') != ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+            // Enrolments were already synchronised in restore_instance(), we do not want any suspended leftovers.
+            return;
+        }
+
+        // ENROL_EXT_REMOVED_SUSPENDNOROLES means all previous enrolments are restored
+        // but without roles and suspended.
+
+        if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
+            $this->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, ENROL_USER_SUSPENDED);
+        }
+    }
+}
index 869e06c..d26ee18 100644 (file)
@@ -17,8 +17,7 @@
 /**
  * Local stuff for cohort enrolment plugin.
  *
- * @package    enrol
- * @subpackage cohort
+ * @package    enrol_cohort
  * @copyright  2010 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -36,7 +35,7 @@ require_once($CFG->dirroot . '/enrol/locallib.php');
  */
 class enrol_cohort_handler {
     /**
-     * Event processor - cohort member added
+     * Event processor - cohort member added.
      * @param stdClass $ca
      * @return bool
      */
@@ -47,12 +46,12 @@ class enrol_cohort_handler {
             return true;
         }
 
-        // does any enabled cohort instance want to sync with this cohort?
+        // Does any enabled cohort instance want to sync with this cohort?
         $sql = "SELECT e.*, r.id as roleexists
                   FROM {enrol} e
              LEFT JOIN {role} r ON (r.id = e.roleid)
-                 WHERE customint1 = :cohortid AND enrol = 'cohort'
-              ORDER BY id ASC";
+                 WHERE e.customint1 = :cohortid AND e.enrol = 'cohort'
+              ORDER BY e.id ASC";
         if (!$instances = $DB->get_records_sql($sql, array('cohortid'=>$ca->cohortid))) {
             return true;
         }
@@ -60,14 +59,14 @@ class enrol_cohort_handler {
         $plugin = enrol_get_plugin('cohort');
         foreach ($instances as $instance) {
             if ($instance->status != ENROL_INSTANCE_ENABLED ) {
-                // no roles for disabled instances
+                // No roles for disabled instances.
                 $instance->roleid = 0;
             } else if ($instance->roleid and !$instance->roleexists) {
-                // invalid role - let's just enrol, they will have to create new sync and delete this one
+                // Invalid role - let's just enrol, they will have to create new sync and delete this one.
                 $instance->roleid = 0;
             }
             unset($instance->roleexists);
-            // no problem if already enrolled
+            // No problem if already enrolled.
             $plugin->enrol_user($instance, $ca->userid, $instance->roleid, 0, 0, ENROL_USER_ACTIVE);
         }
 
@@ -75,14 +74,14 @@ class enrol_cohort_handler {
     }
 
     /**
-     * Event processor - cohort member removed
+     * Event processor - cohort member removed.
      * @param stdClass $ca
      * @return bool
      */
     public static function member_removed($ca) {
         global $DB;
 
-        // does anything want to sync with this cohort?
+        // Does anything want to sync with this cohort?
         if (!$instances = $DB->get_records('enrol', array('customint1'=>$ca->cohortid, 'enrol'=>'cohort'), 'id ASC')) {
             return true;
         }
@@ -110,14 +109,14 @@ class enrol_cohort_handler {
     }
 
     /**
-     * Event processor - cohort deleted
+     * Event processor - cohort deleted.
      * @param stdClass $cohort
      * @return bool
      */
     public static function deleted($cohort) {
         global $DB;
 
-        // does anything want to sync with this cohort?
+        // Does anything want to sync with this cohort?
         if (!$instances = $DB->get_records('enrol', array('customint1'=>$cohort->id, 'enrol'=>'cohort'), 'id ASC')) {
             return true;
         }
@@ -149,7 +148,7 @@ class enrol_cohort_handler {
 function enrol_cohort_sync($courseid = NULL, $verbose = false) {
     global $CFG, $DB;
 
-    // purge all roles if cohort sync disabled, those can be recreated later here by cron or CLI
+    // Purge all roles if cohort sync disabled, those can be recreated later here by cron or CLI.
     if (!enrol_is_enabled('cohort')) {
         if ($verbose) {
             mtrace('Cohort sync plugin is disabled, unassigning all plugin roles and stopping.');
@@ -158,7 +157,7 @@ function enrol_cohort_sync($courseid = NULL, $verbose = false) {
         return 2;
     }
 
-    // unfortunately this may take a long time, this script can be interrupted without problems
+    // Unfortunately this may take a long time, this script can be interrupted without problems.
     @set_time_limit(0);
     raise_memory_limit(MEMORY_HUGE);
 
@@ -173,7 +172,7 @@ function enrol_cohort_sync($courseid = NULL, $verbose = false) {
     $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL);
 
 
-    // iterate through all not enrolled yet users
+    // Iterate through all not enrolled yet users.
     $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
     $sql = "SELECT cm.userid, e.id AS enrolid, ue.status
               FROM {cohort_members} cm
@@ -204,7 +203,7 @@ function enrol_cohort_sync($courseid = NULL, $verbose = false) {
     $rs->close();
 
 
-    // unenrol as necessary
+    // Unenrol as necessary.
     $sql = "SELECT ue.*, e.courseid
               FROM {user_enrolments} ue
               JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'cohort' $onecourse)
@@ -217,14 +216,14 @@ function enrol_cohort_sync($courseid = NULL, $verbose = false) {
         }
         $instance = $instances[$ue->enrolid];
         if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
-            // remove enrolment together with group membership, grades, preferences, etc.
+            // Temove enrolment together with group membership, grades, preferences, etc.
             $plugin->unenrol_user($instance, $ue->userid);
             if ($verbose) {
                 mtrace("  unenrolling: $ue->userid ==> $instance->courseid via cohort $instance->customint1");
             }
 
         } else { // ENROL_EXT_REMOVED_SUSPENDNOROLES
-            // just disable and ignore any changes
+            // Just disable and ignore any changes.
             if ($ue->status != ENROL_USER_SUSPENDED) {
                 $plugin->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
                 $context = context_course::instance($instance->courseid);
@@ -239,7 +238,7 @@ function enrol_cohort_sync($courseid = NULL, $verbose = false) {
     unset($instances);
 
 
-    // now assign all necessary roles to enrolled users - skip suspended instances and users
+    // Now assign all necessary roles to enrolled users - skip suspended instances and users.
     $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
     $sql = "SELECT e.roleid, ue.userid, c.id AS contextid, e.id AS itemid, e.courseid
               FROM {user_enrolments} ue
@@ -264,7 +263,7 @@ function enrol_cohort_sync($courseid = NULL, $verbose = false) {
     $rs->close();
 
 
-    // remove unwanted roles - sync role can not be changed, we only remove role when unenrolled
+    // Remove unwanted roles - sync role can not be changed, we only remove role when unenrolled.
     $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
     $sql = "SELECT ra.roleid, ra.userid, ra.contextid, ra.itemid, e.courseid
               FROM {role_assignments} ra
@@ -384,7 +383,7 @@ function enrol_cohort_get_cohorts(course_enrolment_manager $manager) {
 }
 
 /**
- * Check if cohort exists and user is allowed to enrol it
+ * Check if cohort exists and user is allowed to enrol it.
  *
  * @global moodle_database $DB
  * @param int $cohortid Cohort ID
@@ -426,10 +425,10 @@ function enrol_cohort_search_cohorts(course_enrolment_manager $manager, $offset
 
     list($sqlparents, $params) = $DB->get_in_or_equal(get_parent_contexts($context));
 
-    // Add some additional sensible conditions
+    // Add some additional sensible conditions.
     $tests = array('contextid ' . $sqlparents);
 
-    // Modify the query to perform the search if required
+    // Modify the query to perform the search if required.
     if (!empty($search)) {
         $conditions = array(
             'name',
@@ -452,17 +451,17 @@ function enrol_cohort_search_cohorts(course_enrolment_manager $manager, $offset
     $order = ' ORDER BY name ASC';
     $rs = $DB->get_recordset_sql($fields . $sql . $order, $params, $offset);
 
-    // Produce the output respecting parameters
+    // Produce the output respecting parameters.
     foreach ($rs as $c) {
-        // Track offset
+        // Track offset.
         $offset++;
-        // Check capabilities
+        // Check capabilities.
         $context = context::instance_by_id($c->contextid);
         if (!has_capability('moodle/cohort:view', $context)) {
             continue;
         }
         if ($limit === 0) {
-            // we have reached the required number of items and know that there are more, exit now
+            // we have reached the required number of items and know that there are more, exit now.
             $offset--;
             break;
         }
@@ -472,9 +471,9 @@ function enrol_cohort_search_cohorts(course_enrolment_manager $manager, $offset
             'users'=>$DB->count_records('cohort_members', array('cohortid'=>$c->id)),
             'enrolled'=>in_array($c->id, $enrolled)
         );
-        // Count items
+        // Count items.
         $limit--;
     }
     $rs->close();
     return array('more' => !(bool)$limit, 'offset' => $offset, 'cohorts' => $cohorts);
-}
\ No newline at end of file
+}
index db9702a..8c08a74 100644 (file)
@@ -17,8 +17,7 @@
 /**
  * Cohort enrolment plugin settings and presets.
  *
- * @package    enrol
- * @subpackage cohort
+ * @package    enrol_cohort
  * @copyright  2010 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -45,4 +44,3 @@ if ($ADMIN->fulltree) {
         $settings->add(new admin_setting_configselect('enrol_cohort/unenrolaction', get_string('extremovedaction', 'enrol'), get_string('extremovedaction_help', 'enrol'), ENROL_EXT_REMOVED_UNENROL, $options));
     }
 }
-
index 74a2aa8..f23b2b3 100644 (file)
@@ -17,8 +17,7 @@
 /**
  * Cohort enrolment plugin version specification.
  *
- * @package    enrol
- * @subpackage cohort
+ * @package    enrol_cohort
  * @copyright  2010 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -28,4 +27,4 @@ defined('MOODLE_INTERNAL') || die();
 $plugin->version   = 2012061700;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2012061700;        // Requires this Moodle version
 $plugin->component = 'enrol_cohort';    // Full name of the plugin (used for diagnostics)
-$plugin->cron      = 60*60;             // run cron every hour by default, it is not out-of-sync often
\ No newline at end of file
+$plugin->cron      = 60*60;             // run cron every hour by default, it is not out-of-sync often
index 9509c01..ae247a0 100644 (file)
@@ -2,6 +2,7 @@
 .qce-panel .yui3-widget-hd {background:url("sprite.png");background-repeat:repeat-x;background-color:#DDD;background-position: 0 -15px;border-bottom:1px solid #555;border-top:1px solid #fff;}
 .qce-panel .yui3-widget-hd h2 {margin:3px 5px 2px;padding:0;font-size:110%;}
 .qce-panel .yui3-widget-hd .close {width:25px;height:15px;position:absolute;top:3px;right:1em;cursor:pointer;background:url("sprite.png") no-repeat scroll 0 0 transparent;}
+.dir-rtl .qce-panel .yui3-widget-hd .close {right:auto;left:1em;}
 .qce-panel .yui3-overlay-content {background-color:#F6F6F6;border:1px solid #555;margin-top:-2px;margin-left:-2px;}
 .qce-panel .qce-enrollable-cohorts {margin:5px;}
 .qce-panel .qce-cohorts {border:1px solid #666;min-width:408px;background-color:#FFF;height:375px;overflow:auto;}
index e53600b..79de6be 100644 (file)
@@ -368,6 +368,7 @@ class enrol_database_plugin extends enrol_plugin {
                 continue;
             }
             $existing[$course->mapping] = $course;
+            unset($externalcourses[$course->mapping]);
         }
         $rs->close();
 
@@ -388,18 +389,22 @@ class enrol_database_plugin extends enrol_plugin {
                 continue;
             }
             if (!isset($externalcourses[$course->mapping])) {
-                // course not synced
-                continue;
-            }
-            if (isset($existing[$course->mapping])) {
-                // some duplicate, sorry
+                // Course not synced or duplicate.
                 continue;
             }
             $course->enrolid = $this->add_instance($course);
             $existing[$course->mapping] = $course;
+            unset($externalcourses[$course->mapping]);
         }
         $rs->close();
 
+        // Print list of missing courses.
+        if ($verbose and $externalcourses) {
+            $list = implode(', ', array_keys($externalcourses));
+            mtrace("  error: following courses do not exist - $list");
+            unset($list);
+        }
+
         // free memory
         unset($externalcourses);
 
@@ -452,13 +457,18 @@ class enrol_database_plugin extends enrol_plugin {
                     while ($fields = $rs->FetchRow()) {
                         $fields = array_change_key_case($fields, CASE_LOWER);
                         if (empty($fields[$userfield])) {
-                            //user identification is mandatory!
+                            if ($verbose) {
+                                mtrace("  error: skipping user without mandatory $localuserfield in course '$course->mapping'");
+                            }
+                            continue;
                         }
                         $mapping = $fields[$userfield];
                         if (!isset($user_mapping[$mapping])) {
                             $usersearch[$localuserfield] = $mapping;
                             if (!$user = $DB->get_record('user', $usersearch, 'id', IGNORE_MULTIPLE)) {
-                                // user does not exist or was deleted
+                                if ($verbose) {
+                                    mtrace("  error: skipping unknown user $localuserfield '$mapping' in course '$course->mapping'");
+                                }
                                 continue;
                             }
                             $user_mapping[$mapping] = $user->id;
@@ -468,7 +478,9 @@ class enrol_database_plugin extends enrol_plugin {
                         }
                         if (empty($fields[$rolefield]) or !isset($roles[$fields[$rolefield]])) {
                             if (!$defaultrole) {
-                                // role is mandatory
+                                if ($verbose) {
+                                    mtrace("  error: skipping user '$userid' in course '$course->mapping' - missing course and default role");
+                                }
                                 continue;
                             }
                             $roleid = $defaultrole;
index 02aec70..acd2536 100644 (file)
@@ -366,18 +366,22 @@ class enrol_guest_plugin extends enrol_plugin {
         return $this->add_instance($course, $fields);
     }
 
-}
+    /**
+     * Restore instance and map settings.
+     *
+     * @param restore_enrolments_structure_step $step
+     * @param stdClass $data
+     * @param stdClass $course
+     * @param int $oldid
+     */
+    public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
+        global $DB;
 
-/**
- * Indicates API features that the enrol plugin supports.
- *
- * @param string $feature
- * @return mixed True if yes (some features may use other values)
- */
-function enrol_guest_supports($feature) {
-    switch($feature) {
-        case ENROL_RESTORE_TYPE: return ENROL_RESTORE_NOUSERS;
+        if (!$DB->record_exists('enrol', array('courseid' => $data->courseid, 'enrol' => $this->get_name()))) {
+            $this->add_instance($course, (array)$data);
+        }
 
-        default: return null;
+        // No need to set mapping, we do not restore users or roles here.
+        $step->set_mapping('enrol', $oldid, 0);
     }
 }
diff --git a/enrol/manual/cli/sync.php b/enrol/manual/cli/sync.php
new file mode 100644 (file)
index 0000000..c18488e
--- /dev/null
@@ -0,0 +1,65 @@
+<?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/>.
+
+/**
+ * CLI update for manual enrolments expiration.
+ *
+ * Notes:
+ *   - it is required to use the web server account when executing PHP CLI scripts
+ *   - you need to change the "www-data" to match the apache user account
+ *   - use "su" if "sudo" not available
+ *
+ * @package    enrol_manual
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
+require_once($CFG->libdir.'/clilib.php');
+
+// Now get cli options.
+list($options, $unrecognized) = cli_get_params(array('verbose'=>false, 'help'=>false), array('v'=>'verbose', 'h'=>'help'));
+
+if ($unrecognized) {
+    $unrecognized = implode("\n  ", $unrecognized);
+    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+if ($options['help']) {
+    $help =
+        "Execute manual enrolments expiration sync.
+
+Options:
+-v, --verbose         Print verbose progress information
+-h, --help            Print out this help
+
+Example:
+\$sudo -u www-data /usr/bin/php enrol/self/manual/sync.php
+";
+
+    echo $help;
+    die;
+}
+
+$verbose = !empty($options['verbose']);
+
+$plugin = enrol_get_plugin('manual');
+
+$result = $plugin->sync(null, $verbose);
+
+exit($result);
index 9e51fd1..7abb510 100644 (file)
@@ -37,6 +37,8 @@ $string['editenrolment'] = 'Edit enrolment';
 $string['editselectedusers'] = 'Edit selected user enrolments';
 $string['enrolledincourserole'] = 'Enrolled in "{$a->course}" as "{$a->role}"';
 $string['enrolusers'] = 'Enrol users';
+$string['expiredaction'] = 'Enrolment expiration action';
+$string['expiredaction_help'] = 'Select action to carry out when user enrolment expires. Please note that some user data and settings are purged from course during course unenrolment.';
 $string['manual:config'] = 'Configure manual enrol instances';
 $string['manual:enrol'] = 'Enrol users';
 $string['manual:manage'] = 'Manage user enrolments';
index 2cf94bb..d55c68c 100644 (file)
@@ -257,6 +257,106 @@ class enrol_manual_plugin extends enrol_plugin {
         return $button;
     }
 
+    /**
+     * Enrol cron support.
+     * @return void
+     */
+    public function cron() {
+        $this->sync(null, true);
+    }
+
+    /**
+     * Sync all meta course links.
+     *
+     * @param int $courseid one course, empty mean all
+     * @param bool $verbose verbose CLI output
+     * @return int 0 means ok, 1 means error, 2 means plugin disabled
+     */
+    public function sync($courseid = null, $verbose = false) {
+        global $DB;
+
+        if (!enrol_is_enabled('manual')) {
+            return 2;
+        }
+
+        // Unfortunately this may take a long time, execution can be interrupted safely here.
+        @set_time_limit(0);
+        raise_memory_limit(MEMORY_HUGE);
+
+        if ($verbose) {
+            mtrace('Verifying manual enrolment expiration...');
+        }
+
+        $params = array('now'=>time(), 'useractive'=>ENROL_USER_ACTIVE, 'courselevel'=>CONTEXT_COURSE);
+        $coursesql = "";
+        if ($courseid) {
+            $coursesql = "AND e.courseid = :courseid";
+            $params['courseid'] = $courseid;
+        }
+
+        // Deal with expired accounts.
+        $action = $this->get_config('expiredaction', ENROL_EXT_REMOVED_KEEP);
+
+        if ($action == ENROL_EXT_REMOVED_UNENROL) {
+            $instances = array();
+            $sql = "SELECT ue.*, e.courseid, c.id AS contextid
+                      FROM {user_enrolments} ue
+                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'manual')
+                      JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
+                     WHERE ue.timeend > 0 AND ue.timeend < :now
+                           $coursesql";
+            $rs = $DB->get_recordset_sql($sql, $params);
+            foreach ($rs as $ue) {
+                if (empty($instances[$ue->enrolid])) {
+                    $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
+                }
+                $instance = $instances[$ue->enrolid];
+                // Always remove all manually assigned roles here, this may break enrol_self roles but we do not want hardcoded hacks here.
+                role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0), true);
+                $this->unenrol_user($instance, $ue->userid);
+                if ($verbose) {
+                    mtrace("  unenrolling expired user $ue->userid from course $instance->courseid");
+                }
+            }
+            $rs->close();
+            unset($instances);
+
+        } else if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+            $instances = array();
+            $sql = "SELECT ue.*, e.courseid, c.id AS contextid
+                      FROM {user_enrolments} ue
+                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'manual')
+                      JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
+                     WHERE ue.timeend > 0 AND ue.timeend < :now
+                           AND ue.status = :useractive
+                           $coursesql";
+            $rs = $DB->get_recordset_sql($sql, $params);
+            foreach ($rs as $ue) {
+                if (empty($instances[$ue->enrolid])) {
+                    $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
+                }
+                $instance = $instances[$ue->enrolid];
+                // Always remove all manually assigned roles here, this may break enrol_self roles but we do not want hardcoded hacks here.
+                role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0), true);
+                $this->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
+                if ($verbose) {
+                    mtrace("  suspending expired user $ue->userid in course $instance->courseid");
+                }
+            }
+            $rs->close();
+            unset($instances);
+
+        } else {
+            // ENROL_EXT_REMOVED_KEEP means no changes.
+        }
+
+        if ($verbose) {
+            mtrace('...manual enrolment updates finished.');
+        }
+
+        return 0;
+    }
+
     /**
      * Gets an array of the user enrolment actions.
      *
@@ -295,18 +395,89 @@ class enrol_manual_plugin extends enrol_plugin {
         );
         return $bulkoperations;
     }
-}
 
-/**
- * Indicates API features that the enrol plugin supports.
- *
- * @param string $feature
- * @return mixed True if yes (some features may use other values)
- */
-function enrol_manual_supports($feature) {
-    switch($feature) {
-        case ENROL_RESTORE_TYPE: return ENROL_RESTORE_EXACT;
+    /**
+     * Restore instance and map settings.
+     *
+     * @param restore_enrolments_structure_step $step
+     * @param stdClass $data
+     * @param stdClass $course
+     * @param int $oldid
+     */
+    public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
+        global $DB;
+        // There is only I manual enrol instance allowed per course.
+        if ($instances = $DB->get_records('enrol', array('courseid'=>$data->courseid, 'enrol'=>'manual'), 'id')) {
+            $instance = reset($instances);
+            $instanceid = $instance->id;
+        } else {
+            $instanceid = $this->add_instance($course, (array)$data);
+        }
+        $step->set_mapping('enrol', $oldid, $instanceid);
+    }
+
+    /**
+     * Restore user enrolment.
+     *
+     * @param restore_enrolments_structure_step $step
+     * @param stdClass $data
+     * @param stdClass $instance
+     * @param int $oldinstancestatus
+     * @param int $userid
+     */
+    public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) {
+        global $DB;
+
+        // Note: this is a bit tricky because other types may be converted to manual enrolments,
+        //       and manual is restricted to one enrolment per user.
+
+        $ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid));
+        $enrol = false;
+        if ($ue and $ue->status == ENROL_USER_ACTIVE) {
+            // We do not want to restrict current active enrolments, let's kind of merge the times only.
+            // This prevents some teacher lockouts too.
+            if ($data->status == ENROL_USER_ACTIVE) {
+                if ($data->timestart > $ue->timestart) {
+                    $data->timestart = $ue->timestart;
+                    $enrol = true;
+                }
+
+                if ($data->timeend == 0) {
+                    if ($ue->timeend != 0) {
+                        $enrol = true;
+                    }
+                } else if ($ue->timeend == 0) {
+                    $data->timeend = 0;
+                } else if ($data->timeend < $ue->timeend) {
+                    $data->timeend = $ue->timeend;
+                    $enrol = true;
+                }
+            }
+        } else {
+            if ($instance->status == ENROL_INSTANCE_ENABLED and $oldinstancestatus != ENROL_INSTANCE_ENABLED) {
+                // Make sure that user enrolments are not activated accidentally,
+                // we do it only here because it is not expected that enrolments are migrated to other plugins.
+                $data->status = ENROL_USER_SUSPENDED;
+            }
+            $enrol = true;
+        }
 
-        default: return null;
+        if ($enrol) {
+            $this->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, $data->status);
+        }
+    }
+
+    /**
+     * Restore role assignment.
+     *
+     * @param stdClass $instance
+     * @param int $roleid
+     * @param int $userid
+     * @param int $contextid
+     */
+    public function restore_role_assignment($instance, $roleid, $userid, $contextid) {
+        // This is necessary only because we may migrate other types to this instance,
+        // we do not use component in manual or self enrol.
+        role_assign($roleid, $userid, $contextid, '', 0);
     }
 }
index 79686aa..f793485 100644 (file)
@@ -29,6 +29,15 @@ if ($ADMIN->fulltree) {
     //--- general settings -----------------------------------------------------------------------------------
     $settings->add(new admin_setting_heading('enrol_manual_settings', '', get_string('pluginname_desc', 'enrol_manual')));
 
+    // Note: let's reuse the ext sync constants and strings here, internally it is very similar,
+    //       it describes what should happend when users are not supposed to be enerolled any more.
+    $options = array(
+        ENROL_EXT_REMOVED_KEEP           => get_string('extremovedkeep', 'enrol'),
+        ENROL_EXT_REMOVED_SUSPENDNOROLES => get_string('extremovedsuspendnoroles', 'enrol'),
+        ENROL_EXT_REMOVED_UNENROL        => get_string('extremovedunenrol', 'enrol'),
+    );
+    $settings->add(new admin_setting_configselect('enrol_manual/expiredaction', get_string('expiredaction', 'enrol_manual'), get_string('expiredaction_help', 'enrol_manual'), ENROL_EXT_REMOVED_KEEP, $options));
+
 
     //--- enrol instance defaults ----------------------------------------------------------------------------
     $settings->add(new admin_setting_heading('enrol_manual_defaults',
diff --git a/enrol/manual/tests/externallib_test.php b/enrol/manual/tests/externallib_test.php
new file mode 100644 (file)
index 0000000..823568b
--- /dev/null
@@ -0,0 +1,73 @@
+<?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/>.
+
+/**
+ * Enrol manual external PHPunit tests
+ *
+ * @package    enrol_manual
+ * @category   external
+ * @copyright  2012 Jerome Mouneyrac
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.4
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+require_once($CFG->dirroot . '/enrol/manual/externallib.php');
+
+class enrol_manual_external_testcase extends externallib_advanced_testcase {
+
+    /**
+     * Test get_enrolled_users
+     */
+    public function test_enrol_users() {
+        global $USER, $CFG;
+
+        $this->resetAfterTest(true);
+
+        $course = self::getDataGenerator()->create_course();
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+
+        // Set the required capabilities by the external function.
+        $context = context_course::instance($course->id);
+        $roleid = $this->assignUserCapability('enrol/manual:enrol', $context->id);
+        $this->assignUserCapability('moodle/course:view', $context->id, $roleid);
+
+        // Add manager role to $USER.
+        // So $USER is allowed to assign 'manager', 'editingteacher', 'teacher' and 'student'.
+        role_assign(1, $USER->id, context_system::instance()->id);
+
+        // Call the external function.
+        enrol_manual_external::enrol_users(array(
+            array('roleid' => 3, 'userid' => $user1->id, 'courseid' => $course->id),
+            array('roleid' => 3, 'userid' => $user2->id, 'courseid' => $course->id)
+        ));
+
+        // Check we retrieve the good total number of enrolled users.
+        require_once($CFG->dirroot . '/enrol/externallib.php');
+        $enrolledusers = core_enrol_external::get_enrolled_users($course->id);
+        $this->assertEquals(2, count($enrolledusers));
+
+        // Call without required capability.
+        $this->unassignUserCapability('enrol/manual:enrol', $context->id, $roleid);
+        $this->setExpectedException('moodle_exception');
+        $categories = enrol_manual_external::enrol_users($course->id);
+    }
+}
\ No newline at end of file
index 745bac0..6d712f7 100644 (file)
@@ -201,4 +201,104 @@ class enrol_manual_lib_testcase extends advanced_testcase {
         enrol_manual_migrate_plugin_enrolments('manual');
         enrol_manual_migrate_plugin_enrolments('yyyy');
     }
+
+    public function test_expired() {
+        global $DB;
+        $this->resetAfterTest();
+
+        $manualplugin = enrol_get_plugin('manual');
+
+        $now = time();
+
+        // Prepare some data.
+
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->assertNotEmpty($studentrole);
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->assertNotEmpty($teacherrole);
+        $managerrole = $DB->get_record('role', array('shortname'=>'manager'));
+        $this->assertNotEmpty($managerrole);
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $user4 = $this->getDataGenerator()->create_user();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $course3 = $this->getDataGenerator()->create_course();
+        $context1 = context_course::instance($course1->id);
+        $context2 = context_course::instance($course2->id);
+        $context3 = context_course::instance($course3->id);
+
+        $this->assertEquals(3, $DB->count_records('enrol', array('enrol'=>'manual')));
+        $instance1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+        $this->assertEquals($studentrole->id, $instance1->roleid);
+        $instance2 = $DB->get_record('enrol', array('courseid'=>$course2->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+        $this->assertEquals($studentrole->id, $instance2->roleid);
+        $instance3 = $DB->get_record('enrol', array('courseid'=>$course3->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+        $this->assertEquals($studentrole->id, $instance3->roleid);
+
+        $this->assertEquals(0, $DB->count_records('user_enrolments'));
+        $this->assertEquals(0, $DB->count_records('role_assignments'));
+
+        $manualplugin->enrol_user($instance1, $user1->id, $studentrole->id);
+        $manualplugin->enrol_user($instance1, $user2->id, $studentrole->id);
+        $manualplugin->enrol_user($instance1, $user3->id, $studentrole->id, 0, $now-60);
+
+        $manualplugin->enrol_user($instance3, $user1->id, $studentrole->id, 0, 0);
+        $manualplugin->enrol_user($instance3, $user2->id, $studentrole->id, 0, $now+60*60);
+        $manualplugin->enrol_user($instance3, $user3->id, $teacherrole->id, 0, $now-60*60);
+
+        role_assign($managerrole->id, $user4->id, $context1->id);
+
+        $this->assertEquals(6, $DB->count_records('user_enrolments'));
+        $this->assertEquals(7, $DB->count_records('role_assignments'));
+        $this->assertEquals(5, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$managerrole->id)));
+
+        // Execute tests.
+
+        $this->assertEquals(ENROL_EXT_REMOVED_KEEP, $manualplugin->get_config('expiredaction'));
+        $manualplugin->sync(null, false);
+        $this->assertEquals(6, $DB->count_records('user_enrolments'));
+        $this->assertEquals(7, $DB->count_records('role_assignments'));
+
+
+        $manualplugin->set_config('expiredaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
+        $manualplugin->sync($course2->id, false);
+        $this->assertEquals(6, $DB->count_records('user_enrolments'));
+        $this->assertEquals(7, $DB->count_records('role_assignments'));
+
+        $this->assertTrue($DB->record_exists('role_assignments', array('contextid'=>$context1->id, 'userid'=>$user3->id, 'roleid'=>$studentrole->id)));
+        $this->assertTrue($DB->record_exists('role_assignments', array('contextid'=>$context3->id, 'userid'=>$user3->id, 'roleid'=>$teacherrole->id)));
+        $manualplugin->sync(null, false);
+        $this->assertEquals(6, $DB->count_records('user_enrolments'));
+        $this->assertEquals(5, $DB->count_records('role_assignments'));
+        $this->assertEquals(4, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(0, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+        $this->assertFalse($DB->record_exists('role_assignments', array('contextid'=>$context1->id, 'userid'=>$user3->id, 'roleid'=>$studentrole->id)));
+        $this->assertFalse($DB->record_exists('role_assignments', array('contextid'=>$context3->id, 'userid'=>$user3->id, 'roleid'=>$teacherrole->id)));
+
+
+        $manualplugin->set_config('expiredaction', ENROL_EXT_REMOVED_UNENROL);
+
+        role_assign($studentrole->id, $user3->id, $context1->id);
+        role_assign($teacherrole->id, $user3->id, $context3->id);
+        $this->assertEquals(6, $DB->count_records('user_enrolments'));
+        $this->assertEquals(7, $DB->count_records('role_assignments'));
+        $this->assertEquals(5, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$managerrole->id)));
+
+        $manualplugin->sync(null, false);
+        $this->assertEquals(4, $DB->count_records('user_enrolments'));
+        $this->assertFalse($DB->record_exists('user_enrolments', array('enrolid'=>$instance1->id, 'userid'=>$user3->id)));
+        $this->assertFalse($DB->record_exists('user_enrolments', array('enrolid'=>$instance3->id, 'userid'=>$user3->id)));
+        $this->assertEquals(5, $DB->count_records('role_assignments'));
+        $this->assertEquals(4, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(0, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$managerrole->id)));
+    }
 }
index 2107a67..a878cb2 100644 (file)
@@ -24,6 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012061700;        // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2012061700;        // Requires this Moodle version
+$plugin->version   = 2012091500;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2012091400;        // Requires this Moodle version
 $plugin->component = 'enrol_manual';    // Full name of the plugin (used for diagnostics)
+$plugin->cron      = 600;
diff --git a/enrol/self/cli/sync.php b/enrol/self/cli/sync.php
new file mode 100644 (file)
index 0000000..aa1ea5c
--- /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/>.
+
+/**
+ * CLI update for self enrolments, use for debugging or immediate update
+ * of all courses.
+ *
+ * Notes:
+ *   - it is required to use the web server account when executing PHP CLI scripts
+ *   - you need to change the "www-data" to match the apache user account
+ *   - use "su" if "sudo" not available
+ *
+ * @package    enrol_self
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
+require_once($CFG->libdir.'/clilib.php');
+
+// Now get cli options.
+list($options, $unrecognized) = cli_get_params(array('verbose'=>false, 'help'=>false), array('v'=>'verbose', 'h'=>'help'));
+
+if ($unrecognized) {
+    $unrecognized = implode("\n  ", $unrecognized);
+    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+if ($options['help']) {
+    $help =
+        "Execute self course enrol updates.
+
+Options:
+-v, --verbose         Print verbose progress information
+-h, --help            Print out this help
+
+Example:
+\$sudo -u www-data /usr/bin/php enrol/self/cli/sync.php
+";
+
+    echo $help;
+    die;
+}
+
+$verbose = !empty($options['verbose']);
+
+$plugin = enrol_get_plugin('self');
+
+$result = $plugin->sync(null, $verbose);
+
+exit($result);
index 06ba0e4..5492c5d 100644 (file)
@@ -44,6 +44,8 @@ $string['enrolperiod_desc'] = 'Default length of time that the enrolment is vali
 $string['enrolperiod_help'] = 'Length of time that the enrolment is valid, starting with the moment the user enrols themselves. If disabled, the enrolment duration will be unlimited.';
 $string['enrolstartdate'] = 'Start date';
 $string['enrolstartdate_help'] = 'If enabled, users can enrol themselves from this date onward only.';
+$string['expiredaction'] = 'Enrolment expiration action';
+$string['expiredaction_help'] = 'Select action to carry out when user enrolment expires. Please note that some user data and settings are purged from course during course unenrolment.';
 $string['groupkey'] = 'Use group enrolment keys';
 $string['groupkey_desc'] = 'Use group enrolment keys by default.';
 $string['groupkey_help'] = 'In addition to restricting access to the course to only those who know the key, use of a group enrolment key means users are automatically added to the group when they enrol in the course.
index fda2414..e4ac835 100644 (file)
@@ -334,31 +334,57 @@ class enrol_self_plugin extends enrol_plugin {
      * @return void
      */
     public function cron() {
+        $this->sync(null, true);
+    }
+
+    /**
+     * Sync all meta course links.
+     *
+     * @param int $courseid one course, empty mean all
+     * @param bool $verbose verbose CLI output
+     * @return int 0 means ok, 1 means error, 2 means plugin disabled
+     */
+    public function sync($courseid = null, $verbose = false) {
         global $DB;
 
         if (!enrol_is_enabled('self')) {
-            return;
+            return 2;
         }
 
-        $plugin = enrol_get_plugin('self');
+        // Unfortunately this may take a long time, execution can be interrupted safely here.
+        @set_time_limit(0);
+        raise_memory_limit(MEMORY_HUGE);
 
-        $now = time();
+        if ($verbose) {
+            mtrace('Verifying self-enrolments...');
+        }
+
+        $params = array('now'=>time(), 'useractive'=>ENROL_USER_ACTIVE, 'courselevel'=>CONTEXT_COURSE);
+        $coursesql = "";
+        if ($courseid) {
+            $coursesql = "AND e.courseid = :courseid";
+            $params['courseid'] = $courseid;
+        }
 
         // Note: the logic of self enrolment guarantees that user logged in at least once (=== u.lastaccess set)
         //       and that user accessed course at least once too (=== user_lastaccess record exists).
 
-        // First deal with users that did not log in for a really long time.
+        // First deal with users that did not log in for a really long time - they do not have user_lastaccess records.
         $sql = "SELECT e.*, ue.userid
                   FROM {user_enrolments} ue
                   JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'self' AND e.customint2 > 0)
                   JOIN {user} u ON u.id = ue.userid
-                 WHERE :now - u.lastaccess > e.customint2";
-        $rs = $DB->get_recordset_sql($sql, array('now'=>$now));
+                 WHERE :now - u.lastaccess > e.customint2
+                       $coursesql";
+        $rs = $DB->get_recordset_sql($sql, $params);
         foreach ($rs as $instance) {
             $userid = $instance->userid;
             unset($instance->userid);
-            $plugin->unenrol_user($instance, $userid);
-            mtrace("unenrolling user $userid from course $instance->courseid as they have did not log in for $instance->customint2 days");
+            $this->unenrol_user($instance, $userid);
+            if ($verbose) {
+                $days = $instance->customint2 / 60*60*24;
+                mtrace("  unenrolling user $userid from course $instance->courseid as they have did not log in for at least $days days");
+            }
         }
         $rs->close();
 
@@ -367,17 +393,85 @@ class enrol_self_plugin extends enrol_plugin {
                   FROM {user_enrolments} ue
                   JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'self' AND e.customint2 > 0)
                   JOIN {user_lastaccess} ul ON (ul.userid = ue.userid AND ul.courseid = e.courseid)
-                 WHERE :now - ul.timeaccess > e.customint2";
-        $rs = $DB->get_recordset_sql($sql, array('now'=>$now));
+                 WHERE :now - ul.timeaccess > e.customint2
+                       $coursesql";
+        $rs = $DB->get_recordset_sql($sql, $params);
         foreach ($rs as $instance) {
             $userid = $instance->userid;
             unset($instance->userid);
-            $plugin->unenrol_user($instance, $userid);
-            mtrace("unenrolling user $userid from course $instance->courseid as they have did not access course for $instance->customint2 days");
+            $this->unenrol_user($instance, $userid);
+            if ($verbose) {
+                $days = $instance->customint2 / 60*60*24;
+                mtrace("  unenrolling user $userid from course $instance->courseid as they have did not access course for at least $days days");
+            }
         }
         $rs->close();
 
-        flush();
+        // Deal with expired accounts.
+        $action = $this->get_config('expiredaction', ENROL_EXT_REMOVED_KEEP);
+
+        if ($action == ENROL_EXT_REMOVED_UNENROL) {
+            $instances = array();
+            $sql = "SELECT ue.*, e.courseid, c.id AS contextid
+                      FROM {user_enrolments} ue
+                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'self')
+                      JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
+                     WHERE ue.timeend > 0 AND ue.timeend < :now
+                           $coursesql";
+            $rs = $DB->get_recordset_sql($sql, $params);
+            foreach ($rs as $ue) {
+                if (empty($instances[$ue->enrolid])) {
+                    $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
+                }
+                $instance = $instances[$ue->enrolid];
+                if ($instance->roleid) {
+                    role_unassign($instance->roleid, $ue->userid, $ue->contextid, '', 0);
+                }
+                $this->unenrol_user($instance, $ue->userid);
+                if ($verbose) {
+                    mtrace("  unenrolling expired user $ue->userid from course $instance->courseid");
+                }
+            }
+            $rs->close();
+            unset($instances);
+
+        } else if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+            $instances = array();
+            $sql = "SELECT ue.*, e.courseid, c.id AS contextid
+                      FROM {user_enrolments} ue
+                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'self')
+                      JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
+                     WHERE ue.timeend > 0 AND ue.timeend < :now
+                           AND ue.status = :useractive
+                           $coursesql";
+            $rs = $DB->get_recordset_sql($sql, $params);
+            foreach ($rs as $ue) {
+                if (empty($instances[$ue->enrolid])) {
+                    $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
+                }
+                $instance = $instances[$ue->enrolid];
+                if (1 == $DB->count_records('role_assignments', array('userid'=>$ue->userid, 'contextid'=>$ue->contextid))) {
+                    role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0), true);
+                } else if ($instance->roleid) {
+                    role_unassign($instance->roleid, $ue->userid, $ue->contextid, '', 0);
+                }
+                $this->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
+                if ($verbose) {
+                    mtrace("  suspending expired user $ue->userid in course $instance->courseid");
+                }
+            }
+            $rs->close();
+            unset($instances);
+
+        } else {
+            // ENROL_EXT_REMOVED_KEEP means no changes.
+        }
+
+        if ($verbose) {
+            mtrace('...user self-enrolment updates finished.');
+        }
+
+        return 0;
     }
 
      /**
@@ -403,18 +497,68 @@ class enrol_self_plugin extends enrol_plugin {
         }
         return $actions;
     }
-}
 
-/**
- * Indicates API features that the enrol plugin supports.
- *
- * @param string $feature
- * @return mixed true if yes (some features may use other values)
- */
-function enrol_self_supports($feature) {
-    switch($feature) {
-        case ENROL_RESTORE_TYPE: return ENROL_RESTORE_EXACT;
+    /**
+     * Restore instance and map settings.
+     *
+     * @param restore_enrolments_structure_step $step
+     * @param stdClass $data
+     * @param stdClass $course
+     * @param int $oldid
+     */
+    public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
+        global $DB;
+        if ($step->get_task()->get_target() == backup::TARGET_NEW_COURSE) {
+            $merge = false;
+        } else {
+            $merge = array(
+                'courseid'   => $data->courseid,
+                'enrol'      => $this->get_name(),
+                'roleid'     => $data->roleid,
+            );
+        }
+        if ($merge and $instances = $DB->get_records('enrol', $merge, 'id')) {
+            $instance = reset($instances);
+            $instanceid = $instance->id;
+        } else {
+            if (!empty($data->customint5)) {
+                if ($step->get_task()->is_samesite()) {
+                    // Keep cohort restriction unchanged - we are on the same site.
+                } else {
+                    // Use some id that can not exist in order to prevent self enrolment,
+                    // because we do not know what cohort it is in this site.
+                    $data->customint5 = -1;
+                }
+            }
+            $instanceid = $this->add_instance($course, (array)$data);
+        }
+        $step->set_mapping('enrol', $oldid, $instanceid);
+    }
+
+    /**
+     * Restore user enrolment.
+     *
+     * @param restore_enrolments_structure_step $step
+     * @param stdClass $data
+     * @param stdClass $instance
+     * @param int $oldinstancestatus
+     * @param int $userid
+     */
+    public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) {
+        $this->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, $data->status);
+    }
 
-        default: return null;
+    /**
+     * Restore role assignment.
+     *
+     * @param stdClass $instance
+     * @param int $roleid
+     * @param int $userid
+     * @param int $contextid
+     */
+    public function restore_role_assignment($instance, $roleid, $userid, $contextid) {
+        // This is necessary only because we may migrate other types to this instance,
+        // we do not use component in manual or self enrol.
+        role_assign($roleid, $userid, $contextid, '', 0);
     }
 }
index c8c2f02..25d561e 100644 (file)
@@ -38,6 +38,15 @@ if ($ADMIN->fulltree) {
     $settings->add(new admin_setting_configcheckbox('enrol_self/showhint',
         get_string('showhint', 'enrol_self'), get_string('showhint_desc', 'enrol_self'), 0));
 
+    // Note: let's reuse the ext sync constants and strings here, internally it is very similar,
+    //       it describes what should happend when users are not supposed to be enerolled any more.
+    $options = array(
+        ENROL_EXT_REMOVED_KEEP           => get_string('extremovedkeep', 'enrol'),
+        ENROL_EXT_REMOVED_SUSPENDNOROLES => get_string('extremovedsuspendnoroles', 'enrol'),
+        ENROL_EXT_REMOVED_UNENROL        => get_string('extremovedunenrol', 'enrol'),
+    );
+    $settings->add(new admin_setting_configselect('enrol_self/expiredaction', get_string('expiredaction', 'enrol_self'), get_string('expiredaction_help', 'enrol_self'), ENROL_EXT_REMOVED_KEEP, $options));
+
     //--- enrol instance defaults ----------------------------------------------------------------------------
     $settings->add(new admin_setting_heading('enrol_self_defaults',
         get_string('enrolinstancedefaults', 'admin'), get_string('enrolinstancedefaults_desc', 'admin')));
diff --git a/enrol/self/tests/self_test.php b/enrol/self/tests/self_test.php
new file mode 100644 (file)
index 0000000..f84f8c5
--- /dev/null
@@ -0,0 +1,264 @@
+<?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/>.
+
+/**
+ * Self enrolment plugin tests.
+ *
+ * @package    enrol_self
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot.'/enrol/self/lib.php');
+require_once($CFG->dirroot.'/enrol/self/locallib.php');
+
+class enrol_self_testcase extends advanced_testcase {
+
+    public function test_basics() {
+        $this->assertTrue(enrol_is_enabled('self'));
+        $plugin = enrol_get_plugin('self');
+        $this->assertInstanceOf('enrol_self_plugin', $plugin);
+        $this->assertEquals(1, get_config('enrol_self', 'defaultenrol'));
+        $this->assertEquals(ENROL_EXT_REMOVED_KEEP, get_config('enrol_self', 'expiredaction'));
+    }
+
+    public function test_sync_nothing() {
+        global $SITE;
+
+        $selfplugin = enrol_get_plugin('self');
+
+        // Just make sure the sync does not throw any errors when nothing to do.
+        $selfplugin->sync(NULL, false);
+        $selfplugin->sync($SITE->id, false);
+    }
+
+    public function test_longtimnosee() {
+        global $DB;
+        $this->resetAfterTest();
+
+        $selfplugin = enrol_get_plugin('self');
+        $manualplugin = enrol_get_plugin('manual');
+        $this->assertNotEmpty($manualplugin);
+
+        $now = time();
+
+        // Prepare some data.
+
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->assertNotEmpty($studentrole);
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->assertNotEmpty($teacherrole);
+
+        $record = array('firstaccess'=>$now-60*60*24*800);
+        $record['lastaccess'] = $now-60*60*24*100;
+        $user1 = $this->getDataGenerator()->create_user($record);
+        $record['lastaccess'] = $now-60*60*24*10;
+        $user2 = $this->getDataGenerator()->create_user($record);
+        $record['lastaccess'] = $now-60*60*24*1;
+        $user3 = $this->getDataGenerator()->create_user($record);
+        $record['lastaccess'] = $now-10;
+        $user4 = $this->getDataGenerator()->create_user($record);
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $course3 = $this->getDataGenerator()->create_course();
+        $context1 = context_course::instance($course1->id);
+        $context2 = context_course::instance($course2->id);
+        $context3 = context_course::instance($course3->id);
+
+        $this->assertEquals(3, $DB->count_records('enrol', array('enrol'=>'self')));
+        $instance1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'self'), '*', MUST_EXIST);
+        $instance2 = $DB->get_record('enrol', array('courseid'=>$course2->id, 'enrol'=>'self'), '*', MUST_EXIST);
+        $instance3 = $DB->get_record('enrol', array('courseid'=>$course3->id, 'enrol'=>'self'), '*', MUST_EXIST);
+        $id = $selfplugin->add_instance($course3, array('status'=>ENROL_INSTANCE_ENABLED, 'roleid'=>$teacherrole->id));
+        $instance3b = $DB->get_record('enrol', array('id'=>$id), '*', MUST_EXIST);
+        unset($id);
+
+        $this->assertEquals($studentrole->id, $instance1->roleid);
+        $instance1->customint2 = 60*60*24*14;
+        $DB->update_record('enrol', $instance1);
+        $selfplugin->enrol_user($instance1, $user1->id, $studentrole->id);
+        $selfplugin->enrol_user($instance1, $user2->id, $studentrole->id);
+        $selfplugin->enrol_user($instance1, $user3->id, $studentrole->id);
+        $this->assertEquals(3, $DB->count_records('user_enrolments'));
+        $DB->insert_record('user_lastaccess', array('userid'=>$user2->id, 'courseid'=>$course1->id, 'timeaccess'=>$now-60*60*24*20));
+        $DB->insert_record('user_lastaccess', array('userid'=>$user3->id, 'courseid'=>$course1->id, 'timeaccess'=>$now-60*60*24*2));
+        $DB->insert_record('user_lastaccess', array('userid'=>$user4->id, 'courseid'=>$course1->id, 'timeaccess'=>$now-60));
+
+        $this->assertEquals($studentrole->id, $instance3->roleid);
+        $instance3->customint2 = 60*60*24*50;
+        $DB->update_record('enrol', $instance3);
+        $selfplugin->enrol_user($instance3, $user1->id, $studentrole->id);
+        $selfplugin->enrol_user($instance3, $user2->id, $studentrole->id);
+        $selfplugin->enrol_user($instance3, $user3->id, $studentrole->id);
+        $selfplugin->enrol_user($instance3b, $user1->id, $teacherrole->id);
+        $selfplugin->enrol_user($instance3b, $user4->id, $teacherrole->id);
+        $this->assertEquals(8, $DB->count_records('user_enrolments'));
+        $DB->insert_record('user_lastaccess', array('userid'=>$user2->id, 'courseid'=>$course3->id, 'timeaccess'=>$now-60*60*24*11));
+        $DB->insert_record('user_lastaccess', array('userid'=>$user3->id, 'courseid'=>$course3->id, 'timeaccess'=>$now-60*60*24*200));
+        $DB->insert_record('user_lastaccess', array('userid'=>$user4->id, 'courseid'=>$course3->id, 'timeaccess'=>$now-60*60*24*200));
+
+        $maninstance2 = $DB->get_record('enrol', array('courseid'=>$course2->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+        $maninstance3 = $DB->get_record('enrol', array('courseid'=>$course3->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+
+        $manualplugin->enrol_user($maninstance2, $user1->id, $studentrole->id);
+        $manualplugin->enrol_user($maninstance3, $user1->id, $teacherrole->id);
+
+        $this->assertEquals(10, $DB->count_records('user_enrolments'));
+        $this->assertEquals(9, $DB->count_records('role_assignments'));
+        $this->assertEquals(7, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+
+        // Execute sync - this is the same thing used from cron.
+
+        $selfplugin->sync($course2->id, false);
+        $this->assertEquals(10, $DB->count_records('user_enrolments'));
+
+        $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$instance1->id, 'userid'=>$user1->id)));
+        $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$instance1->id, 'userid'=>$user2->id)));
+        $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$instance3->id, 'userid'=>$user1->id)));
+        $this->assertTrue($DB->record_exists('user_enrolments', array('enrolid'=>$instance3->id, 'userid'=>$user3->id)));
+        $selfplugin->sync(null, false);
+        $this->assertEquals(6, $DB->count_records('user_enrolments'));
+        $this->assertFalse($DB->record_exists('user_enrolments', array('enrolid'=>$instance1->id, 'userid'=>$user1->id)));
+        $this->assertFalse($DB->record_exists('user_enrolments', array('enrolid'=>$instance1->id, 'userid'=>$user2->id)));
+        $this->assertFalse($DB->record_exists('user_enrolments', array('enrolid'=>$instance3->id, 'userid'=>$user1->id)));
+        $this->assertFalse($DB->record_exists('user_enrolments', array('enrolid'=>$instance3->id, 'userid'=>$user3->id)));
+
+        $this->assertEquals(6, $DB->count_records('role_assignments'));
+        $this->assertEquals(4, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+    }
+
+    public function test_expired() {
+        global $DB;
+        $this->resetAfterTest();
+
+        $selfplugin = enrol_get_plugin('self');
+        $manualplugin = enrol_get_plugin('manual');
+        $this->assertNotEmpty($manualplugin);
+
+        $now = time();
+
+        // Prepare some data.
+
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->assertNotEmpty($studentrole);
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->assertNotEmpty($teacherrole);
+        $managerrole = $DB->get_record('role', array('shortname'=>'manager'));
+        $this->assertNotEmpty($managerrole);
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $user4 = $this->getDataGenerator()->create_user();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $course3 = $this->getDataGenerator()->create_course();
+        $context1 = context_course::instance($course1->id);
+        $context2 = context_course::instance($course2->id);
+        $context3 = context_course::instance($course3->id);
+
+        $this->assertEquals(3, $DB->count_records('enrol', array('enrol'=>'self')));
+        $instance1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'self'), '*', MUST_EXIST);
+        $this->assertEquals($studentrole->id, $instance1->roleid);
+        $instance2 = $DB->get_record('enrol', array('courseid'=>$course2->id, 'enrol'=>'self'), '*', MUST_EXIST);
+        $this->assertEquals($studentrole->id, $instance2->roleid);
+        $instance3 = $DB->get_record('enrol', array('courseid'=>$course3->id, 'enrol'=>'self'), '*', MUST_EXIST);
+        $this->assertEquals($studentrole->id, $instance3->roleid);
+        $id = $selfplugin->add_instance($course3, array('status'=>ENROL_INSTANCE_ENABLED, 'roleid'=>$teacherrole->id));
+        $instance3b = $DB->get_record('enrol', array('id'=>$id), '*', MUST_EXIST);
+        $this->assertEquals($teacherrole->id, $instance3b->roleid);
+        unset($id);
+
+        $maninstance2 = $DB->get_record('enrol', array('courseid'=>$course2->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+        $maninstance3 = $DB->get_record('enrol', array('courseid'=>$course3->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+
+        $manualplugin->enrol_user($maninstance2, $user1->id, $studentrole->id);
+        $manualplugin->enrol_user($maninstance3, $user1->id, $teacherrole->id);
+
+        $this->assertEquals(2, $DB->count_records('user_enrolments'));
+        $this->assertEquals(2, $DB->count_records('role_assignments'));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+
+        $selfplugin->enrol_user($instance1, $user1->id, $studentrole->id);
+        $selfplugin->enrol_user($instance1, $user2->id, $studentrole->id);
+        $selfplugin->enrol_user($instance1, $user3->id, $studentrole->id, 0, $now-60);
+
+        $selfplugin->enrol_user($instance3, $user1->id, $studentrole->id, 0, 0);
+        $selfplugin->enrol_user($instance3, $user2->id, $studentrole->id, 0, $now-60*60);
+        $selfplugin->enrol_user($instance3, $user3->id, $studentrole->id, 0, $now+60*60);
+        $selfplugin->enrol_user($instance3b, $user1->id, $teacherrole->id, $now-60*60*24*7, $now-60);
+        $selfplugin->enrol_user($instance3b, $user4->id, $teacherrole->id);
+
+        role_assign($managerrole->id, $user3->id, $context1->id);
+
+        $this->assertEquals(10, $DB->count_records('user_enrolments'));
+        $this->assertEquals(10, $DB->count_records('role_assignments'));
+        $this->assertEquals(7, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+
+        // Execute tests.
+
+        $this->assertEquals(ENROL_EXT_REMOVED_KEEP, $selfplugin->get_config('expiredaction'));
+        $selfplugin->sync(null, false);
+        $this->assertEquals(10, $DB->count_records('user_enrolments'));
+        $this->assertEquals(10, $DB->count_records('role_assignments'));
+
+
+        $selfplugin->set_config('expiredaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
+        $selfplugin->sync($course2->id, false);
+        $this->assertEquals(10, $DB->count_records('user_enrolments'));
+        $this->assertEquals(10, $DB->count_records('role_assignments'));
+
+        $selfplugin->sync(null, false);
+        $this->assertEquals(10, $DB->count_records('user_enrolments'));
+        $this->assertEquals(7, $DB->count_records('role_assignments'));
+        $this->assertEquals(5, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+        $this->assertFalse($DB->record_exists('role_assignments', array('contextid'=>$context1->id, 'userid'=>$user3->id, 'roleid'=>$studentrole->id)));
+        $this->assertFalse($DB->record_exists('role_assignments', array('contextid'=>$context3->id, 'userid'=>$user2->id, 'roleid'=>$studentrole->id)));
+        $this->assertFalse($DB->record_exists('role_assignments', array('contextid'=>$context3->id, 'userid'=>$user1->id, 'roleid'=>$teacherrole->id)));
+        $this->assertTrue($DB->record_exists('role_assignments', array('contextid'=>$context3->id, 'userid'=>$user1->id, 'roleid'=>$studentrole->id)));
+
+
+        $selfplugin->set_config('expiredaction', ENROL_EXT_REMOVED_UNENROL);
+
+        role_assign($studentrole->id, $user3->id, $context1->id);
+        role_assign($studentrole->id, $user2->id, $context3->id);
+        role_assign($teacherrole->id, $user1->id, $context3->id);
+        $this->assertEquals(10, $DB->count_records('user_enrolments'));
+        $this->assertEquals(10, $DB->count_records('role_assignments'));
+        $this->assertEquals(7, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+
+        $selfplugin->sync(null, false);
+        $this->assertEquals(7, $DB->count_records('user_enrolments'));
+        $this->assertFalse($DB->record_exists('user_enrolments', array('enrolid'=>$instance1->id, 'userid'=>$user3->id)));
+        $this->assertFalse($DB->record_exists('user_enrolments', array('enrolid'=>$instance3->id, 'userid'=>$user2->id)));
+        $this->assertFalse($DB->record_exists('user_enrolments', array('enrolid'=>$instance3b->id, 'userid'=>$user1->id)));
+        $this->assertEquals(6, $DB->count_records('role_assignments'));
+        $this->assertEquals(5, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+    }
+}
index 1153b05..e5bbf47 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012082300;        // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2012082300;        // Requires this Moodle version
+$plugin->version   = 2012091500;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2012091400;        // Requires this Moodle version
 $plugin->component = 'enrol_self';      // Full name of the plugin (used for diagnostics)
-$plugin->cron      = 180;
\ No newline at end of file
+$plugin->cron      = 600;
diff --git a/enrol/tests/externallib_test.php b/enrol/tests/externallib_test.php
new file mode 100644 (file)
index 0000000..7fb9500
--- /dev/null
@@ -0,0 +1,224 @@
+<?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/>.
+
+/**
+ * Enrol/Role external PHPunit tests
+ *
+ * @package    core_enrol
+ * @category   external
+ * @copyright  2012 Jerome Mouneyrac
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.4
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+require_once($CFG->dirroot . '/enrol/externallib.php');
+
+/**
+ * Enrol external PHPunit tests
+ *
+ * @package    core_enrol
+ * @category   external
+ * @copyright  2012 Jerome Mouneyrac
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.4
+ */
+class core_enrol_external_testcase extends externallib_advanced_testcase {
+
+    /**
+     * Test get_enrolled_users
+     */
+    public function test_get_enrolled_users() {
+        global $USER;
+
+        $this->resetAfterTest(true);
+
+        $course = self::getDataGenerator()->create_course();
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+
+        // Set the required capabilities by the external function.
+        $context = context_course::instance($course->id);
+        $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id);
+        $this->assignUserCapability('moodle/user:viewdetails', $context->id, $roleid);
+
+        // Enrol the users in the course.
+        // We use the manual plugin.
+        $enrol = enrol_get_plugin('manual');
+        $enrolinstances = enrol_get_instances($course->id, true);
+        foreach ($enrolinstances as $courseenrolinstance) {
+            if ($courseenrolinstance->enrol == "manual") {
+                $instance = $courseenrolinstance;
+                break;
+            }
+        }
+        $enrol->enrol_user($instance, $user1->id, $roleid);
+        $enrol->enrol_user($instance, $user2->id, $roleid);
+        $enrol->enrol_user($instance, $USER->id, $roleid);
+
+        // Call the external function.
+        $enrolledusers = core_enrol_external::get_enrolled_users($course->id);
+
+        // Check we retrieve the good total number of enrolled users.
+        $this->assertEquals(3, count($enrolledusers));
+
+        // Call without required capability.
+        $this->unassignUserCapability('moodle/course:viewparticipants', $context->id, $roleid);
+        $this->setExpectedException('moodle_exception');
+        $categories = core_enrol_external::get_enrolled_users($course->id);
+    }
+
+    /**
+     * Test get_users_courses
+     */
+    public function test_get_users_courses() {
+        global $USER;
+
+        $this->resetAfterTest(true);
+
+        $course1 = self::getDataGenerator()->create_course();
+        $course2 = self::getDataGenerator()->create_course();
+        $courses = array($course1, $course2);
+
+        // Enrol $USER in the courses.
+        // We use the manual plugin.
+        $enrol = enrol_get_plugin('manual');
+        $roleid = null;
+        foreach ($courses as $course) {
+            $context = context_course::instance($course->id);
+            $roleid = $this->assignUserCapability('moodle/course:viewparticipants',
+                    $context->id, $roleid);
+
+            $enrolinstances = enrol_get_instances($course->id, true);
+            foreach ($enrolinstances as $courseenrolinstance) {
+                if ($courseenrolinstance->enrol == "manual") {
+                    $instance = $courseenrolinstance;
+                    break;
+                }
+            }
+            $enrol->enrol_user($instance, $USER->id, $roleid);
+        }
+
+        // Call the external function.
+        $enrolledincourses = core_enrol_external::get_users_courses($USER->id);
+
+        // Check we retrieve the good total number of enrolled users.
+        $this->assertEquals(2, count($enrolledincourses));
+    }
+}
+
+/**
+ * Role external PHPunit tests
+ *
+ * @package    core_enrol
+ * @category   external
+ * @copyright  2012 Jerome Mouneyrac
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.4
+ */
+class core_role_external_testcase extends externallib_advanced_testcase {
+
+    /**
+     * Tests set up
+     */
+    protected function setUp() {
+        global $CFG;
+        require_once($CFG->dirroot . '/enrol/externallib.php');
+    }
+
+    /**
+     * Test assign_roles
+     */
+    public function test_assign_roles() {
+        global $USER;
+
+        $this->resetAfterTest(true);
+
+        $course = self::getDataGenerator()->create_course();
+
+        // Set the required capabilities by the external function.
+        $context = context_course::instance($course->id);
+        $roleid = $this->assignUserCapability('moodle/role:assign', $context->id);
+        $this->assignUserCapability('moodle/course:view', $context->id, $roleid);
+
+        // Add manager role to $USER.
+        // So $USER is allowed to assign 'manager', 'editingteacher', 'teacher' and 'student'.
+        role_assign(1, $USER->id, context_system::instance()->id);
+
+        // Check the teacher role has not been assigned to $USER.
+        $users = get_role_users(3, $context);
+        $this->assertEquals(count($users), 0);
+
+        // Call the external function. Assign teacher role to $USER.
+        core_role_external::assign_roles(array(
+            array('roleid' => 3, 'userid' => $USER->id, 'contextid' => $context->id)));
+
+        // Check the role has been assigned.
+        $users = get_role_users(3, $context);
+        $this->assertEquals(count($users), 1);
+
+        // Call without required capability.
+        $this->unassignUserCapability('moodle/role:assign', $context->id, $roleid);
+        $this->setExpectedException('moodle_exception');
+        $categories = core_role_external::assign_roles(
+            array('roleid' => 3, 'userid' => $USER->id, 'contextid' => $context->id));
+    }
+
+    /**
+     * Test unassign_roles
+     */
+    public function test_unassign_roles() {
+        global $USER;
+
+        $this->resetAfterTest(true);
+
+        $course = self::getDataGenerator()->create_course();
+
+        // Set the required capabilities by the external function.
+        $context = context_course::instance($course->id);
+        $roleid = $this->assignUserCapability('moodle/role:assign', $context->id);
+        $this->assignUserCapability('moodle/course:view', $context->id, $roleid);
+
+        // Add manager role to $USER.
+        // So $USER is allowed to assign 'manager', 'editingteacher', 'teacher' and 'student'.
+        role_assign(1, $USER->id, context_system::instance()->id);
+
+        // Add teacher role to $USER on course context.
+        role_assign(3, $USER->id, $context->id);
+
+        // Check the teacher role has been assigned to $USER on course context.
+        $users = get_role_users(3, $context);
+        $this->assertEquals(count($users), 1);
+
+        // Call the external function. Assign teacher role to $USER.
+        core_role_external::unassign_roles(array(
+            array('roleid' => 3, 'userid' => $USER->id, 'contextid' => $context->id)));
+
+        // Check the role has been unassigned on course context.
+        $users = get_role_users(3, $context);
+        $this->assertEquals(count($users), 0);
+
+        // Call without required capability.
+        $this->unassignUserCapability('moodle/role:assign', $context->id, $roleid);
+        $this->setExpectedException('moodle_exception');
+        $categories = core_role_external::unassign_roles(
+            array('roleid' => 3, 'userid' => $USER->id, 'contextid' => $context->id));
+    }
+}
index 8fcd760..85dbf78 100644 (file)
@@ -7,6 +7,8 @@ information provided here is intended especially for developers.
 required changes in code:
 * use role_get_name() or role_fix_names() if you need any role names, using role.name
   directly from database is not correct any more
+* new restore support: ENROL_RESTORE_EXACT, ENROL_RESTORE_NOUSERS
+  and ENROL_RESTORE_CLASS were removed, implement new restore_* plugin methods instead
 
 other changes:
 * course enrolment manager now works with disabled plugins too
index 6a648b0..98c8431 100644 (file)
@@ -526,6 +526,7 @@ class core_files_renderer extends plugin_renderer_base {
                     <div class="{!}fp-tb-logout"><img src="'.$this->pix_url('a/logout').'" /><a href="#"></a></div>
                     <div class="{!}fp-tb-manage"><a href="#"><img src="'.$this->pix_url('a/setting').'" /> '.get_string('manageurl', 'repository').'</a></div>
                     <div class="{!}fp-tb-help"><a href="#"><img src="'.$this->pix_url('a/help').'" /> '.get_string('help').'</a></div>
+                    <div class="{!}fp-tb-message"></div>
                 </div>
                 <div class="{!}fp-viewbar">
                     <a class="{!}fp-vb-icons" href="#"></a>
diff --git a/group/tests/externallib_test.php b/group/tests/externallib_test.php
new file mode 100644 (file)
index 0000000..5963937
--- /dev/null
@@ -0,0 +1,204 @@
+<?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/>.
+
+/**
+ * Group external PHPunit tests
+ *
+ * @package    core_group
+ * @category   external
+ * @copyright  2012 Jerome Mouneyrac
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.4
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+require_once($CFG->dirroot . '/group/externallib.php');
+
+class core_group_external_testcase extends externallib_advanced_testcase {
+
+    /**
+     * Test create_groups
+     */
+    public function test_create_groups() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $course  = self::getDataGenerator()->create_course();
+
+        $group1 =&nbs