Merge branch 'MDL-41485-master' of git://github.com/ankitagarwal/moodle
authorDamyon Wiese <damyon@moodle.com>
Wed, 4 Sep 2013 04:24:54 +0000 (12:24 +0800)
committerDamyon Wiese <damyon@moodle.com>
Wed, 4 Sep 2013 04:24:54 +0000 (12:24 +0800)
506 files changed:
admin/index.php
admin/plugins.php
admin/renderer.php
admin/repository.php
admin/roles/classes/define_role_table_advanced.php
admin/tool/behat/renderer.php
admin/tool/behat/tests/behat/basic_actions.feature
admin/tool/generator/classes/backend.php
admin/tool/generator/classes/course_backend.php [new file with mode: 0644]
admin/tool/generator/classes/make_form.php
admin/tool/generator/classes/site_backend.php [new file with mode: 0644]
admin/tool/generator/cli/maketestcourse.php
admin/tool/generator/cli/maketestsite.php [new file with mode: 0644]
admin/tool/generator/lang/en/tool_generator.php
admin/tool/generator/maketestcourse.php
admin/tool/generator/tests/maketestcourse_test.php
admin/tool/generator/version.php
admin/tool/langimport/lang/en/tool_langimport.php
admin/tool/uploadcourse/lang/en/tool_uploadcourse.php
admin/tool/uploaduser/index.php
admin/tool/xmldb/lang/en/tool_xmldb.php
admin/user.php
auth/db/auth.php
auth/email/auth.php
auth/ldap/auth.php
auth/ldap/ntlmsso_magic.php
auth/mnet/auth.php
backup/backup.php
backup/controller/backup_controller.class.php
backup/controller/restore_controller.class.php
backup/import.php
backup/moodle2/backup_final_task.class.php
backup/moodle2/restore_stepslib.php
backup/restore.php
backup/util/dbops/backup_controller_dbops.class.php
backup/util/includes/backup_includes.php
backup/util/includes/restore_includes.php
backup/util/loggers/database_logger.class.php
backup/util/plan/backup_plan.class.php
backup/util/plan/base_plan.class.php
backup/util/plan/base_task.class.php
backup/util/plan/restore_plan.class.php
backup/util/plan/tests/fixtures/plan_fixtures.php
backup/util/progress/core_backup_display_progress.class.php [new file with mode: 0644]
backup/util/progress/core_backup_null_progress.class.php [new file with mode: 0644]
backup/util/progress/core_backup_progress.class.php [new file with mode: 0644]
backup/util/progress/tests/progress_test.php [new file with mode: 0644]
backup/util/structure/tests/baseoptiogroup_test.php
backup/util/ui/renderer.php
backup/util/ui/restore_ui_components.php
backup/util/ui/tests/behat/backup_courses.feature
backup/util/ui/tests/behat/restore_moodle2_courses.feature
badges/classes/observer.php
badges/external.php
badges/mybackpack.php
badges/renderer.php
badges/tests/badgeslib_test.php
blocks/course_list/lang/en/block_course_list.php
blocks/navigation/tests/behat/view_my_courses.feature
blog/locallib.php
cache/classes/loaders.php
cache/stores/file/lib.php
cache/stores/memcache/lib.php
cache/stores/memcached/lib.php
cache/stores/session/lib.php
cache/stores/session/tests/session_test.php
cache/tests/cache_test.php
cache/tests/fixtures/lib.php
course/externallib.php
course/format/scorm/format.php [deleted file]
course/format/scorm/lib.php [deleted file]
course/lib.php
course/manage.php
course/rest.php
course/tests/behat/course_controls.feature
course/tests/courselib_test.php
course/view.php
enrol/ldap/lang/en/enrol_ldap.php
enrol/ldap/lib.php
enrol/manual/locallib.php
enrol/meta/classes/observer.php [new file with mode: 0644]
enrol/meta/db/events.php
enrol/meta/locallib.php
enrol/meta/tests/plugin_test.php
enrol/tests/enrollib_test.php
error/index.php
files/renderer.php
files/tests/externallib_test.php
grade/export/lib.php
grade/externallib.php
grade/grading/form/guide/lib.php
grade/grading/form/lib.php
grade/grading/form/rubric/lib.php
grade/report/grader/lib.php
grade/tests/externallib_test.php
group/index.php
group/lib.php
group/tests/lib_test.php [new file with mode: 0644]
lang/en/admin.php
lang/en/backup.php
lang/en/badges.php
lang/en/cache.php
lang/en/completion.php
lang/en/enrol.php
lang/en/error.php
lang/en/group.php
lang/en/install.php
lang/en/mathslib.php
lang/en/moodle.php
lang/en/repository.php
lang/en/tag.php
lib/accesslib.php
lib/adminlib.php
lib/ajax/ajaxlib.php
lib/ajax/tests/ajaxlib_test.php [deleted file]
lib/badgeslib.php
lib/behat/behat_base.php
lib/behat/classes/behat_command.php
lib/behat/classes/behat_selectors.php [new file with mode: 0644]
lib/behat/classes/util.php
lib/blocklib.php
lib/classes/component.php
lib/classes/event/content_viewed.php [new file with mode: 0644]
lib/classes/event/course_completed.php
lib/classes/event/course_completion_updated.php
lib/classes/event/course_module_completion_updated.php
lib/classes/event/group_created.php [new file with mode: 0644]
lib/classes/event/group_deleted.php [new file with mode: 0644]
lib/classes/event/group_member_added.php [new file with mode: 0644]
lib/classes/event/group_member_removed.php [new file with mode: 0644]
lib/classes/event/group_updated.php [new file with mode: 0644]
lib/classes/event/grouping_created.php [new file with mode: 0644]
lib/classes/event/grouping_deleted.php [new file with mode: 0644]
lib/classes/event/grouping_updated.php [new file with mode: 0644]
lib/classes/event/user_created.php [new file with mode: 0644]
lib/classes/event/user_deleted.php [new file with mode: 0644]
lib/classes/event/user_enrolment_created.php [new file with mode: 0644]
lib/classes/event/user_enrolment_deleted.php [new file with mode: 0644]
lib/classes/event/user_enrolment_updated.php [new file with mode: 0644]
lib/classes/event/user_loggedin.php
lib/classes/event/user_loggedout.php [new file with mode: 0644]
lib/classes/event/user_updated.php [new file with mode: 0644]
lib/classes/text.php
lib/classes/useragent.php [new file with mode: 0644]
lib/configonlylib.php
lib/coursecatlib.php
lib/db/caches.php
lib/db/events.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/ddl/tests/ddl_test.php
lib/ddl/tests/fixtures/invalid.xml
lib/ddl/tests/fixtures/xmldb_table.xml
lib/deprecatedlib.php
lib/dml/mssql_native_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/editor/atto/db/install.php [new file with mode: 0644]
lib/editor/atto/db/subplugins.php [moved from course/format/scorm/lang/en/format_scorm.php with 68% similarity]
lib/editor/atto/lang/en/editor_atto.php [new file with mode: 0644]
lib/editor/atto/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/bold/lang/en/atto_bold.php [new file with mode: 0644]
lib/editor/atto/plugins/bold/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/bold/pix/bold.png [new file with mode: 0644]
lib/editor/atto/plugins/bold/pix/bold.svg [new file with mode: 0644]
lib/editor/atto/plugins/bold/version.php [moved from course/format/scorm/version.php with 73% similarity]
lib/editor/atto/plugins/bold/yui/build/moodle-atto_bold-button/moodle-atto_bold-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/bold/yui/build/moodle-atto_bold-button/moodle-atto_bold-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/bold/yui/build/moodle-atto_bold-button/moodle-atto_bold-button.js [new file with mode: 0644]
lib/editor/atto/plugins/bold/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/bold/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/bold/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/clear/lang/en/atto_clear.php [new file with mode: 0644]
lib/editor/atto/plugins/clear/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/clear/pix/clear.png [new file with mode: 0644]
lib/editor/atto/plugins/clear/pix/clear.svg [new file with mode: 0644]
lib/editor/atto/plugins/clear/version.php [new file with mode: 0644]
lib/editor/atto/plugins/clear/yui/build/moodle-atto_clear-button/moodle-atto_clear-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/clear/yui/build/moodle-atto_clear-button/moodle-atto_clear-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/clear/yui/build/moodle-atto_clear-button/moodle-atto_clear-button.js [new file with mode: 0644]
lib/editor/atto/plugins/clear/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/clear/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/clear/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/html/lang/en/atto_html.php [new file with mode: 0644]
lib/editor/atto/plugins/html/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/html/pix/html.png [new file with mode: 0644]
lib/editor/atto/plugins/html/pix/html.svg [new file with mode: 0644]
lib/editor/atto/plugins/html/version.php [new file with mode: 0644]
lib/editor/atto/plugins/html/yui/build/moodle-atto_html-button/moodle-atto_html-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/html/yui/build/moodle-atto_html-button/moodle-atto_html-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/html/yui/build/moodle-atto_html-button/moodle-atto_html-button.js [new file with mode: 0644]
lib/editor/atto/plugins/html/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/html/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/html/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/image/lang/en/atto_image.php [new file with mode: 0644]
lib/editor/atto/plugins/image/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/image/pix/image.png [new file with mode: 0644]
lib/editor/atto/plugins/image/pix/image.svg [new file with mode: 0644]
lib/editor/atto/plugins/image/version.php [new file with mode: 0644]
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js [new file with mode: 0644]
lib/editor/atto/plugins/image/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/image/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/image/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/indent/lang/en/atto_indent.php [new file with mode: 0644]
lib/editor/atto/plugins/indent/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/indent/pix/indent.png [new file with mode: 0644]
lib/editor/atto/plugins/indent/pix/indent.svg [new file with mode: 0644]
lib/editor/atto/plugins/indent/version.php [new file with mode: 0644]
lib/editor/atto/plugins/indent/yui/build/moodle-atto_indent-button/moodle-atto_indent-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/indent/yui/build/moodle-atto_indent-button/moodle-atto_indent-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/indent/yui/build/moodle-atto_indent-button/moodle-atto_indent-button.js [new file with mode: 0644]
lib/editor/atto/plugins/indent/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/indent/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/indent/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/italic/lang/en/atto_italic.php [new file with mode: 0644]
lib/editor/atto/plugins/italic/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/italic/pix/italic.png [new file with mode: 0644]
lib/editor/atto/plugins/italic/pix/italic.svg [new file with mode: 0644]
lib/editor/atto/plugins/italic/version.php [new file with mode: 0644]
lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button.js [new file with mode: 0644]
lib/editor/atto/plugins/italic/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/italic/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/italic/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/link/lang/en/atto_link.php [new file with mode: 0644]
lib/editor/atto/plugins/link/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/link/pix/link.png [new file with mode: 0644]
lib/editor/atto/plugins/link/pix/link.svg [new file with mode: 0644]
lib/editor/atto/plugins/link/version.php [new file with mode: 0644]
lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button.js [new file with mode: 0644]
lib/editor/atto/plugins/link/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/link/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/link/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/media/lang/en/atto_media.php [new file with mode: 0644]
lib/editor/atto/plugins/media/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/media/pix/media.png [new file with mode: 0644]
lib/editor/atto/plugins/media/pix/media.svg [new file with mode: 0644]
lib/editor/atto/plugins/media/version.php [new file with mode: 0644]
lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button.js [new file with mode: 0644]
lib/editor/atto/plugins/media/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/media/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/media/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/orderedlist/lang/en/atto_orderedlist.php [new file with mode: 0644]
lib/editor/atto/plugins/orderedlist/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/orderedlist/pix/orderedlist.png [new file with mode: 0644]
lib/editor/atto/plugins/orderedlist/pix/orderedlist.svg [new file with mode: 0644]
lib/editor/atto/plugins/orderedlist/version.php [new file with mode: 0644]
lib/editor/atto/plugins/orderedlist/yui/build/moodle-atto_orderedlist-button/moodle-atto_orderedlist-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/orderedlist/yui/build/moodle-atto_orderedlist-button/moodle-atto_orderedlist-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/orderedlist/yui/build/moodle-atto_orderedlist-button/moodle-atto_orderedlist-button.js [new file with mode: 0644]
lib/editor/atto/plugins/orderedlist/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/orderedlist/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/orderedlist/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/outdent/lang/en/atto_outdent.php [new file with mode: 0644]
lib/editor/atto/plugins/outdent/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/outdent/pix/outdent.png [new file with mode: 0644]
lib/editor/atto/plugins/outdent/pix/outdent.svg [new file with mode: 0644]
lib/editor/atto/plugins/outdent/version.php [new file with mode: 0644]
lib/editor/atto/plugins/outdent/yui/build/moodle-atto_outdent-button/moodle-atto_outdent-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/outdent/yui/build/moodle-atto_outdent-button/moodle-atto_outdent-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/outdent/yui/build/moodle-atto_outdent-button/moodle-atto_outdent-button.js [new file with mode: 0644]
lib/editor/atto/plugins/outdent/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/outdent/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/outdent/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/strike/lang/en/atto_strike.php [new file with mode: 0644]
lib/editor/atto/plugins/strike/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/strike/pix/strike.png [new file with mode: 0644]
lib/editor/atto/plugins/strike/pix/strike.svg [new file with mode: 0644]
lib/editor/atto/plugins/strike/version.php [new file with mode: 0644]
lib/editor/atto/plugins/strike/yui/build/moodle-atto_strike-button/moodle-atto_strike-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/strike/yui/build/moodle-atto_strike-button/moodle-atto_strike-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/strike/yui/build/moodle-atto_strike-button/moodle-atto_strike-button.js [new file with mode: 0644]
lib/editor/atto/plugins/strike/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/strike/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/strike/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/title/lang/en/atto_title.php [new file with mode: 0644]
lib/editor/atto/plugins/title/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/title/pix/title.png [new file with mode: 0644]
lib/editor/atto/plugins/title/pix/title.svg [new file with mode: 0644]
lib/editor/atto/plugins/title/version.php [new file with mode: 0644]
lib/editor/atto/plugins/title/yui/build/moodle-atto_title-button/moodle-atto_title-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/title/yui/build/moodle-atto_title-button/moodle-atto_title-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/title/yui/build/moodle-atto_title-button/moodle-atto_title-button.js [new file with mode: 0644]
lib/editor/atto/plugins/title/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/title/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/title/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/underline/lang/en/atto_underline.php [new file with mode: 0644]
lib/editor/atto/plugins/underline/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/underline/pix/underline.png [new file with mode: 0644]
lib/editor/atto/plugins/underline/pix/underline.svg [new file with mode: 0644]
lib/editor/atto/plugins/underline/version.php [new file with mode: 0644]
lib/editor/atto/plugins/underline/yui/build/moodle-atto_underline-button/moodle-atto_underline-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/underline/yui/build/moodle-atto_underline-button/moodle-atto_underline-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/underline/yui/build/moodle-atto_underline-button/moodle-atto_underline-button.js [new file with mode: 0644]
lib/editor/atto/plugins/underline/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/underline/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/underline/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/unlink/lang/en/atto_unlink.php [new file with mode: 0644]
lib/editor/atto/plugins/unlink/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/unlink/pix/unlink.png [new file with mode: 0644]
lib/editor/atto/plugins/unlink/pix/unlink.svg [new file with mode: 0644]
lib/editor/atto/plugins/unlink/version.php [new file with mode: 0644]
lib/editor/atto/plugins/unlink/yui/build/moodle-atto_unlink-button/moodle-atto_unlink-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/unlink/yui/build/moodle-atto_unlink-button/moodle-atto_unlink-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/unlink/yui/build/moodle-atto_unlink-button/moodle-atto_unlink-button.js [new file with mode: 0644]
lib/editor/atto/plugins/unlink/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/unlink/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/unlink/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/plugins/unorderedlist/lang/en/atto_unorderedlist.php [new file with mode: 0644]
lib/editor/atto/plugins/unorderedlist/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/unorderedlist/pix/unorderedlist.png [new file with mode: 0644]
lib/editor/atto/plugins/unorderedlist/pix/unorderedlist.svg [new file with mode: 0644]
lib/editor/atto/plugins/unorderedlist/version.php [new file with mode: 0644]
lib/editor/atto/plugins/unorderedlist/yui/build/moodle-atto_unorderedlist-button/moodle-atto_unorderedlist-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/unorderedlist/yui/build/moodle-atto_unorderedlist-button/moodle-atto_unorderedlist-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/unorderedlist/yui/build/moodle-atto_unorderedlist-button/moodle-atto_unorderedlist-button.js [new file with mode: 0644]
lib/editor/atto/plugins/unorderedlist/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/unorderedlist/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/unorderedlist/yui/src/button/meta/editor.json [new file with mode: 0644]
lib/editor/atto/styles.css [new file with mode: 0644]
lib/editor/atto/version.php [new file with mode: 0644]
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js [new file with mode: 0644]
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js [new file with mode: 0644]
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js [new file with mode: 0644]
lib/editor/atto/yui/src/editor/build.json [new file with mode: 0644]
lib/editor/atto/yui/src/editor/js/editor.js [new file with mode: 0644]
lib/editor/atto/yui/src/editor/meta/editor.json [new file with mode: 0644]
lib/editor/tinymce/lib.php
lib/editor/tinymce/module.js
lib/editor/tinymce/plugins/spellchecker/lib.php
lib/editor/tinymce/version.php
lib/editorlib.php
lib/enrollib.php
lib/excellib.class.php
lib/external/tests/external_test.php
lib/filelib.php
lib/filestorage/tests/file_storage_test.php
lib/filestorage/tests/zip_packer_test.php
lib/form/filemanager.js
lib/form/tests/dateselector_test.php
lib/form/tests/datetimeselector_test.php
lib/form/tests/duration_test.php
lib/grade/tests/fixtures/lib.php
lib/grade/tests/grade_category_test.php
lib/grade/tests/grade_grade_test.php
lib/grade/tests/grade_item_test.php
lib/grade/tests/grade_scale_test.php
lib/ldaplib.php
lib/medialib.php
lib/modinfolib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputcomponents.php
lib/outputlib.php
lib/outputrenderers.php
lib/pagelib.php
lib/phpunit/tests/advanced_test.php
lib/phpunit/tests/basic_test.php
lib/pluginlib.php
lib/setuplib.php
lib/testing/tests/generator_test.php
lib/tests/behat/behat_hooks.php
lib/tests/component_test.php
lib/tests/configonlylib_test.php
lib/tests/event_content_viewed_test.php [new file with mode: 0644]
lib/tests/fixtures/event_fixtures.php
lib/tests/medialib_test.php
lib/tests/modinfolib_test.php
lib/tests/moodlelib_test.php
lib/tests/navigationlib_test.php
lib/tests/text_test.php
lib/tests/theme_config_test.php
lib/tests/useragent_test.php [new file with mode: 0644]
lib/upgrade.txt
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js
lib/yui/build/moodle-core-notification/moodle-core-notification-debug.js
lib/yui/src/notification/js/dialogue.js
lib/yui/src/notification/js/notification.js
login/change_password.php
message/index.php
mod/assign/backup/moodle2/restore_assign_stepslib.php
mod/assign/classes/event/all_submissions_downloaded.php [new file with mode: 0644]
mod/assign/classes/event/assessable_submitted.php
mod/assign/classes/event/extension_granted.php [new file with mode: 0644]
mod/assign/classes/event/identities_revealed.php [new file with mode: 0644]
mod/assign/classes/event/marker_updated.php [new file with mode: 0644]
mod/assign/classes/event/statement_accepted.php [new file with mode: 0644]
mod/assign/classes/event/submission_duplicated.php [new file with mode: 0644]
mod/assign/classes/event/submission_graded.php [new file with mode: 0644]
mod/assign/classes/event/submission_locked.php [new file with mode: 0644]
mod/assign/classes/event/submission_status_updated.php [new file with mode: 0644]
mod/assign/classes/event/submission_unlocked.php [new file with mode: 0644]
mod/assign/classes/event/submission_updated.php [new file with mode: 0644]
mod/assign/classes/event/workflow_state_updated.php [new file with mode: 0644]
mod/assign/lang/en/assign.php
mod/assign/locallib.php
mod/assign/tests/base_test.php
mod/assign/tests/locallib_test.php
mod/assignment/backup/moodle1/lib.php
mod/assignment/backup/moodle2/restore_assignment_stepslib.php
mod/assignment/lang/en/assignment.php
mod/assignment/lib.php
mod/assignment/mod_form.php
mod/assignment/type/online/classes/event/assessable_uploaded.php
mod/assignment/type/upload/classes/event/assessable_submitted.php
mod/assignment/type/upload/classes/event/assessable_uploaded.php
mod/assignment/view.php
mod/choice/lib.php
mod/choice/renderer.php
mod/choice/report.php
mod/choice/view.php
mod/data/view.php
mod/feedback/lang/en/feedback.php
mod/forum/classes/event/assessable_uploaded.php
mod/forum/classes/observer.php [new file with mode: 0644]
mod/forum/db/events.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/tests/lib_test.php
mod/lesson/format.php
mod/lesson/import.php
mod/lesson/lang/en/lesson.php
mod/lti/lang/en/lti.php
mod/lti/locallib.php
mod/quiz/renderer.php
mod/quiz/upgrade.txt
mod/scorm/lang/en/scorm.php
mod/scorm/lib.php
mod/scorm/locallib.php
mod/scorm/mod_form.php
mod/scorm/tests/packages/badscorm.zip [new file with mode: 0644]
mod/scorm/tests/packages/invalid.zip [new file with mode: 0644]
mod/scorm/tests/packages/validaicc.zip [new file with mode: 0644]
mod/scorm/tests/packages/validscorm.zip [new file with mode: 0644]
mod/scorm/tests/validatepackage_test.php [new file with mode: 0644]
mod/wiki/pagelib.php
mod/workshop/classes/event/assessable_uploaded.php
phpunit.xml.dist
portfolio/googledocs/db/upgrade.php
portfolio/googledocs/db/upgradelib.php [new file with mode: 0644]
portfolio/picasa/db/upgrade.php
portfolio/picasa/db/upgradelib.php [new file with mode: 0644]
question/behaviour/informationitem/lang/en/qbehaviour_informationitem.php
question/engine/lib.php
report/log/classes/event/content_viewed.php [new file with mode: 0644]
report/log/graph.php
report/log/index.php
report/log/user.php
report/loglive/classes/event/content_viewed.php [new file with mode: 0644]
report/loglive/index.php
report/outline/classes/event/content_viewed.php [new file with mode: 0644]
report/outline/index.php
report/outline/user.php
report/participation/classes/event/content_viewed.php [new file with mode: 0644]
report/participation/index.php
report/security/lang/en/report_security.php
report/stats/classes/event/content_viewed.php [new file with mode: 0644]
report/stats/graph.php
report/stats/index.php
report/stats/user.php
repository/coursefiles/lib.php
repository/filepicker.js
repository/filesystem/lib.php
repository/googledocs/db/upgrade.php
repository/googledocs/db/upgradelib.php [new file with mode: 0644]
repository/lib.php
repository/picasa/db/upgrade.php
repository/picasa/db/upgradelib.php [new file with mode: 0644]
repository/repository_ajax.php
repository/tests/behat/behat_filepicker.php
repository/upgrade.txt
repository/url/locallib.php
tag/manage.php
theme/base/style/core.css
theme/bootstrapbase/config.php
theme/bootstrapbase/less/moodle/backup-restore.less
theme/bootstrapbase/less/moodle/bootstrapoverride.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/filemanager.less
theme/bootstrapbase/less/moodle/forms.less
theme/bootstrapbase/less/moodle/responsive.less
theme/bootstrapbase/less/moodle/user.less
theme/bootstrapbase/style/moodle.css
theme/index.php
theme/mymobile/config.php
theme/switchdevice.php
user/edit.php
user/editadvanced.php
user/emailupdate.php
user/filters/lib.php
user/lib.php
user/messageselect.php
user/profile/field/menu/lang/en/profilefield_menu.php
user/tests/userlib_test.php [new file with mode: 0644]
user/view.php
version.php

index 1e9e810..466832c 100644 (file)
@@ -59,6 +59,10 @@ if (empty($_GET['cache']) and empty($_POST['cache']) and empty($_GET['sesskey'])
     if (function_exists('opcache_reset')) {
         opcache_reset();
     }
+    $cache = 0;
+
+} else {
+    $cache = 1;
 }
 
 require('../config.php');
@@ -74,22 +78,28 @@ $showallplugins = optional_param('showallplugins', 0, PARAM_BOOL);
 $agreelicense   = optional_param('agreelicense', 0, PARAM_BOOL);
 $fetchupdates   = optional_param('fetchupdates', 0, PARAM_BOOL);
 $newaddonreq    = optional_param('installaddonrequest', null, PARAM_RAW);
-$cache          = optional_param('cache', 0, PARAM_BOOL);
 
 // Set up PAGE.
 $url = new moodle_url('/admin/index.php');
-if (!is_null($newaddonreq)) {
-    // We need to set the eventual add-on installation request in the $PAGE's URL
-    // so that it is stored in $SESSION->wantsurl and the admin is redirected
-    // correctly once they are logged-in.
-    $url->param('installaddonrequest', $newaddonreq);
-}
 if ($cache) {
-    $url->param('cache', $cache);
+    $url->param('cache', 1);
 }
 $PAGE->set_url($url);
 unset($url);
 
+// Are we returning from an add-on installation request at moodle.org/plugins?
+if ($newaddonreq and !$cache and empty($CFG->disableonclickaddoninstall)) {
+    $target = new moodle_url('/admin/tool/installaddon/index.php', array(
+        'installaddonrequest' => $newaddonreq,
+        'confirm' => 0));
+    if (!isloggedin() or isguestuser()) {
+        // Login and go the the add-on tool page.
+        $SESSION->wantsurl = $target->out();
+        redirect(get_login_url());
+    }
+    redirect($target);
+}
+
 $PAGE->set_pagelayout('admin'); // Set a default pagelayout
 
 $documentationlink = '<a href="http://docs.moodle.org/en/Installation">Installation docs</a>';
@@ -153,6 +163,7 @@ if (!core_tables_exist()) {
         $PAGE->set_heading($strinstallation);
         $PAGE->set_cacheable(false);
 
+        /** @var core_admin_renderer $output */
         $output = $PAGE->get_renderer('core', 'admin');
         echo $output->install_licence_page();
         die();
@@ -167,6 +178,7 @@ if (!core_tables_exist()) {
         $PAGE->set_heading($strinstallation . ' - Moodle ' . $CFG->target_release);
         $PAGE->set_cacheable(false);
 
+        /** @var core_admin_renderer $output */
         $output = $PAGE->get_renderer('core', 'admin');
         echo $output->install_environment_page($maturity, $envstatus, $environment_results, $release);
         die();
@@ -221,14 +233,12 @@ if (empty($CFG->version)) {
 }
 
 // Detect config cache inconsistency, this happens when you switch branches on dev servers.
-if ($cache) {
-    if ($CFG->version != $DB->get_field('config', 'value', array('name'=>'version'))) {
-        purge_all_caches();
-        redirect(new moodle_url('/admin/index.php'), 'Config cache inconsistency detected, resetting caches...');
-    }
+if ($CFG->version != $DB->get_field('config', 'value', array('name'=>'version'))) {
+    purge_all_caches();
+    redirect(new moodle_url('/admin/index.php'), 'Config cache inconsistency detected, resetting caches...');
 }
 
-if ($version > $CFG->version) {  // upgrade
+if (!$cache and $version > $CFG->version) {  // upgrade
     // We purge all of MUC's caches here.
     // Caches are disabled for upgrade by CACHE_DISABLE_ALL so we must set the first arg to true.
     // This ensures a real config object is loaded and the stores will be purged.
@@ -245,6 +255,7 @@ if ($version > $CFG->version) {  // upgrade
         $PAGE->set_title($stradministration);
         $PAGE->set_cacheable(false);
 
+        /** @var core_admin_renderer $output */
         $output = $PAGE->get_renderer('core', 'admin');
         echo $output->upgrade_stale_php_files_page();
         die();
@@ -260,6 +271,7 @@ if ($version > $CFG->version) {  // upgrade
         $PAGE->set_heading($strdatabasechecking);
         $PAGE->set_cacheable(false);
 
+        /** @var core_admin_renderer $output */
         $output = $PAGE->get_renderer('core', 'admin');
         echo $output->upgrade_confirm_page($a->newversion, $maturity);
         die();
@@ -274,6 +286,7 @@ if ($version > $CFG->version) {  // upgrade
         $PAGE->set_heading($strcurrentrelease);
         $PAGE->set_cacheable(false);
 
+        /** @var core_admin_renderer $output */
         $output = $PAGE->get_renderer('core', 'admin');
         echo $output->upgrade_environment_page($release, $envstatus, $environment_results);
         die();
@@ -288,10 +301,12 @@ if ($version > $CFG->version) {  // upgrade
 
         $reloadurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1));
 
+        /** @var core_admin_renderer $output */
+        $output = $PAGE->get_renderer('core', 'admin');
+
         // check plugin dependencies first
         $failed = array();
         if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) {
-            $output = $PAGE->get_renderer('core', 'admin');
             echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
             die();
         }
@@ -305,8 +320,6 @@ if ($version > $CFG->version) {  // upgrade
             redirect($reloadurl);
         }
 
-        $output = $PAGE->get_renderer('core', 'admin');
-
         $deployer = available_update_deployer::instance();
         if ($deployer->enabled()) {
             $deployer->initialize($reloadurl, $reloadurl);
@@ -333,15 +346,15 @@ if ($version > $CFG->version) {  // upgrade
 }
 
 // Updated human-readable release version if necessary
-if ($release <> $CFG->release) {  // Update the release version
+if (!$cache and $release <> $CFG->release) {  // Update the release version
     set_config('release', $release);
 }
 
-if ($branch <> $CFG->branch) {  // Update the branch
+if (!$cache and $branch <> $CFG->branch) {  // Update the branch
     set_config('branch', $branch);
 }
 
-if (moodle_needs_upgrading()) {
+if (!$cache and moodle_needs_upgrading()) {
     if (!$PAGE->headerprinted) {
         // means core upgrade or installation was not already done
         if (!$confirmplugins) {
@@ -450,8 +463,8 @@ if (during_initial_install()) {
 
 // Now we can be sure everything was upgraded and caches work fine,
 // redirect if necessary to make sure caching is enabled.
-if (!$cache and !optional_param('sesskey', '', PARAM_RAW)) {
-    redirect(new moodle_url($PAGE->url, array('cache' => 1)));
+if (!$cache) {
+    redirect(new moodle_url('/admin/index.php', array('cache' => 1)));
 }
 
 // Check for valid admin user - no guest autologin
@@ -473,17 +486,6 @@ if (!empty($id) and $id == $CFG->siteidentifier) {
     set_config('registered', time());
 }
 
-// Check if we are returning from an add-on installation request at moodle.org/plugins
-if (!is_null($newaddonreq)) {
-    if (!empty($CFG->disableonclickaddoninstall)) {
-        // The feature is disabled in config.php, ignore the request.
-    } else {
-        redirect(new moodle_url('/admin/tool/installaddon/index.php', array(
-            'installaddonrequest' => $newaddonreq,
-            'confirm' => 0)));
-    }
-}
-
 // setup critical warnings before printing admin tree block
 $insecuredataroot = is_dataroot_insecure(true);
 $SESSION->admin_critical_warning = ($insecuredataroot==INSECURE_DATAROOT_ERROR);
index 8ceba12..a092e00 100644 (file)
@@ -38,9 +38,6 @@ require_once($CFG->libdir . '/adminlib.php');
 require_once($CFG->libdir . '/pluginlib.php');
 require_once($CFG->libdir . '/filelib.php');
 
-admin_externalpage_setup('pluginsoverview');
-require_capability('moodle/site:config', context_system::instance());
-
 $fetchremote = optional_param('fetchremote', false, PARAM_BOOL);
 $updatesonly = optional_param('updatesonly', false, PARAM_BOOL);
 $contribonly = optional_param('contribonly', false, PARAM_BOOL);
@@ -48,12 +45,30 @@ $uninstall = optional_param('uninstall', '', PARAM_COMPONENT);
 $delete = optional_param('delete', '', PARAM_COMPONENT);
 $confirmed = optional_param('confirm', false, PARAM_BOOL);
 
-$output = $PAGE->get_renderer('core', 'admin');
+// NOTE: do not use admin_externalpage_setup() here because it loads
+//       full admin tree which is not possible during uninstallation.
+
+require_login();
+$syscontext = context_system::instance();
+require_capability('moodle/site:config', $syscontext);
 
 $pluginman = plugin_manager::instance();
 
 if ($uninstall) {
     require_sesskey();
+
+    if (!$confirmed) {
+        admin_externalpage_setup('pluginsoverview');
+    } else {
+        $PAGE->set_url('/admin/plugins.php');
+        $PAGE->set_context($syscontext);
+        $PAGE->set_pagelayout('maintenance');
+        $PAGE->set_popup_notification_allowed(false);
+    }
+
+    /** @var core_admin_renderer $output */
+    $output = $PAGE->get_renderer('core', 'admin');
+
     $pluginfo = $pluginman->get_plugin_info($uninstall);
 
     // Make sure we know the plugin.
@@ -104,6 +119,15 @@ if ($uninstall) {
 
 if ($delete and $confirmed) {
     require_sesskey();
+
+    $PAGE->set_url('/admin/plugins.php');
+    $PAGE->set_context($syscontext);
+    $PAGE->set_pagelayout('maintenance');
+    $PAGE->set_popup_notification_allowed(false);
+
+    /** @var core_admin_renderer $output */
+    $output = $PAGE->get_renderer('core', 'admin');
+
     $pluginfo = $pluginman->get_plugin_info($delete);
 
     // Make sure we know the plugin.
@@ -143,9 +167,15 @@ if ($delete and $confirmed) {
     if (function_exists('opcache_reset')) {
         opcache_reset();
     }
-    redirect($PAGE->url);
+    // We need to execute upgrade to make sure everything including caches is up to date.
+    redirect(new moodle_url('/admin/index.php'));
 }
 
+admin_externalpage_setup('pluginsoverview');
+
+/** @var core_admin_renderer $output */
+$output = $PAGE->get_renderer('core', 'admin');
+
 $checker = available_update_checker::instance();
 
 // Filtering options.
index 68cdb49..350254c 100644 (file)
@@ -140,12 +140,13 @@ class core_admin_renderer extends plugin_renderer_base {
     public function upgrade_confirm_page($strnewversion, $maturity) {
         $output = '';
 
-        $continueurl = new moodle_url('index.php', array('confirmupgrade' => 1));
-        $cancelurl = new moodle_url('index.php');
+        $continueurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1));
+        $continue = new single_button($continueurl, get_string('continue'), 'get');
+        $cancelurl = new moodle_url('/admin/index.php');
 
         $output .= $this->header();
         $output .= $this->maturity_warning($maturity);
-        $output .= $this->confirm(get_string('upgradesure', 'admin', $strnewversion), $continueurl, $cancelurl);
+        $output .= $this->confirm(get_string('upgradesure', 'admin', $strnewversion), $continue, $cancelurl);
         $output .= $this->footer();
 
         return $output;
@@ -412,6 +413,10 @@ class core_admin_renderer extends plugin_renderer_base {
 
         $pluginname = $pluginman->plugin_name($pluginfo->component);
 
+        // Do not show navigation here, they must click one of the buttons.
+        $this->page->set_pagelayout('maintenance');
+        $this->page->set_cacheable(false);
+
         $output .= $this->output->header();
         $output .= $this->output->heading(get_string('uninstalling', 'core_plugin', array('name' => $pluginname)));
 
@@ -425,7 +430,8 @@ class core_admin_renderer extends plugin_renderer_base {
                 'uninstalldeleteconfirmexternal');
         }
 
-        $output .= $this->output->confirm($confirm, $continueurl, $this->page->url);
+        // After any uninstall we must execute full upgrade to finish the cleanup!
+        $output .= $this->output->confirm($confirm, $continueurl, new moodle_url('/admin/index.php'));
         $output .= $this->output->footer();
 
         return $output;
@@ -442,7 +448,7 @@ class core_admin_renderer extends plugin_renderer_base {
     public function plugin_uninstall_results_page(plugin_manager $pluginman, plugininfo_base $pluginfo, progress_trace_buffer $progress) {
         $output = '';
 
-        $pluginname = $pluginman->plugin_name($pluginfo->component);
+        $pluginname = $pluginfo->component;
 
         $output .= $this->output->header();
         $output .= $this->output->heading(get_string('uninstalling', 'core_plugin', array('name' => $pluginname)));
@@ -451,7 +457,7 @@ class core_admin_renderer extends plugin_renderer_base {
 
         $output .= $this->output->box(get_string('uninstalldelete', 'core_plugin',
             array('name' => $pluginname, 'rootdir' => $pluginfo->rootdir)), 'generalbox uninstalldelete');
-        $output .= $this->output->continue_button($this->page->url);
+        $output .= $this->output->continue_button(new moodle_url('/admin/index.php'));
         $output .= $this->output->footer();
 
         return $output;
@@ -708,7 +714,7 @@ class core_admin_renderer extends plugin_renderer_base {
 
         if (!$registered) {
 
-            $registerbutton = $this->single_button(new moodle_url('registration/register.php',
+            $registerbutton = $this->single_button(new moodle_url('/admin/registration/register.php',
                     array('huburl' =>  HUB_MOODLEORGHUBURL, 'hubname' => 'Moodle.org')),
                     get_string('register', 'admin'));
 
@@ -1005,9 +1011,11 @@ class core_admin_renderer extends plugin_renderer_base {
             } else {
                 $str = 'otherplugin';
             }
+            $componenturl = new moodle_url('https://moodle.org/plugins/view.php?plugin='.$component);
+            $componenturl = html_writer::tag('a', $component, array('href' => $componenturl->out()));
             $requires[] = html_writer::tag('li',
                     get_string($str, 'core_plugin',
-                            array('component' => $component, 'version' => $requiredversion)),
+                            array('component' => $componenturl, 'version' => $requiredversion)),
                     array('class' => $class));
         }
 
index 50a08ee..8f976cf 100644 (file)
@@ -143,8 +143,10 @@ if (($action == 'edit') || ($action == 'new')) {
             $success = $repositorytype->update_options($settings);
         } else {
             $type = new repository_type($plugin, (array)$fromform, $visible);
-            $type->create();
             $success = true;
+            if (!$repoid = $type->create()) {
+                $success = false;
+            }
             $data = data_submitted();
         }
         if ($success) {
index cd7741c..66cc014 100644 (file)
@@ -49,14 +49,11 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         $this->displaypermissions = $this->allpermissions;
         $this->strperms[$this->allpermissions[CAP_INHERIT]] = get_string('notset', 'core_role');
 
-        $this->allcontextlevels = array(
-            CONTEXT_SYSTEM => get_string('coresystem'),
-            CONTEXT_USER => get_string('user'),
-            CONTEXT_COURSECAT => get_string('category'),
-            CONTEXT_COURSE => get_string('course'),
-            CONTEXT_MODULE => get_string('activitymodule'),
-            CONTEXT_BLOCK => get_string('block')
-        );
+        $this->allcontextlevels = array();
+        $levels = context_helper::get_all_levels();
+        foreach ($levels as $level => $classname) {
+            $this->allcontextlevels[$level] = context_helper::get_level_name($level);
+        }
     }
 
     protected function load_current_permissions() {
index db94c85..0f5ac5e 100644 (file)
@@ -25,7 +25,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 global $CFG;
-require_once($CFG->libdir . '/behat/classes/behat_command.php');
+require_once($CFG->libdir . '/behat/classes/behat_selectors.php');
 
 /**
  * Renderer for behat tool web features
@@ -92,7 +92,7 @@ class tool_behat_renderer extends plugin_renderer_base {
             // Replace text selector type arguments with a user-friendly select.
             $stepsdefinitions = preg_replace_callback('/(TEXT_SELECTOR\d?_STRING)/',
                 function ($matches) {
-                    return html_writer::select(behat_command::$allowedtextselectors, uniqid());
+                    return html_writer::select(behat_selectors::get_allowed_text_selectors(), uniqid());
                 },
                 $stepsdefinitions
             );
@@ -100,7 +100,7 @@ class tool_behat_renderer extends plugin_renderer_base {
             // Replace selector type arguments with a user-friendly select.
             $stepsdefinitions = preg_replace_callback('/(SELECTOR\d?_STRING)/',
                 function ($matches) {
-                    return html_writer::select(behat_command::$allowedselectors, uniqid());
+                    return html_writer::select(behat_selectors::get_allowed_selectors(), uniqid());
                 },
                 $stepsdefinitions
             );
index 637086f..4ec803d 100644 (file)
@@ -35,7 +35,7 @@ Feature: Page contents assertions
       | Course 1 | C1 | 0 |
     And I log in as "admin"
     And I follow "Course 1"
-    When I click on "Move this to the dock" "button" in the ".block_settings" "css_element"
+    When I click on "Move this to the dock" "button" in the "Administration" "block"
     Then I should not see "Question bank"
     And I click on "//div[@id='dock']/descendant::h2[normalize-space(.)='Administration']" "xpath_element"
 
@@ -45,5 +45,5 @@ Feature: Page contents assertions
       | fullname | shortname | category |
       | Course 1 | C1 | 0 |
     And I log in as "admin"
-    When I click on "Move this to the dock" "button" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' block_settings ')]" "xpath_element"
+    When I click on "Move this to the dock" "button" in the "Administration" "block"
     Then I should not see "Turn editing on"
index beed421..20b3370 100644 (file)
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
+/**
+ * Backend generic code.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Backend code for the 'make large course' tool.
+ * Backend generic code for all tool_generator commands.
  *
+ * @abstract
  * @package tool_generator
  * @copyright 2013 The Open University
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class tool_generator_backend {
+abstract class tool_generator_backend {
     /**
      * @var int Lowest (smallest) size index
      */
@@ -38,126 +47,56 @@ class tool_generator_backend {
     const DEFAULT_SIZE = 3;
 
     /**
-     * @var array Number of sections in course
-     */
-    private static $paramsections = array(1, 10, 100, 500, 1000, 2000);
-    /**
-     * @var array Number of Page activities in course
-     */
-    private static $parampages = array(1, 50, 200, 1000, 5000, 10000);
-    /**
-     * @var array Number of students enrolled in course
-     */
-    private static $paramusers = array(1, 100, 1000, 10000, 50000, 100000);
-    /**
-     * Total size of small files: 1KB, 1MB, 10MB, 100MB, 1GB, 2GB.
-     *
-     * @var array Number of small files created in a single file activity
-     */
-    private static $paramsmallfilecount = array(1, 64, 128, 1024, 16384, 32768);
-    /**
-     * @var array Size of small files (to make the totals into nice numbers)
-     */
-    private static $paramsmallfilesize = array(1024, 16384, 81920, 102400, 65536, 65536);
-    /**
-     * Total size of big files: 8KB, 8MB, 80MB, 800MB, 8GB, 16GB.
-     *
-     * @var array Number of big files created as individual file activities
-     */
-    private static $parambigfilecount = array(1, 2, 5, 10, 10, 10);
-    /**
-     * @var array Size of each large file
-     */
-    private static $parambigfilesize = array(8192, 4194304, 16777216, 83886080,
-            858993459, 1717986918);
-    /**
-     * @var array Number of forum discussions
-     */
-    private static $paramforumdiscussions = array(1, 10, 100, 500, 1000, 2000);
-    /**
-     * @var array Number of forum posts per discussion
-     */
-    private static $paramforumposts = array(2, 2, 5, 10, 10, 10);
-
-    /**
-     * @var string Course shortname
-     */
-    private $shortname;
-
-    /**
-     * @var int Size code (index in the above arrays)
+     * @var bool True if we want a fixed dataset or false to generate random data
      */
-    private $size;
+    protected $fixeddataset;
 
     /**
      * @var bool True if displaying progress
      */
-    private $progress;
-
-    /**
-     * @var testing_data_generator Data generator
-     */
-    private $generator;
-
-    /**
-     * @var stdClass Course object
-     */
-    private $course;
+    protected $progress;
 
     /**
      * @var int Epoch time at which last dot was displayed
      */
-    private $lastdot;
+    protected $lastdot;
 
     /**
      * @var int Epoch time at which last percentage was displayed
      */
-    private $lastpercentage;
+    protected $lastpercentage;
 
     /**
      * @var int Epoch time at which current step (current set of dots) started
      */
-    private $starttime;
+    protected $starttime;
 
     /**
-     * @var array Array from test user number (1...N) to userid in database
+     * @var int Size code (index in the above arrays)
      */
-    private $userids;
+    protected $size;
 
     /**
-     * Constructs object ready to create course.
+     * Generic generator class
      *
-     * @param string $shortname Course shortname
      * @param int $size Size as numeric index
+     * @param bool $fixeddataset To use fixed or random data
      * @param bool $progress True if progress information should be displayed
-     * @return int Course id
      * @throws coding_exception If parameters are invalid
      */
-    public function __construct($shortname, $size, $progress = true) {
+    public function __construct($size, $fixeddataset = false, $progress = true) {
+
         // Check parameter.
         if ($size < self::MIN_SIZE || $size > self::MAX_SIZE) {
             throw new coding_exception('Invalid size');
         }
 
         // Set parameters.
-        $this->shortname = $shortname;
         $this->size = $size;
+        $this->fixeddataset = $fixeddataset;
         $this->progress = $progress;
     }
 
-    /**
-     * Gets a list of size choices supported by this backend.
-     *
-     * @return array List of size (int) => text description for display
-     */
-    public static function get_size_choices() {
-        $options = array();
-        for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
-            $options[$size] = get_string('size_' . $size, 'tool_generator');
-        }
-        return $options;
-    }
-
     /**
      * Converts a size name into the numeric constant.
      *
@@ -174,367 +113,13 @@ class tool_generator_backend {
         throw new coding_exception("Unknown size name '$sizename'");
     }
 
-    /**
-     * Checks that a shortname is available (unused).
-     *
-     * @param string $shortname Proposed course shortname
-     * @return string An error message if the name is unavailable or '' if OK
-     */
-    public static function check_shortname_available($shortname) {
-        global $DB;
-        $fullname = $DB->get_field('course', 'fullname',
-                array('shortname' => $shortname), IGNORE_MISSING);
-        if ($fullname !== false) {
-            // I wanted to throw an exception here but it is not possible to
-            // use strings from moodle.php in exceptions, and I didn't want
-            // to duplicate the string in tool_generator, so I changed this to
-            // not use exceptions.
-            return get_string('shortnametaken', 'moodle', $fullname);
-        }
-        return '';
-    }
-
-    /**
-     * Runs the entire 'make' process.
-     *
-     * @return int Course id
-     */
-    public function make() {
-        global $DB, $CFG;
-        require_once($CFG->dirroot . '/lib/phpunit/classes/util.php');
-
-        raise_memory_limit(MEMORY_EXTRA);
-
-        if ($this->progress && !CLI_SCRIPT) {
-            echo html_writer::start_tag('ul');
-        }
-
-        $entirestart = microtime(true);
-
-        // Start transaction.
-        $transaction = $DB->start_delegated_transaction();
-
-        // Get generator.
-        $this->generator = phpunit_util::get_data_generator();
-
-        // Make course.
-        $this->course = $this->create_course();
-        $this->create_users();
-        $this->create_pages();
-        $this->create_small_files();
-        $this->create_big_files();
-        $this->create_forum();
-
-        // Log total time.
-        $this->log('complete', round(microtime(true) - $entirestart, 1));
-
-        if ($this->progress && !CLI_SCRIPT) {
-            echo html_writer::end_tag('ul');
-        }
-
-        // Commit transaction and finish.
-        $transaction->allow_commit();
-        return $this->course->id;
-    }
-
-    /**
-     * Creates the actual course.
-     *
-     * @return stdClass Course record
-     */
-    private function create_course() {
-        $this->log('createcourse', $this->shortname);
-        $courserecord = array('shortname' => $this->shortname,
-                'fullname' => get_string('fullname', 'tool_generator',
-                    array('size' => get_string('shortsize_' . $this->size, 'tool_generator'))),
-                'numsections' => self::$paramsections[$this->size]);
-        return $this->generator->create_course($courserecord, array('createsections' => true));
-    }
-
-    /**
-     * Creates a number of user accounts and enrols them on the course.
-     * Note: Existing user accounts that were created by this system are
-     * reused if available.
-     */
-    private function create_users() {
-        global $DB;
-
-        // Work out total number of users.
-        $count = self::$paramusers[$this->size];
-
-        // Get existing users in order. We will 'fill up holes' in this up to
-        // the required number.
-        $this->log('checkaccounts', $count);
-        $nextnumber = 1;
-        $rs = $DB->get_recordset_select('user', $DB->sql_like('username', '?'),
-                array('tool_generator_%'), 'username', 'id, username');
-        foreach ($rs as $rec) {
-            // Extract number from username.
-            $matches = array();
-            if (!preg_match('~^tool_generator_([0-9]{6})$~', $rec->username, $matches)) {
-                continue;
-            }
-            $number = (int)$matches[1];
-
-            // Create missing users in range up to this.
-            if ($number != $nextnumber) {
-                $this->create_user_accounts($nextnumber, min($number - 1, $count));
-            } else {
-                $this->userids[$number] = (int)$rec->id;
-            }
-
-            // Stop if we've got enough users.
-            $nextnumber = $number + 1;
-            if ($number >= $count) {
-                break;
-            }
-        }
-        $rs->close();
-
-        // Create users from end of existing range.
-        if ($nextnumber <= $count) {
-            $this->create_user_accounts($nextnumber, $count);
-        }
-
-        // Assign all users to course.
-        $this->log('enrol', $count, true);
-
-        $enrolplugin = enrol_get_plugin('manual');
-        $instances = enrol_get_instances($this->course->id, true);
-        foreach ($instances as $instance) {
-            if ($instance->enrol === 'manual') {
-                break;
-            }
-        }
-        if ($instance->enrol !== 'manual') {
-            throw new coding_exception('No manual enrol plugin in course');
-        }
-        $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
-
-        for ($number = 1; $number <= $count; $number++) {
-            // Enrol user.
-            $enrolplugin->enrol_user($instance, $this->userids[$number], $role->id);
-            $this->dot($number, $count);
-        }
-
-        $this->end_log();
-    }
-
-    /**
-     * Creates user accounts with a numeric range.
-     *
-     * @param int $first Number of first user
-     * @param int $last Number of last user
-     */
-    private function create_user_accounts($first, $last) {
-        $this->log('createaccounts', (object)array('from' => $first, 'to' => $last), true);
-        $count = $last - $first + 1;
-        $done = 0;
-        for ($number = $first; $number <= $last; $number++, $done++) {
-            // Work out username with 6-digit number.
-            $textnumber = (string)$number;
-            while (strlen($textnumber) < 6) {
-                $textnumber = '0' . $textnumber;
-            }
-            $username = 'tool_generator_' . $textnumber;
-
-            // Create user account.
-            $record = array('firstname' => get_string('firstname', 'tool_generator'),
-                    'lastname' => $number, 'username' => $username);
-            $user = $this->generator->create_user($record);
-            $this->userids[$number] = (int)$user->id;
-            $this->dot($done, $count);
-        }
-        $this->end_log();
-    }
-
-    /**
-     * Creates a number of Page activities.
-     */
-    private function create_pages() {
-        // Set up generator.
-        $pagegenerator = $this->generator->get_plugin_generator('mod_page');
-
-        // Create pages.
-        $number = self::$parampages[$this->size];
-        $this->log('createpages', $number, true);
-        for ($i=0; $i<$number; $i++) {
-            $record = array('course' => $this->course->id);
-            $options = array('section' => $this->get_random_section());
-            $pagegenerator->create_instance($record, $options);
-            $this->dot($i, $number);
-        }
-
-        $this->end_log();
-    }
-
-    /**
-     * Creates one resource activity with a lot of small files.
-     */
-    private function create_small_files() {
-        $count = self::$paramsmallfilecount[$this->size];
-        $this->log('createsmallfiles', $count, true);
-
-        // Create resource with default textfile only.
-        $resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
-        $record = array('course' => $this->course->id,
-                'name' => get_string('smallfiles', 'tool_generator'));
-        $options = array('section' => 0);
-        $resource = $resourcegenerator->create_instance($record, $options);
-
-        // Add files.
-        $fs = get_file_storage();
-        $context = context_module::instance($resource->cmid);
-        $filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
-                'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/');
-        for ($i = 0; $i < $count; $i++) {
-            $filerecord['filename'] = 'smallfile' . $i . '.dat';
-
-            // Generate random binary data (different for each file so it
-            // doesn't compress unrealistically).
-            $data = self::get_random_binary(self::$paramsmallfilesize[$this->size]);
-
-            $fs->create_file_from_string($filerecord, $data);
-            $this->dot($i, $count);
-        }
-
-        $this->end_log();
-    }
-
-    /**
-     * Creates a string of random binary data. The start of the string includes
-     * the current time, in an attempt to avoid large-scale repetition.
-     *
-     * @param int $length Number of bytes
-     * @return Random data
-     */
-    private static function get_random_binary($length) {
-        $data = microtime(true);
-        if (strlen($data) > $length) {
-            // Use last digits of data.
-            return substr($data, -$length);
-        }
-        $length -= strlen($data);
-        for ($j=0; $j < $length; $j++) {
-            $data .= chr(rand(1, 255));
-        }
-        return $data;
-    }
-
-    /**
-     * Creates a number of resource activities with one big file each.
-     */
-    private function create_big_files() {
-        global $CFG;
-
-        // Work out how many files and how many blocks to use (up to 64KB).
-        $count = self::$parambigfilecount[$this->size];
-        $blocks = ceil(self::$parambigfilesize[$this->size] / 65536);
-        $blocksize = floor(self::$parambigfilesize[$this->size] / $blocks);
-
-        $this->log('createbigfiles', $count, true);
-
-        // Prepare temp area.
-        $tempfolder = make_temp_directory('tool_generator');
-        $tempfile = $tempfolder . '/' . rand();
-
-        // Create resources and files.
-        $fs = get_file_storage();
-        $resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
-        for ($i = 0; $i < $count; $i++) {
-            // Create resource.
-            $record = array('course' => $this->course->id,
-                    'name' => get_string('bigfile', 'tool_generator', $i));
-            $options = array('section' => $this->get_random_section());
-            $resource = $resourcegenerator->create_instance($record, $options);
-
-            // Write file.
-            $handle = fopen($tempfile, 'w');
-            if (!$handle) {
-                throw new coding_exception('Failed to open temporary file');
-            }
-            for ($j = 0; $j < $blocks; $j++) {
-                $data = self::get_random_binary($blocksize);
-                fwrite($handle, $data);
-                $this->dot($i * $blocks + $j, $count * $blocks);
-            }
-            fclose($handle);
-
-            // Add file.
-            $context = context_module::instance($resource->cmid);
-            $filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
-                    'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/',
-                    'filename' => 'bigfile' . $i . '.dat');
-            $fs->create_file_from_pathname($filerecord, $tempfile);
-        }
-
-        unlink($tempfile);
-        $this->end_log();
-    }
-
-    /**
-     * Creates one forum activity with a bunch of posts.
-     */
-    private function create_forum() {
-        global $DB;
-
-        $discussions = self::$paramforumdiscussions[$this->size];
-        $posts = self::$paramforumposts[$this->size];
-        $totalposts = $discussions * $posts;
-
-        $this->log('createforum', $totalposts, true);
-
-        // Create empty forum.
-        $forumgenerator = $this->generator->get_plugin_generator('mod_forum');
-        $record = array('course' => $this->course->id,
-                'name' => get_string('pluginname', 'forum'));
-        $options = array('section' => 0);
-        $forum = $forumgenerator->create_instance($record, $options);
-
-        // Add discussions and posts.
-        $sofar = 0;
-        for ($i=0; $i < $discussions; $i++) {
-            $record = array('forum' => $forum->id, 'course' => $this->course->id,
-                    'userid' => $this->get_random_user());
-            $discussion = $forumgenerator->create_discussion($record);
-            $parentid = $DB->get_field('forum_posts', 'id', array('discussion' => $discussion->id), MUST_EXIST);
-            $sofar++;
-            for ($j=0; $j < $posts - 1; $j++, $sofar++) {
-                $record = array('discussion' => $discussion->id,
-                        'userid' => $this->get_random_user(), 'parent' => $parentid);
-                $forumgenerator->create_post($record);
-                $this->dot($sofar, $totalposts);
-            }
-        }
-
-        $this->end_log();
-    }
-
-    /**
-     * Gets a random section number.
-     *
-     * @return int A section number from 1 to the number of sections
-     */
-    private function get_random_section() {
-        return rand(1, self::$paramsections[$this->size]);
-    }
-
-    /**
-     * Gets a random user id.
-     *
-     * @return int A user id for a random created user
-     */
-    private function get_random_user() {
-        return $this->userids[rand(1, self::$paramusers[$this->size])];
-    }
-
     /**
      * Displays information as part of progress.
      * @param string $langstring Part of langstring (after progress_)
      * @param mixed $a Optional lang string parameters
      * @param bool $leaveopen If true, doesn't close LI tag (ready for dots)
      */
-    private function log($langstring, $a = null, $leaveopen = false) {
+    protected function log($langstring, $a = null, $leaveopen = false) {
         if (!$this->progress) {
             return;
         }
@@ -564,7 +149,7 @@ class tool_generator_backend {
      * @param int $number Number of completed items
      * @param int $total Total number of items to complete
      */
-    private function dot($number, $total) {
+    protected function dot($number, $total) {
         if (!$this->progress) {
             return;
         }
@@ -592,7 +177,7 @@ class tool_generator_backend {
     /**
      * Ends a log string that was started using log function with $leaveopen.
      */
-    private function end_log() {
+    protected function end_log() {
         if (!$this->progress) {
             return;
         }
@@ -603,4 +188,5 @@ class tool_generator_backend {
             echo html_writer::end_tag('li');
         }
     }
+
 }
diff --git a/admin/tool/generator/classes/course_backend.php b/admin/tool/generator/classes/course_backend.php
new file mode 100644 (file)
index 0000000..07e1e7b
--- /dev/null
@@ -0,0 +1,507 @@
+<?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/>.
+
+/**
+ * tool_generator course backend code.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Backend code for the 'make large course' tool.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_generator_course_backend extends tool_generator_backend {
+    /**
+     * @var array Number of sections in course
+     */
+    private static $paramsections = array(1, 10, 100, 500, 1000, 2000);
+    /**
+     * @var array Number of Page activities in course
+     */
+    private static $parampages = array(1, 50, 200, 1000, 5000, 10000);
+    /**
+     * @var array Number of students enrolled in course
+     */
+    private static $paramusers = array(1, 100, 1000, 10000, 50000, 100000);
+    /**
+     * Total size of small files: 1KB, 1MB, 10MB, 100MB, 1GB, 2GB.
+     *
+     * @var array Number of small files created in a single file activity
+     */
+    private static $paramsmallfilecount = array(1, 64, 128, 1024, 16384, 32768);
+    /**
+     * @var array Size of small files (to make the totals into nice numbers)
+     */
+    private static $paramsmallfilesize = array(1024, 16384, 81920, 102400, 65536, 65536);
+    /**
+     * Total size of big files: 8KB, 8MB, 80MB, 800MB, 8GB, 16GB.
+     *
+     * @var array Number of big files created as individual file activities
+     */
+    private static $parambigfilecount = array(1, 2, 5, 10, 10, 10);
+    /**
+     * @var array Size of each large file
+     */
+    private static $parambigfilesize = array(8192, 4194304, 16777216, 83886080,
+            858993459, 1717986918);
+    /**
+     * @var array Number of forum discussions
+     */
+    private static $paramforumdiscussions = array(1, 10, 100, 500, 1000, 2000);
+    /**
+     * @var array Number of forum posts per discussion
+     */
+    private static $paramforumposts = array(2, 2, 5, 10, 10, 10);
+
+    /**
+     * @var string Course shortname
+     */
+    private $shortname;
+
+    /**
+     * @var testing_data_generator Data generator
+     */
+    protected $generator;
+
+    /**
+     * @var stdClass Course object
+     */
+    private $course;
+
+    /**
+     * @var array Array from test user number (1...N) to userid in database
+     */
+    private $userids;
+
+    /**
+     * Constructs object ready to create course.
+     *
+     * @param string $shortname Course shortname
+     * @param int $size Size as numeric index
+     * @param bool $fixeddataset To use fixed or random data
+     * @param bool $progress True if progress information should be displayed
+     * @return int Course id
+     */
+    public function __construct($shortname, $size, $fixeddataset = false, $progress = true) {
+
+        // Set parameters.
+        $this->shortname = $shortname;
+
+        parent::__construct($size, $fixeddataset, $progress);
+    }
+
+    /**
+     * Gets a list of size choices supported by this backend.
+     *
+     * @return array List of size (int) => text description for display
+     */
+    public static function get_size_choices() {
+        $options = array();
+        for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
+            $options[$size] = get_string('coursesize_' . $size, 'tool_generator');
+        }
+        return $options;
+    }
+
+    /**
+     * Checks that a shortname is available (unused).
+     *
+     * @param string $shortname Proposed course shortname
+     * @return string An error message if the name is unavailable or '' if OK
+     */
+    public static function check_shortname_available($shortname) {
+        global $DB;
+        $fullname = $DB->get_field('course', 'fullname',
+                array('shortname' => $shortname), IGNORE_MISSING);
+        if ($fullname !== false) {
+            // I wanted to throw an exception here but it is not possible to
+            // use strings from moodle.php in exceptions, and I didn't want
+            // to duplicate the string in tool_generator, so I changed this to
+            // not use exceptions.
+            return get_string('shortnametaken', 'moodle', $fullname);
+        }
+        return '';
+    }
+
+    /**
+     * Runs the entire 'make' process.
+     *
+     * @return int Course id
+     */
+    public function make() {
+        global $DB, $CFG;
+        require_once($CFG->dirroot . '/lib/phpunit/classes/util.php');
+
+        raise_memory_limit(MEMORY_EXTRA);
+
+        if ($this->progress && !CLI_SCRIPT) {
+            echo html_writer::start_tag('ul');
+        }
+
+        $entirestart = microtime(true);
+
+        // Start transaction.
+        $transaction = $DB->start_delegated_transaction();
+
+        // Get generator.
+        $this->generator = phpunit_util::get_data_generator();
+
+        // Make course.
+        $this->course = $this->create_course();
+        $this->create_users();
+        $this->create_pages();
+        $this->create_small_files();
+        $this->create_big_files();
+        $this->create_forum();
+
+        // Log total time.
+        $this->log('coursecompleted', round(microtime(true) - $entirestart, 1));
+
+        if ($this->progress && !CLI_SCRIPT) {
+            echo html_writer::end_tag('ul');
+        }
+
+        // Commit transaction and finish.
+        $transaction->allow_commit();
+        return $this->course->id;
+    }
+
+    /**
+     * Creates the actual course.
+     *
+     * @return stdClass Course record
+     */
+    private function create_course() {
+        $this->log('createcourse', $this->shortname);
+        $courserecord = array('shortname' => $this->shortname,
+                'fullname' => get_string('fullname', 'tool_generator',
+                    array('size' => get_string('shortsize_' . $this->size, 'tool_generator'))),
+                'numsections' => self::$paramsections[$this->size]);
+        return $this->generator->create_course($courserecord, array('createsections' => true));
+    }
+
+    /**
+     * Creates a number of user accounts and enrols them on the course.
+     * Note: Existing user accounts that were created by this system are
+     * reused if available.
+     */
+    private function create_users() {
+        global $DB;
+
+        // Work out total number of users.
+        $count = self::$paramusers[$this->size];
+
+        // Get existing users in order. We will 'fill up holes' in this up to
+        // the required number.
+        $this->log('checkaccounts', $count);
+        $nextnumber = 1;
+        $rs = $DB->get_recordset_select('user', $DB->sql_like('username', '?'),
+                array('tool_generator_%'), 'username', 'id, username');
+        foreach ($rs as $rec) {
+            // Extract number from username.
+            $matches = array();
+            if (!preg_match('~^tool_generator_([0-9]{6})$~', $rec->username, $matches)) {
+                continue;
+            }
+            $number = (int)$matches[1];
+
+            // Create missing users in range up to this.
+            if ($number != $nextnumber) {
+                $this->create_user_accounts($nextnumber, min($number - 1, $count));
+            } else {
+                $this->userids[$number] = (int)$rec->id;
+            }
+
+            // Stop if we've got enough users.
+            $nextnumber = $number + 1;
+            if ($number >= $count) {
+                break;
+            }
+        }
+        $rs->close();
+
+        // Create users from end of existing range.
+        if ($nextnumber <= $count) {
+            $this->create_user_accounts($nextnumber, $count);
+        }
+
+        // Assign all users to course.
+        $this->log('enrol', $count, true);
+
+        $enrolplugin = enrol_get_plugin('manual');
+        $instances = enrol_get_instances($this->course->id, true);
+        foreach ($instances as $instance) {
+            if ($instance->enrol === 'manual') {
+                break;
+            }
+        }
+        if ($instance->enrol !== 'manual') {
+            throw new coding_exception('No manual enrol plugin in course');
+        }
+        $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
+
+        for ($number = 1; $number <= $count; $number++) {
+            // Enrol user.
+            $enrolplugin->enrol_user($instance, $this->userids[$number], $role->id);
+            $this->dot($number, $count);
+        }
+
+        // Sets the pointer at the beginning to be aware of the users we use.
+        reset($this->userids);
+
+        $this->end_log();
+    }
+
+    /**
+     * Creates user accounts with a numeric range.
+     *
+     * @param int $first Number of first user
+     * @param int $last Number of last user
+     */
+    private function create_user_accounts($first, $last) {
+        $this->log('createaccounts', (object)array('from' => $first, 'to' => $last), true);
+        $count = $last - $first + 1;
+        $done = 0;
+        for ($number = $first; $number <= $last; $number++, $done++) {
+            // Work out username with 6-digit number.
+            $textnumber = (string)$number;
+            while (strlen($textnumber) < 6) {
+                $textnumber = '0' . $textnumber;
+            }
+            $username = 'tool_generator_' . $textnumber;
+
+            // Create user account.
+            $record = array('firstname' => get_string('firstname', 'tool_generator'),
+                    'lastname' => $number, 'username' => $username);
+            $user = $this->generator->create_user($record);
+            $this->userids[$number] = (int)$user->id;
+            $this->dot($done, $count);
+        }
+        $this->end_log();
+    }
+
+    /**
+     * Creates a number of Page activities.
+     */
+    private function create_pages() {
+        // Set up generator.
+        $pagegenerator = $this->generator->get_plugin_generator('mod_page');
+
+        // Create pages.
+        $number = self::$parampages[$this->size];
+        $this->log('createpages', $number, true);
+        for ($i=0; $i<$number; $i++) {
+            $record = array('course' => $this->course->id);
+            $options = array('section' => $this->get_target_section());
+            $pagegenerator->create_instance($record, $options);
+            $this->dot($i, $number);
+        }
+
+        $this->end_log();
+    }
+
+    /**
+     * Creates one resource activity with a lot of small files.
+     */
+    private function create_small_files() {
+        $count = self::$paramsmallfilecount[$this->size];
+        $this->log('createsmallfiles', $count, true);
+
+        // Create resource with default textfile only.
+        $resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
+        $record = array('course' => $this->course->id,
+                'name' => get_string('smallfiles', 'tool_generator'));
+        $options = array('section' => 0);
+        $resource = $resourcegenerator->create_instance($record, $options);
+
+        // Add files.
+        $fs = get_file_storage();
+        $context = context_module::instance($resource->cmid);
+        $filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
+                'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/');
+        for ($i = 0; $i < $count; $i++) {
+            $filerecord['filename'] = 'smallfile' . $i . '.dat';
+
+            // Generate random binary data (different for each file so it
+            // doesn't compress unrealistically).
+            $data = self::get_random_binary(self::$paramsmallfilesize[$this->size]);
+
+            $fs->create_file_from_string($filerecord, $data);
+            $this->dot($i, $count);
+        }
+
+        $this->end_log();
+    }
+
+    /**
+     * Creates a string of random binary data. The start of the string includes
+     * the current time, in an attempt to avoid large-scale repetition.
+     *
+     * @param int $length Number of bytes
+     * @return Random data
+     */
+    private static function get_random_binary($length) {
+        $data = microtime(true);
+        if (strlen($data) > $length) {
+            // Use last digits of data.
+            return substr($data, -$length);
+        }
+        $length -= strlen($data);
+        for ($j=0; $j < $length; $j++) {
+            $data .= chr(rand(1, 255));
+        }
+        return $data;
+    }
+
+    /**
+     * Creates a number of resource activities with one big file each.
+     */
+    private function create_big_files() {
+        global $CFG;
+
+        // Work out how many files and how many blocks to use (up to 64KB).
+        $count = self::$parambigfilecount[$this->size];
+        $blocks = ceil(self::$parambigfilesize[$this->size] / 65536);
+        $blocksize = floor(self::$parambigfilesize[$this->size] / $blocks);
+
+        $this->log('createbigfiles', $count, true);
+
+        // Prepare temp area.
+        $tempfolder = make_temp_directory('tool_generator');
+        $tempfile = $tempfolder . '/' . rand();
+
+        // Create resources and files.
+        $fs = get_file_storage();
+        $resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
+        for ($i = 0; $i < $count; $i++) {
+            // Create resource.
+            $record = array('course' => $this->course->id,
+                    'name' => get_string('bigfile', 'tool_generator', $i));
+            $options = array('section' => $this->get_target_section());
+            $resource = $resourcegenerator->create_instance($record, $options);
+
+            // Write file.
+            $handle = fopen($tempfile, 'w');
+            if (!$handle) {
+                throw new coding_exception('Failed to open temporary file');
+            }
+            for ($j = 0; $j < $blocks; $j++) {
+                $data = self::get_random_binary($blocksize);
+                fwrite($handle, $data);
+                $this->dot($i * $blocks + $j, $count * $blocks);
+            }
+            fclose($handle);
+
+            // Add file.
+            $context = context_module::instance($resource->cmid);
+            $filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
+                    'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/',
+                    'filename' => 'bigfile' . $i . '.dat');
+            $fs->create_file_from_pathname($filerecord, $tempfile);
+        }
+
+        unlink($tempfile);
+        $this->end_log();
+    }
+
+    /**
+     * Creates one forum activity with a bunch of posts.
+     */
+    private function create_forum() {
+        global $DB;
+
+        $discussions = self::$paramforumdiscussions[$this->size];
+        $posts = self::$paramforumposts[$this->size];
+        $totalposts = $discussions * $posts;
+
+        $this->log('createforum', $totalposts, true);
+
+        // Create empty forum.
+        $forumgenerator = $this->generator->get_plugin_generator('mod_forum');
+        $record = array('course' => $this->course->id,
+                'name' => get_string('pluginname', 'forum'));
+        $options = array('section' => 0);
+        $forum = $forumgenerator->create_instance($record, $options);
+
+        // Add discussions and posts.
+        $sofar = 0;
+        for ($i=0; $i < $discussions; $i++) {
+            $record = array('forum' => $forum->id, 'course' => $this->course->id,
+                    'userid' => $this->get_target_user());
+            $discussion = $forumgenerator->create_discussion($record);
+            $parentid = $DB->get_field('forum_posts', 'id', array('discussion' => $discussion->id), MUST_EXIST);
+            $sofar++;
+            for ($j=0; $j < $posts - 1; $j++, $sofar++) {
+                $record = array('discussion' => $discussion->id,
+                        'userid' => $this->get_target_user(), 'parent' => $parentid);
+                $forumgenerator->create_post($record);
+                $this->dot($sofar, $totalposts);
+            }
+        }
+
+        $this->end_log();
+    }
+
+    /**
+     * Gets a section number.
+     *
+     * Depends on $this->fixeddataset.
+     *
+     * @return int A section number from 1 to the number of sections
+     */
+    private function get_target_section() {
+
+        if (!$this->fixeddataset) {
+            $key = rand(1, self::$paramsections[$this->size]);
+        } else {
+            // Using section 1.
+            $key = 1;
+        }
+
+        return $key;
+    }
+
+    /**
+     * Gets a user id.
+     *
+     * Depends on $this->fixeddataset.
+     *
+     * @return int A user id for a random created user
+     */
+    private function get_target_user() {
+
+        if (!$this->fixeddataset) {
+            $userid = $this->userids[rand(1, self::$paramusers[$this->size])];
+        } else if ($userid = current($this->userids)) {
+            // Moving pointer to the next user.
+            next($this->userids);
+        } else {
+            // Returning to the beginning if we reached the end.
+            $userid = reset($this->userids);
+        }
+
+        return $userid;
+    }
+
+}
index 25c8350..879364d 100644 (file)
@@ -31,8 +31,8 @@ class tool_generator_make_form extends moodleform {
         $mform = $this->_form;
 
         $mform->addElement('select', 'size', get_string('size', 'tool_generator'),
-                tool_generator_backend::get_size_choices());
-        $mform->setDefault('size', tool_generator_backend::DEFAULT_SIZE);
+                tool_generator_course_backend::get_size_choices());
+        $mform->setDefault('size', tool_generator_course_backend::DEFAULT_SIZE);
 
         $mform->addElement('text', 'shortname', get_string('shortnamecourse'));
         $mform->addRule('shortname', get_string('missingshortname'), 'required', null, 'client');
@@ -48,7 +48,7 @@ class tool_generator_make_form extends moodleform {
         // Check course doesn't already exist.
         if (!empty($data['shortname'])) {
             // Check shortname.
-            $error =  tool_generator_backend::check_shortname_available($data['shortname']);
+            $error =  tool_generator_course_backend::check_shortname_available($data['shortname']);
             if ($error) {
                 $errors['shortname'] = $error;
             }
diff --git a/admin/tool/generator/classes/site_backend.php b/admin/tool/generator/classes/site_backend.php
new file mode 100644 (file)
index 0000000..01bde97
--- /dev/null
@@ -0,0 +1,203 @@
+<?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/>.
+
+/**
+ * tool_generator site backend.
+ *
+ * @package tool_generator
+ * @copyright 2013 David Monllaó
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Backend code for the site generator.
+ *
+ * @package tool_generator
+ * @copyright 2013 David Monllaó
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_generator_site_backend extends tool_generator_backend {
+
+    /**
+     * @var string The course's shortname prefix.
+     */
+    const SHORTNAMEPREFIX = 'testcourse_';
+
+    /**
+     * @var bool If the debugging level checking was skipped.
+     */
+    protected $bypasscheck;
+
+    /**
+     * @var array Multidimensional array where the first level is the course size and the second the site size.
+     */
+    protected static $sitecourses = array(
+        array(2, 8, 64, 256, 1024, 4096),
+        array(1, 4, 8, 16, 32, 64),
+        array(0, 0, 1, 4, 8, 16),
+        array(0, 0, 0, 1, 0, 0),
+        array(0, 0, 0, 0, 1, 0),
+        array(0, 0, 0, 0, 0, 1)
+    );
+
+    /**
+     * Constructs object ready to make the site.
+     *
+     * @param int $size Size as numeric index
+     * @param bool $bypasscheck If debugging level checking was skipped.
+     * @param bool $fixeddataset To use fixed or random data
+     * @param bool $progress True if progress information should be displayed
+     * @return int Course id
+     */
+    public function __construct($size, $bypasscheck, $fixeddataset = false, $progress = true) {
+
+        // Set parameters.
+        $this->bypasscheck = $bypasscheck;
+
+        parent::__construct($size, $fixeddataset, $progress);
+    }
+
+    /**
+     * Gets a list of size choices supported by this backend.
+     *
+     * @return array List of size (int) => text description for display
+     */
+    public static function get_size_choices() {
+        $options = array();
+        for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
+            $options[$size] = get_string('sitesize_' . $size, 'tool_generator');
+        }
+        return $options;
+    }
+
+    /**
+     * Runs the entire 'make' process.
+     *
+     * @return int Course id
+     */
+    public function make() {
+        global $DB, $CFG;
+
+        raise_memory_limit(MEMORY_EXTRA);
+
+        if ($this->progress && !CLI_SCRIPT) {
+            echo html_writer::start_tag('ul');
+        }
+
+        $entirestart = microtime(true);
+
+        // Create courses.
+        $prevchdir = getcwd();
+        chdir($CFG->dirroot);
+        $ncourse = $this->get_last_testcourse_id();
+        foreach (self::$sitecourses as $coursesize => $ncourses) {
+            for ($i = 1; $i <= $ncourses[$this->size]; $i++) {
+                // Non language-dependant shortname.
+                $ncourse++;
+                $this->run_create_course(self::SHORTNAMEPREFIX . $ncourse, $coursesize);
+            }
+        }
+        chdir($prevchdir);
+
+        // Store last course id to return it (will be the bigger one).
+        $lastcourseid = $DB->get_field('course', 'id', array('shortname' => self::SHORTNAMEPREFIX . $ncourse));
+
+        // Log total time.
+        $this->log('sitecompleted', round(microtime(true) - $entirestart, 1));
+
+        if ($this->progress && !CLI_SCRIPT) {
+            echo html_writer::end_tag('ul');
+        }
+
+        return $lastcourseid;
+    }
+
+    /**
+     * Creates a course with the specified shortname, coursesize and the provided maketestsite options.
+     *
+     * @param string $shortname The course shortname
+     * @param int $coursesize One of the possible course sizes.
+     * @return void
+     */
+    protected function run_create_course($shortname, $coursesize) {
+
+        // We are in $CFG->dirroot.
+        $command = 'php admin/tool/generator/cli/maketestcourse.php';
+
+        $options = array(
+            '--shortname="' . $shortname . '"',
+            '--size="' . get_string('shortsize_' . $coursesize, 'tool_generator') . '"'
+        );
+
+        if (!$this->progress) {
+            $options[] = '--quiet';
+        }
+
+        // Extend options.
+        $optionstoextend = array(
+            'fixeddataset' => 'fixeddataset',
+            'bypasscheck' => 'bypasscheck',
+        );
+
+        // Getting an options string.
+        foreach ($optionstoextend as $attribute => $option) {
+            if (!empty($this->{$attribute})) {
+                $options[] = '--' . $option;
+            }
+        }
+        $options = implode(' ', $options);
+        if ($this->progress) {
+            system($command . ' ' . $options, $exitcode);
+        } else {
+            passthru($command . ' ' . $options, $exitcode);
+        }
+
+        if ($exitcode != 0) {
+            exit($exitcode);
+        }
+    }
+
+    /**
+     * Obtains the last unique sufix (numeric) using the test course prefix.
+     *
+     * @return int The last generated numeric value.
+     */
+    protected function get_last_testcourse_id() {
+        global $DB;
+
+        $params = array();
+        $params['shortnameprefix'] = $DB->sql_like_escape(self::SHORTNAMEPREFIX) . '%';
+        $like = $DB->sql_like('shortname', ':shortnameprefix');
+
+        if (!$testcourses = $DB->get_records_select('course', $like, $params, 'shortname DESC')) {
+            return 0;
+        }
+
+        // They come ordered by shortname DESC, so non-numeric values will be the first ones.
+        foreach ($testcourses as $testcourse) {
+            $sufix = substr($testcourse->shortname, strlen(self::SHORTNAMEPREFIX));
+            if (is_numeric($sufix)) {
+                return $sufix;
+            }
+        }
+
+        // If all sufixes are not numeric this is the fist make test site run.
+        return 0;
+    }
+
+}
index b646e2f..407a58f 100644 (file)
@@ -34,6 +34,7 @@ list($options, $unrecognized) = cli_get_params(
         'help' => false,
         'shortname' => false,
         'size' => false,
+        'fixeddataset' => false,
         'bypasscheck' => false,
         'quiet' => false
     ),
@@ -53,6 +54,7 @@ level.
 Options:
 --shortname    Shortname of course to create (required)
 --size         Size of course to create XS, S, M, L, XL, or XXL (required)
+--fixeddataset Use a fixed data set instead of randomly generated data
 --bypasscheck  Bypasses the developer-mode check (be careful!)
 --quiet        Do not show any output
 
@@ -73,16 +75,17 @@ if (empty($options['bypasscheck']) && !debugging('', DEBUG_DEVELOPER)) {
 // Get options.
 $shortname = $options['shortname'];
 $sizename = $options['size'];
+$fixeddataset = $options['fixeddataset'];
 
 // Check size.
 try {
-    $size = tool_generator_backend::size_for_name($sizename);
+    $size = tool_generator_course_backend::size_for_name($sizename);
 } catch (coding_exception $e) {
     cli_error("Invalid size ($sizename). Use --help for help.");
 }
 
 // Check shortname.
-if ($error = tool_generator_backend::check_shortname_available($shortname)) {
+if ($error = tool_generator_course_backend::check_shortname_available($shortname)) {
     cli_error($error);
 }
 
@@ -90,5 +93,5 @@ if ($error = tool_generator_backend::check_shortname_available($shortname)) {
 session_set_user(get_admin());
 
 // Do backend code to generate course.
-$backend = new tool_generator_backend($shortname, $size, empty($options['quiet']));
+$backend = new tool_generator_course_backend($shortname, $size, $fixeddataset, empty($options['quiet']));
 $id = $backend->make();
diff --git a/admin/tool/generator/cli/maketestsite.php b/admin/tool/generator/cli/maketestsite.php
new file mode 100644 (file)
index 0000000..bc91d7d
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * CLI interface for creating a test site.
+ *
+ * @package tool_generator
+ * @copyright 2013 David Monllaó
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+define('NO_OUTPUT_BUFFERING', true);
+
+require(__DIR__ . '/../../../../config.php');
+require_once($CFG->libdir. '/clilib.php');
+
+// CLI options.
+list($options, $unrecognized) = cli_get_params(
+    array(
+        'help' => false,
+        'size' => false,
+        'fixeddataset' => false,
+        'bypasscheck' => false,
+        'quiet' => false
+    ),
+    array(
+        'h' => 'help'
+    )
+);
+
+$sitesizes = '* ' . implode(PHP_EOL . '* ', tool_generator_site_backend::get_size_choices());
+
+// Display help.
+if (!empty($options['help']) || empty($options['size'])) {
+    echo "
+Utility to generate a standard test site data set.
+
+Not for use on live sites; only normally works if debugging is set to DEVELOPER
+level.
+
+Consider that, depending on the size you select, this CLI tool can really generate a lot of data, aproximated sizes:
+
+$sitesizes
+
+Options:
+--size         Size of the generated site, this value affects the number of courses and their size. Accepted values: XS, S, M, L, XL, or XXL (required)
+--fixeddataset Use a fixed data set instead of randomly generated data
+--bypasscheck  Bypasses the developer-mode check (be careful!)
+--quiet        Do not show any output
+
+-h, --help     Print out this help
+
+Example from Moodle root directory:
+\$ php admin/tool/generator/cli/maketestsite.php --size=S
+";
+    // Exit with error unless we're showing this because they asked for it.
+    exit(empty($options['help']) ? 1 : 0);
+}
+
+// Check debugging is set to developer level.
+if (empty($options['bypasscheck']) && !$CFG->debugdeveloper) {
+    cli_error(get_string('error_notdebugging', 'tool_generator'));
+}
+
+// Get options.
+$sizename = $options['size'];
+$fixeddataset = $options['fixeddataset'];
+
+// Check size.
+try {
+    $size = tool_generator_site_backend::size_for_name($sizename);
+} catch (coding_exception $e) {
+    cli_error("Invalid size ($sizename). Use --help for help.");
+}
+
+// Switch to admin user account.
+session_set_user(get_admin());
+
+// Do backend code to generate site.
+$backend = new tool_generator_site_backend($size, $options['bypasscheck'], $fixeddataset, empty($options['quiet']));
+$backend->make();
index 103a4fd..a204ce8 100644 (file)
  */
 
 $string['bigfile'] = 'Big file {$a}';
+$string['coursesize_0'] = 'XS (~10KB; create in ~1 second)';
+$string['coursesize_1'] = 'S (~10MB; create in ~30 seconds)';
+$string['coursesize_2'] = 'M (~100MB; create in ~5 minutes)';
+$string['coursesize_3'] = 'L (~1GB; create in ~1 hour)';
+$string['coursesize_4'] = 'XL (~10GB; create in ~4 hours)';
+$string['coursesize_5'] = 'XXL (~20GB; create in ~8 hours)';
 $string['createcourse'] = 'Create course';
 $string['creating'] = 'Creating course';
 $string['done'] = 'done ({$a}s)';
@@ -48,27 +54,28 @@ $string['error_notdebugging'] = 'Not available on this server because debugging
 $string['firstname'] = 'Test course user';
 $string['fullname'] = 'Test course: {$a->size}';
 $string['maketestcourse'] = 'Make test course';
-$string['pluginname'] = 'Random course generator';
+$string['pluginname'] = 'Development data generator';
 $string['progress_createcourse'] = 'Creating course {$a}';
 $string['progress_checkaccounts'] = 'Checking user accounts ({$a})';
+$string['progress_coursecompleted'] = 'Course completed ({$a}s)';
 $string['progress_createaccounts'] = 'Creating user accounts ({$a->from} - {$a->to})';
 $string['progress_createbigfiles'] = 'Creating big files ({$a})';
 $string['progress_createforum'] = 'Creating forum ({$a} posts)';
 $string['progress_createpages'] = 'Creating pages ({$a})';
 $string['progress_createsmallfiles'] = 'Creating small files ({$a})';
 $string['progress_enrol'] = 'Enrolling users into course ({$a})';
-$string['progress_complete'] = 'Complete ({$a}s)';
+$string['progress_sitecompleted'] = 'Site completed ({$a}s)';
 $string['shortsize_0'] = 'XS';
 $string['shortsize_1'] = 'S';
 $string['shortsize_2'] = 'M';
 $string['shortsize_3'] = 'L';
 $string['shortsize_4'] = 'XL';
 $string['shortsize_5'] = 'XXL';
+$string['sitesize_0'] = 'XS (~10MB; 3 courses, created in ~30 seconds)';
+$string['sitesize_1'] = 'S (~50MB; 8 courses, created in ~2 minutes)';
+$string['sitesize_2'] = 'M (~200MB; 73 courses, created in ~10 minutes)';
+$string['sitesize_3'] = 'L (~1\'5GB; 277 courses, created in ~1\'5 hours)';
+$string['sitesize_4'] = 'XL (~10GB; 1065 courses, created in ~5 hours)';
+$string['sitesize_5'] = 'XXL (~20GB; 4177 courses, created in ~10 hours)';
 $string['size'] = 'Size of course';
-$string['size_0'] = 'XS (~10KB; create in ~1 second)';
-$string['size_1'] = 'S (~10MB; create in ~30 seconds)';
-$string['size_2'] = 'M (~100MB; create in ~5 minutes)';
-$string['size_3'] = 'L (~1GB; create in ~1 hour)';
-$string['size_4'] = 'XL (~10GB; create in ~4 hours)';
-$string['size_5'] = 'XXL (~20GB; create in ~8 hours)';
 $string['smallfiles'] = 'Small files';
index f3ff9f7..82af097 100644 (file)
@@ -55,7 +55,7 @@ $mform = new tool_generator_make_form('maketestcourse.php');
 if ($data = $mform->get_data()) {
     // Do actual work.
     echo $OUTPUT->heading(get_string('creating', 'tool_generator'));
-    $backend = new tool_generator_backend($data->shortname, $data->size);
+    $backend = new tool_generator_course_backend($data->shortname, $data->size);
     $id = $backend->make();
 
     echo html_writer::div(
index 3b2244d..b8637f4 100644 (file)
@@ -35,7 +35,7 @@ class tool_generator_maketestcourse_testcase extends advanced_testcase {
         $this->setAdminUser();
 
         // Create the XS course.
-        $backend = new tool_generator_backend('TOOL_MAKELARGECOURSE_XS', 0, false);
+        $backend = new tool_generator_course_backend('TOOL_MAKELARGECOURSE_XS', 0, false, false);
         $courseid = $backend->make();
 
         // Get course details.
@@ -107,4 +107,48 @@ class tool_generator_maketestcourse_testcase extends advanced_testcase {
                     fd.forum = ?", array($forum->instance));
         $this->assertEquals(2, $posts);
     }
+
+    /**
+     * Creates an small test course with fixed data set and checks the used sections and users.
+     */
+    public function test_fixed_data_set() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        // Create the S course (more sections and activities than XS).
+        $backend = new tool_generator_course_backend('TOOL_S_COURSE_1', 1, true, false);
+        $courseid = $backend->make();
+
+        // Get course details.
+        $course = get_course($courseid);
+        $modinfo = get_fast_modinfo($course);
+
+        // Check module instances belongs to section 1.
+        $instances = $modinfo->get_instances_of('page');
+        $npageinstances = count($instances);
+        foreach ($instances as $instance) {
+            $this->assertEquals(1, $instance->sectionnum);
+        }
+
+        // Users that started discussions are the same.
+        $forums = $modinfo->get_instances_of('forum');
+        $nforuminstances = count($forums);
+        $discussions = forum_get_discussions(reset($forums), 'd.timemodified ASC');
+        $lastusernumber = 0;
+        $discussionstarters = array();
+        foreach ($discussions as $discussion) {
+            $usernumber = intval($discussion->lastname);
+
+            // Checks that the users are odd numbers.
+            $this->assertEquals(1, $usernumber % 2);
+
+            // Checks that the users follows an increasing order.
+            $this->assertGreaterThan($lastusernumber, $usernumber);
+            $lastusernumber = $usernumber;
+            $discussionstarters[$discussion->userid] = $discussion->subject;
+        }
+
+    }
 }
index 66aa662..3733d98 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2013080700;
-$plugin->requires = 2013080200;
+$plugin->version = 2013090200;
+$plugin->requires = 2013090200;
 $plugin->component = 'tool_generator';
index e194219..87dee88 100644 (file)
@@ -23,7 +23,7 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['install'] = 'Install selected language pack';
+$string['install'] = 'Install selected language pack(s)';
 $string['installedlangs'] = 'Installed language packs';
 $string['langimport'] = 'Language import utility';
 $string['langimportdisabled'] = 'Language import feature has been disabled. You have to update your language packs manually at the file-system level. Do not forget to purge string caches after you do so.';
@@ -38,7 +38,7 @@ $string['nolangupdateneeded'] = 'All your language packs are up to date, no upda
 $string['pluginname'] = 'Language packs';
 $string['purgestringcaches'] = 'Purge string caches';
 $string['remotelangnotavailable'] = 'Because Moodle cannot connect to download.moodle.org, it is not possible for language packs to be installed automatically. Please download the appropriate ZIP file(s) from <a href="http://download.moodle.org/langpack/">download.moodle.org/langpack</a>, copy them to your {$a} directory and unzip them manually.';
-$string['uninstall'] = 'Uninstall selected language pack';
+$string['uninstall'] = 'Uninstall selected language pack(s)';
 $string['uninstallconfirm'] = 'You are about to completely uninstall language pack {$a}, are you sure?';
 $string['updatelangs'] = 'Update all installed language packs';
 
index 3be7ec4..999ca70 100644 (file)
@@ -99,7 +99,7 @@ $string['shortnametemplate_help'] = 'The short name of the course is displayed i
 $string['templatefile'] = 'Restore from this file after upload';
 $string['templatefile_help'] = 'Select a file to use as a template for the creation of all courses.';
 $string['unknownimportmode'] = 'Unknown import mode';
-$string['updatemissing'] = 'Fill in missing from CSV data and defaults';
+$string['updatemissing'] = 'Fill in missing items from CSV data and defaults';
 $string['updatemode'] = 'Update mode';
 $string['updatemodedoessettonothing'] = 'Update mode does not allow anything to be updated';
 $string['updateonly'] = 'Only update existing courses';
index c052d1c..7949d94 100644 (file)
@@ -27,6 +27,7 @@ require('../../../config.php');
 require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->libdir.'/csvlib.class.php');
 require_once($CFG->dirroot.'/user/profile/lib.php');
+require_once($CFG->dirroot.'/user/lib.php');
 require_once($CFG->dirroot.'/group/lib.php');
 require_once($CFG->dirroot.'/cohort/lib.php');
 require_once('locallib.php');
@@ -654,9 +655,8 @@ if ($formdata = $mform2->is_cancelled()) {
             }
 
             if ($doupdate or $existinguser->password !== $oldpw) {
-                // we want only users that were really updated
-
-                $DB->update_record('user', $existinguser);
+                // We want only users that were really updated.
+                user_update_user($existinguser, false);
 
                 $upt->track('status', $struserupdated);
                 $usersupdated++;
@@ -668,8 +668,6 @@ if ($formdata = $mform2->is_cancelled()) {
                     profile_save_data($existinguser);
                 }
 
-                events_trigger('user_updated', $existinguser);
-
                 if ($bulk == UU_BULK_UPDATED or $bulk == UU_BULK_ALL) {
                     if (!in_array($user->id, $SESSION->bulk_users)) {
                         $SESSION->bulk_users[] = $user->id;
@@ -789,8 +787,7 @@ if ($formdata = $mform2->is_cancelled()) {
                 $upt->track('password', '-', 'normal', false);
             }
 
-            // create user - insert_record ignores any extra properties
-            $user->id = $DB->insert_record('user', $user);
+            $user->id = user_create_user($user, false);
             $upt->track('username', html_writer::link(new moodle_url('/user/profile.php', array('id'=>$user->id)), s($user->username)), 'normal', false);
 
             // pre-process custom profile menu fields data from csv file
@@ -812,8 +809,6 @@ if ($formdata = $mform2->is_cancelled()) {
             // make sure user context exists
             context_user::instance($user->id);
 
-            events_trigger('user_created', $user);
-
             if ($bulk == UU_BULK_NEW or $bulk == UU_BULK_ALL) {
                 if (!in_array($user->id, $SESSION->bulk_users)) {
                     $SESSION->bulk_users[] = $user->id;
index 4dfc221..040cf3a 100644 (file)
@@ -33,24 +33,38 @@ $string['confirmdeleteindex'] = 'Are you absolutely sure that you want to delete
 $string['confirmdeletekey'] = 'Are you absolutely sure that you want to delete the key:';
 $string['confirmdeletetable'] = 'Are you absolutely sure that you want to delete the table:';
 $string['confirmdeletexmlfile'] = 'Are you absolutely sure that you want to delete the file:';
-$string['confirmcheckbigints'] = 'This functionality will search for <a href="http://tracker.moodle.org/browse/MDL-11038">potential wrong integer fields</a> in your Moodle server, generating (but not executing!) automatically the needed SQL statements to have all the integers in your DB properly defined.<br /><br />
-Once generated you can copy such statements and execute them safely with your favourite SQL interface (don\'t forget to backup your data before doing that).<br /><br />
-It\'s highly recommended to be running the latest (+ version) available of your Moodle release (1.8, 1.9, 2.x ...) before executing the search of wrong integers.<br /><br />
+$string['confirmcheckbigints'] = 'This functionality will search for <a href="http://tracker.moodle.org/browse/MDL-11038">potential wrong integer fields</a> in your Moodle server, generating (but not executing!) automatically the needed SQL statements to have all the integers in your DB properly defined.
+
+Once generated you can copy such statements and execute them safely with your favourite SQL interface (don\'t forget to backup your data before doing that).
+
+It\'s highly recommended to be running the latest (+ version) available of your Moodle release before executing the search of wrong integers.
+
 This functionality doesn\'t perform any action against the DB (just reads from it), so can be safely executed at any moment.';
-$string['confirmcheckdefaults'] = 'This functionality will search for inconsistent default values in your Moodle server, generating (but not executing!) the needed SQL statements to have all the default values properly defined.<br /><br />
-Once generated you can copy such statements and execute them safely with your favourite SQL interface (don\'t forget to backup your data before doing that).<br /><br />
-It\'s highly recommended to be running the latest (+ version) available of your Moodle release (1.8, 1.9, 2.x ...) before executing the search of inconsistent default values.<br /><br />
+$string['confirmcheckdefaults'] = 'This functionality will search for inconsistent default values in your Moodle server, generating (but not executing!) the needed SQL statements to have all the default values properly defined.
+
+Once generated you can copy such statements and execute them safely with your favourite SQL interface (don\'t forget to backup your data before doing that).
+
+It\'s highly recommended to be running the latest (+ version) available of your Moodle release before executing the search of inconsistent default values.
+
 This functionality doesn\'t perform any action against the DB (just reads from it), so can be safely executed at any moment.';
-$string['confirmcheckforeignkeys'] = 'This functionality will search for potential violations of the foreign keys defined in the install.xml definitions. (Moodle does not currently generate actual foreign key constraints in the database, which is why invalid data may be present.)<br /><br />
-It\'s highly recommended to be running the latest (+ version) available of your Moodle release (1.8, 1.9, 2.x ...) before executing the search of missing indexes.<br /><br />
+$string['confirmcheckforeignkeys'] = 'This functionality will search for potential violations of the foreign keys defined in the install.xml definitions. (Moodle does not currently generate actual foreign key constraints in the database, which is why invalid data may be present.)
+
+It\'s highly recommended to be running the latest (+ version) available of your Moodle release before executing the search of missing indexes.
+
 This functionality doesn\'t perform any action against the DB (just reads from it), so can be safely executed at any moment.';
-$string['confirmcheckindexes'] = 'This functionality will search for potential missing indexes in your Moodle server, generating (but not executing!) automatically the needed SQL statements to keep everything updated.<br /><br />
-Once generated you can copy such statements and execute them safely with your favourite SQL interface (don\'t forget to backup your data before doing that).<br /><br />
-It\'s highly recommended to be running the latest (+ version) available of your Moodle release (1.8, 1.9, 2.x ...) before executing the search of missing indexes.<br /><br />
+$string['confirmcheckindexes'] = 'This functionality will search for potential missing indexes in your Moodle server, generating (but not executing!) automatically the needed SQL statements to keep everything updated.
+
+Once generated you can copy such statements and execute them safely with your favourite SQL interface (don\'t forget to backup your data before doing that).
+
+It\'s highly recommended to be running the latest (+ version) available of your Moodle release before executing the search of missing indexes.
+
 This functionality doesn\'t perform any action against the DB (just reads from it), so can be safely executed at any moment.';
-$string['confirmcheckoraclesemantics'] = 'This functionality will search for <a href="http://tracker.moodle.org/browse/MDL-29322">Oracle varchar2 columns using BYTE semantics</a> in your Moodle server, generating (but not executing!) automatically the needed SQL statements to have all the columns converted to use CHAR semantics instead (better for cross-db compatibility and increased contents max. length).<br /><br />
-Once generated you can copy such statements and execute them safely with your favourite SQL interface (don\'t forget to backup your data before doing that).<br /><br />
-It\'s highly recommended to be running the latest (+ version) available of your Moodle release (2.2, 2.3, 2.x ...) before executing the search of BYTE semantics.<br /><br />
+$string['confirmcheckoraclesemantics'] = 'This functionality will search for <a href="http://tracker.moodle.org/browse/MDL-29322">Oracle varchar2 columns using BYTE semantics</a> in your Moodle server, generating (but not executing!) automatically the needed SQL statements to have all the columns converted to use CHAR semantics instead (better for cross-db compatibility and increased contents max. length).
+
+Once generated you can copy such statements and execute them safely with your favourite SQL interface (don\'t forget to backup your data before doing that).
+
+It\'s highly recommended to be running the latest (+ version) available of your Moodle release before executing the search of BYTE semantics.
+
 This functionality doesn\'t perform any action against the DB (just reads from it), so can be safely executed at any moment.';
 $string['confirmrevertchanges'] = 'Are you absolutely sure that you want to revert changes performed over:';
 $string['create'] = 'Create';
index 0f66603..699babb 100644 (file)
@@ -4,6 +4,7 @@
     require_once($CFG->libdir.'/adminlib.php');
     require_once($CFG->libdir.'/authlib.php');
     require_once($CFG->dirroot.'/user/filters/lib.php');
+    require_once($CFG->dirroot.'/user/lib.php');
 
     $delete       = optional_param('delete', 0, PARAM_INT);
     $confirm      = optional_param('confirm', '', PARAM_ALPHANUM);   //md5 confirmation hash
         if ($user = $DB->get_record('user', array('id'=>$suspend, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0))) {
             if (!is_siteadmin($user) and $USER->id != $user->id and $user->suspended != 1) {
                 $user->suspended = 1;
-                $user->timemodified = time();
-                $DB->set_field('user', 'suspended', $user->suspended, array('id'=>$user->id));
-                $DB->set_field('user', 'timemodified', $user->timemodified, array('id'=>$user->id));
-                // force logout
+                // Force logout.
                 session_kill_user($user->id);
-                events_trigger('user_updated', $user);
+                user_update_user($user, false);
             }
         }
         redirect($returnurl);
         if ($user = $DB->get_record('user', array('id'=>$unsuspend, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0))) {
             if ($user->suspended != 0) {
                 $user->suspended = 0;
-                $user->timemodified = time();
-                $DB->set_field('user', 'suspended', $user->suspended, array('id'=>$user->id));
-                $DB->set_field('user', 'timemodified', $user->timemodified, array('id'=>$user->id));
-                events_trigger('user_updated', $user);
+                user_update_user($user, false);
             }
         }
         redirect($returnurl);
index de5061d..a01fc6e 100644 (file)
@@ -284,6 +284,7 @@ class auth_plugin_db extends auth_plugin_base {
             $remove_users = $DB->get_records_sql($sql, $params);
 
             if (!empty($remove_users)) {
+                require_once($CFG->dirroot.'/user/lib.php');
                 $trace->output(get_string('auth_dbuserstoremove','auth_db', count($remove_users)));
 
                 foreach ($remove_users as $user) {
@@ -294,8 +295,7 @@ class auth_plugin_db extends auth_plugin_base {
                         $updateuser = new stdClass();
                         $updateuser->id   = $user->id;
                         $updateuser->suspended = 1;
-                        $updateuser->timemodified = time();
-                        $DB->update_record('user', $updateuser);
+                        user_update_user($updateuser, false);
                         $trace->output(get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
                     }
                 }
index e459004..a670cfc 100644 (file)
@@ -87,17 +87,15 @@ class auth_plugin_email extends auth_plugin_base {
     function user_signup($user, $notify=true) {
         global $CFG, $DB;
         require_once($CFG->dirroot.'/user/profile/lib.php');
+        require_once($CFG->dirroot.'/user/lib.php');
 
         $user->password = hash_internal_user_password($user->password);
 
-        $user->id = $DB->insert_record('user', $user);
+        $user->id = user_create_user($user, false);
 
-        /// Save any custom profile field information
+        // Save any custom profile field information.
         profile_save_data($user);
 
-        $user = $DB->get_record('user', array('id'=>$user->id));
-        events_trigger('user_created', $user);
-
         if (! send_confirmation_email($user)) {
             print_error('auth_emailnoemail','auth_email');
         }
index a1a9645..1bad153 100644 (file)
@@ -78,6 +78,7 @@ if (!defined('LDAP_OPT_DIAGNOSTIC_MESSAGE')) {
 
 require_once($CFG->libdir.'/authlib.php');
 require_once($CFG->libdir.'/ldaplib.php');
+require_once($CFG->dirroot.'/user/lib.php');
 
 /**
  * LDAP authentication plugin.
@@ -550,7 +551,7 @@ class auth_plugin_ldap extends auth_plugin_base {
             print_error('auth_ldap_create_error', 'auth_ldap');
         }
 
-        $user->id = $DB->insert_record('user', $user);
+        $user->id = user_create_user($user, false);
 
         // Save any custom profile field information
         profile_save_data($user);
@@ -562,7 +563,6 @@ class auth_plugin_ldap extends auth_plugin_base {
         update_internal_user_password($user, $plainslashedpassword);
 
         $user = $DB->get_record('user', array('id'=>$user->id));
-        events_trigger('user_created', $user);
 
         if (! send_confirmation_email($user)) {
             print_error('noemail', 'auth_ldap');
@@ -612,12 +612,12 @@ class auth_plugin_ldap extends auth_plugin_base {
                 if (!$this->user_activate($username)) {
                     return AUTH_CONFIRM_FAIL;
                 }
-                $DB->set_field('user', 'confirmed', 1, array('id'=>$user->id));
+                $user->confirmed = 1;
                 if ($user->firstaccess == 0) {
-                    $DB->set_field('user', 'firstaccess', time(), array('id'=>$user->id));
+                    $user->firstaccess = time();
                 }
-                $euser = $DB->get_record('user', array('id' => $user->id));
-                events_trigger('user_updated', $euser);
+                require_once($CFG->dirroot.'/user/lib.php');
+                user_update_user($user, false);
                 return AUTH_CONFIRM_OK;
             }
         } else {
@@ -806,10 +806,8 @@ class auth_plugin_ldap extends auth_plugin_base {
                     $updateuser = new stdClass();
                     $updateuser->id = $user->id;
                     $updateuser->suspended = 1;
-                    $DB->update_record('user', $updateuser);
+                    user_update_user($updateuser, false);
                     echo "\t"; print_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n";
-                    $euser = $DB->get_record('user', array('id' => $user->id));
-                    events_trigger('user_updated', $euser);
                     session_kill_user($user->id);
                 }
             } else {
@@ -835,10 +833,8 @@ class auth_plugin_ldap extends auth_plugin_base {
                     $updateuser->id = $user->id;
                     $updateuser->auth = $this->authtype;
                     $updateuser->suspended = 0;
-                    $DB->update_record('user', $updateuser);
+                    user_update_user($updateuser, false);
                     echo "\t"; print_string('auth_dbreviveduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n";
-                    $euser = $DB->get_record('user', array('id' => $user->id));
-                    events_trigger('user_updated', $euser);
                 }
             } else {
                 print_string('nouserentriestorevive', 'auth_ldap');
@@ -950,10 +946,10 @@ class auth_plugin_ldap extends auth_plugin_base {
                     $user->lang = $CFG->lang;
                 }
 
-                $id = $DB->insert_record('user', $user);
+                $id = user_create_user($user, false);
                 echo "\t"; print_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)); echo "\n";
                 $euser = $DB->get_record('user', array('id' => $id));
-                events_trigger('user_created', $euser);
+
                 if (!empty($this->config->forcechangepassword)) {
                     set_user_preference('auth_forcepasswordchange', 1, $id);
                 }
@@ -1011,22 +1007,25 @@ class auth_plugin_ldap extends auth_plugin_base {
                 $updatekeys = array_keys($newinfo);
             }
 
-            foreach ($updatekeys as $key) {
-                if (isset($newinfo[$key])) {
-                    $value = $newinfo[$key];
-                } else {
-                    $value = '';
-                }
+            if (!empty($updatekeys)) {
+                $newuser = new stdClass();
+                $newuser->id = $userid;
 
-                if (!empty($this->config->{'field_updatelocal_' . $key})) {
-                    if ($user->{$key} != $value) { // only update if it's changed
-                        $DB->set_field('user', $key, $value, array('id'=>$userid));
+                foreach ($updatekeys as $key) {
+                    if (isset($newinfo[$key])) {
+                        $value = $newinfo[$key];
+                    } else {
+                        $value = '';
+                    }
+
+                    if (!empty($this->config->{'field_updatelocal_' . $key})) {
+                        // Only update if it's changed.
+                        if ($user->{$key} != $value) {
+                            $newuser->$key = $value;
+                        }
                     }
                 }
-            }
-            if (!empty($updatekeys)) {
-                $euser = $DB->get_record('user', array('id' => $userid));
-                events_trigger('user_updated', $euser);
+                user_update_user($newuser, false);
             }
         } else {
             return false;
@@ -1646,8 +1645,7 @@ class auth_plugin_ldap extends auth_plugin_base {
             // Now start the whole NTLM machinery.
             if($this->config->ntlmsso_ie_fastpath == AUTH_NTLM_FASTPATH_YESATTEMPT ||
                 $this->config->ntlmsso_ie_fastpath == AUTH_NTLM_FASTPATH_YESFORM) {
-
-                if(check_browser_version('MSIE')) {
+                if (core_useragent::check_ie_version()) {
                     $sesskey = sesskey();
                     redirect($CFG->wwwroot.'/auth/ldap/ntlmsso_magic.php?sesskey='.$sesskey);
                 } else if ($this->config->ntlmsso_ie_fastpath == AUTH_NTLM_FASTPATH_YESFORM) {
index 0d349c5..2ac49a3 100644 (file)
@@ -28,7 +28,7 @@ $file = $CFG->dirroot.'/pix/spacer.gif';
 
 if ($authplugin->ntlmsso_magic($sesskey) && file_exists($file)) {
     if (!empty($authplugin->config->ntlmsso_ie_fastpath)) {
-        if (check_browser_version('MSIE')) {
+        if (core_useragent::check_ie_version()) {
             // $PAGE->https_required() up above takes care of what $CFG->httpswwwroot should be.
             redirect($CFG->httpswwwroot.'/auth/ldap/ntlmsso_finish.php');
         }
index c1861fa..2f626bd 100644 (file)
@@ -216,6 +216,7 @@ class auth_plugin_mnet extends auth_plugin_base {
         global $CFG, $DB;
         require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
         require_once $CFG->libdir . '/gdlib.php';
+        require_once($CFG->dirroot.'/user/lib.php');
 
         // verify the remote host is configured locally before attempting RPC call
         if (! $remotehost = $DB->get_record('mnet_host', array('wwwroot' => $remotepeer->wwwroot, 'deleted' => 0))) {
@@ -361,8 +362,7 @@ class auth_plugin_mnet extends auth_plugin_base {
         if (empty($localuser->firstaccess)) { // Now firstaccess, grab it here
             $localuser->firstaccess = time();
         }
-
-        $DB->update_record('user', $localuser);
+        user_update_user($localuser, false);
 
         if (!$firsttime) {
             // repeat customer! let the IDP know about enrolments
index cfe16ef..cf7d3be 100644 (file)
@@ -89,11 +89,6 @@ if (!($bc = backup_ui::load_controller($backupid))) {
 }
 $backup = new backup_ui($bc);
 $backup->process();
-if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
-    $backup->execute();
-} else {
-    $backup->save_controller();
-}
 
 $PAGE->set_title($heading.': '.$backup->get_stage_name());
 $PAGE->set_heading($heading);
@@ -104,6 +99,19 @@ echo $OUTPUT->header();
 if ($backup->enforce_changed_dependencies()) {
     debugging('Your settings have been altered due to unmet dependencies', DEBUG_DEVELOPER);
 }
+
+if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
+    // Display an extra progress bar so that we can show the progress first.
+    echo html_writer::start_div('', array('id' => 'executionprogress'));
+    echo $renderer->progress_bar($backup->get_progress_bar());
+    $backup->get_controller()->set_progress(new core_backup_display_progress());
+    $backup->execute();
+    echo html_writer::end_div();
+    echo html_writer::script('document.getElementById("executionprogress").style.display = "none";');
+} else {
+    $backup->save_controller();
+}
+
 echo $renderer->progress_bar($backup->get_progress_bar());
 echo $backup->display($renderer);
 $backup->destroy();
index b72c394..b7b87d2 100644 (file)
@@ -64,6 +64,11 @@ class backup_controller extends backup implements loggable {
     protected $destination; // Destination chain object (fs_moodle, fs_os, db, email...)
     protected $logger;      // Logging chain object (moodle, inline, fs, db, syslog)
 
+    /**
+     * @var core_backup_progress Progress reporting object.
+     */
+    protected $progress;
+
     protected $checksum; // Cache @checksumable results for lighter @is_checksum_correct() uses
 
     /**
@@ -109,6 +114,10 @@ class backup_controller extends backup implements loggable {
         // Default logger chain (based on interactive/execution)
         $this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->backupid);
 
+        // By default there is no progress reporter. Interfaces that wish to
+        // display progress must set it.
+        $this->progress = new core_backup_null_progress();
+
         // Instantiate the output_controller singleton and active it if interactive and inmediate
         $oc = output_controller::get_instance();
         if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) {
@@ -302,6 +311,25 @@ class backup_controller extends backup implements loggable {
         return $this->logger;
     }
 
+    /**
+     * Gets the progress reporter, which can be used to report progress within
+     * the backup or restore process.
+     *
+     * @return core_backup_progress Progress reporting object
+     */
+    public function get_progress() {
+        return $this->progress;
+    }
+
+    /**
+     * Sets the progress reporter.
+     *
+     * @param core_backup_progress $progress Progress reporting object
+     */
+    public function set_progress(core_backup_progress $progress) {
+        $this->progress = $progress;
+    }
+
     /**
      * Executes the backup
      * @return void Throws and exception of completes
index ae3d58a..9d98df6 100644 (file)
@@ -57,6 +57,11 @@ class restore_controller extends backup implements loggable {
 
     protected $logger;      // Logging chain object (moodle, inline, fs, db, syslog)
 
+    /**
+     * @var core_backup_progress Progress reporting object.
+     */
+    protected $progress;
+
     protected $checksum; // Cache @checksumable results for lighter @is_checksum_correct() uses
 
     /**
@@ -101,6 +106,10 @@ class restore_controller extends backup implements loggable {
         // Default logger chain (based on interactive/execution)
         $this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->restoreid);
 
+        // By default there is no progress reporter. Interfaces that wish to
+        // display progress must set it.
+        $this->progress = new core_backup_null_progress();
+
         // Instantiate the output_controller singleton and active it if interactive and inmediate
         $oc = output_controller::get_instance();
         if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) {
@@ -300,6 +309,25 @@ class restore_controller extends backup implements loggable {
         return $this->logger;
     }
 
+    /**
+     * Gets the progress reporter, which can be used to report progress within
+     * the backup or restore process.
+     *
+     * @return core_backup_progress Progress reporting object
+     */
+    public function get_progress() {
+        return $this->progress;
+    }
+
+    /**
+     * Sets the progress reporter.
+     *
+     * @param core_backup_progress $progress Progress reporting object
+     */
+    public function set_progress(core_backup_progress $progress) {
+        $this->progress = $progress;
+    }
+
     public function execute_plan() {
         // Basic/initial prevention against time/memory limits
         set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted
index fbd919a..13ad15a 100644 (file)
@@ -90,11 +90,25 @@ if ($backup->get_stage() == backup_ui::STAGE_CONFIRMATION) {
 
 // If it's the final stage process the import
 if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
+    echo $OUTPUT->header();
+
+    // Display an extra progress bar so that we can show the current stage.
+    echo html_writer::start_div('', array('id' => 'executionprogress'));
+    echo $renderer->progress_bar($backup->get_progress_bar());
+
+    // Start the progress display - we split into 2 chunks for backup and restore.
+    $progress = new core_backup_display_progress();
+    $progress->start_progress('', 2);
+    $backup->get_controller()->set_progress($progress);
+
     // First execute the backup
     $backup->execute();
     $backup->destroy();
     unset($backup);
 
+    // Note that we've done that progress.
+    $progress->progress(1);
+
     // Check whether the backup directory still exists. If missing, something
     // went really wrong in backup, throw error. Note that backup::MODE_IMPORT
     // backups don't store resulting files ever
@@ -106,6 +120,7 @@ if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
     // Prepare the restore controller. We don't need a UI here as we will just use what
     // ever the restore has (the user has just chosen).
     $rc = new restore_controller($backupid, $course->id, backup::INTERACTIVE_YES, backup::MODE_IMPORT, $USER->id, $restoretarget);
+    $rc->set_progress($progress);
     // Convert the backup if required.... it should NEVER happed
     if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) {
         $rc->convert();
@@ -140,8 +155,12 @@ if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
     // Delete the temp directory now
     fulldelete($tempdestination);
 
+    // All progress complete. Hide progress area.
+    $progress->end_progress();
+    echo html_writer::end_div();
+    echo html_writer::script('document.getElementById("executionprogress").style.display = "none";');
+
     // Display a notification and a continue button
-    echo $OUTPUT->header();
     if ($warnings) {
         echo $OUTPUT->box_start();
         echo $OUTPUT->notification(get_string('warning'), 'notifywarning');
index ad5ac4a..2cd3754 100644 (file)
@@ -157,6 +157,11 @@ class backup_final_task extends backup_task {
         $this->built = true;
     }
 
+    public function get_weight() {
+        // The final task takes ages, so give it 20 times the weight of a normal task.
+        return 20;
+    }
+
 // Protected API starts here
 
     /**
index 2b6cdab..444f9cf 100644 (file)
@@ -1421,6 +1421,12 @@ class restore_course_structure_step extends restore_structure_step {
             $data->theme = '';
         }
 
+        // Check if this is an old SCORM course format.
+        if ($data->format == 'scorm') {
+            $data->format = 'singleactivity';
+            $data->activitytype = 'scorm';
+        }
+
         // Course record ready, update it
         $DB->update_record('course', $data);
 
@@ -3624,7 +3630,7 @@ class restore_process_file_aliases_queue extends restore_execution_step {
 
         // Iterate over aliases in the queue.
         foreach ($rs as $record) {
-            $info = restore_dbops::decode_backup_temp_info($record->info);
+            $info = backup_controller_dbops::decode_backup_temp_info($record->info);
 
             // Try to pick a repository instance that should serve the alias.
             $repository = $this->choose_repository($info);
index cd51cae..a31501c 100644 (file)
@@ -44,10 +44,28 @@ if ($stage & restore_ui::STAGE_CONFIRM + restore_ui::STAGE_DESTINATION) {
 }
 
 $outcome = $restore->process();
+$heading = $course->fullname;
+
+$PAGE->set_title($heading.': '.$restore->get_stage_name());
+$PAGE->set_heading($heading);
+$PAGE->navbar->add($restore->get_stage_name());
+
+$renderer = $PAGE->get_renderer('core','backup');
+echo $OUTPUT->header();
+if (!$restore->is_independent() && $restore->enforce_changed_dependencies()) {
+    debugging('Your settings have been altered due to unmet dependencies', DEBUG_DEVELOPER);
+}
+
 if (!$restore->is_independent()) {
     if ($restore->get_stage() == restore_ui::STAGE_PROCESS && !$restore->requires_substage()) {
         try {
+            // Display an extra progress bar so that we can show the progress first.
+            echo html_writer::start_div('', array('id' => 'executionprogress'));
+            echo $renderer->progress_bar($restore->get_progress_bar());
+            $restore->get_controller()->set_progress(new core_backup_display_progress());
             $restore->execute();
+            echo html_writer::end_div();
+            echo html_writer::script('document.getElementById("executionprogress").style.display = "none";');
         } catch(Exception $e) {
             $restore->cleanup();
             throw $e;
@@ -56,17 +74,7 @@ if (!$restore->is_independent()) {
         $restore->save_controller();
     }
 }
-$heading = $course->fullname;
-
-$PAGE->set_title($heading.': '.$restore->get_stage_name());
-$PAGE->set_heading($heading);
-$PAGE->navbar->add($restore->get_stage_name());
 
-$renderer = $PAGE->get_renderer('core','backup');
-echo $OUTPUT->header();
-if (!$restore->is_independent() && $restore->enforce_changed_dependencies()) {
-    debugging('Your settings have been altered due to unmet dependencies', DEBUG_DEVELOPER);
-}
 echo $renderer->progress_bar($restore->get_progress_bar());
 echo $restore->display($renderer);
 $restore->destroy();
index 668ff61..409be32 100644 (file)
  */
 abstract class backup_controller_dbops extends backup_dbops {
 
+    /**
+     * @var string Backup id for cached backup_includes_files result.
+     */
+    protected static $includesfilescachebackupid;
+
+    /**
+     * @var int Cached backup_includes_files result
+     */
+    protected static $includesfilescache;
+
     /**
      * Send one backup controller to DB
      *
@@ -441,9 +451,20 @@ abstract class backup_controller_dbops extends backup_dbops {
      * @return int Indicates whether files should be included in backups.
      */
     public static function backup_includes_files($backupid) {
-        // Load controller
+        // This function is called repeatedly in a backup with many files.
+        // Loading the controller is a nontrivial operation (in a large test
+        // backup it took 0.3 seconds), so we do a temporary cache of it within
+        // this request.
+        if (self::$includesfilescachebackupid === $backupid) {
+            return self::$includesfilescache;
+        }
+
+        // Load controller, get value, then destroy controller and return result.
+        self::$includesfilescachebackupid = $backupid;
         $bc = self::load_controller($backupid);
-        return $bc->get_include_files();
+        self::$includesfilescache = $bc->get_include_files();
+        $bc->destroy();
+        return self::$includesfilescache;
     }
 
     /**
index 12a3259..2a4f06b 100644 (file)
@@ -71,6 +71,9 @@ require_once($CFG->dirroot . '/backup/util/loggers/error_log_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_null_progress.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress.class.php');
 require_once($CFG->dirroot . '/backup/util/settings/setting_dependency.class.php');
 require_once($CFG->dirroot . '/backup/util/settings/base_setting.class.php');
 require_once($CFG->dirroot . '/backup/util/settings/backup_setting.class.php');
index ae01911..78cd24b 100644 (file)
@@ -60,6 +60,9 @@ require_once($CFG->dirroot . '/backup/util/loggers/error_log_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_null_progress.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress.class.php');
 require_once($CFG->dirroot . '/backup/util/factories/backup_factory.class.php');
 require_once($CFG->dirroot . '/backup/util/factories/restore_factory.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/backup_helper.class.php');
index dcfddec..5277416 100644 (file)
@@ -59,7 +59,7 @@ class database_logger extends base_logger {
         if ($this->levelcol) {
             $columns[$this->levelcol] = $level;
         }
-        $columns[$this->messagecol] = $message; //TODO: should this be cleaned?
+        $columns[$this->messagecol] = clean_param($message, PARAM_NOTAGS);
         return $this->insert_log_record($this->logtable, $columns);
     }
 
@@ -69,6 +69,6 @@ class database_logger extends base_logger {
         // to preserve DB logs if the whole backup/restore transaction is
         // rollback
         global $DB;
-        return $DB->insert_record($this->logtable, $columns, false); // Don't return inserted id
+        return $DB->insert_record($table, $columns, false); // Don't return inserted id
     }
 }
index 4378221..3d537d2 100644 (file)
@@ -87,6 +87,16 @@ class backup_plan extends base_plan implements loggable {
         return $this->controller->get_logger();
     }
 
+    /**
+     * Gets the progress reporter, which can be used to report progress within
+     * the backup or restore process.
+     *
+     * @return core_backup_progress Progress reporting object
+     */
+    public function get_progress() {
+        return $this->controller->get_progress();
+    }
+
     public function is_excluding_activities() {
         return $this->excludingdactivities;
     }
index d8c4b4d..7479094 100644 (file)
@@ -158,12 +158,33 @@ abstract class base_plan implements checksumable, executable {
         if (!$this->built) {
             throw new base_plan_exception('base_plan_not_built');
         }
+
+        // Calculate the total weight of all tasks and start progress tracking.
+        $progress = $this->get_progress();
+        $totalweight = 0;
+        foreach ($this->tasks as $task) {
+            $totalweight += $task->get_weight();
+        }
+        $progress->start_progress($this->get_name(), $totalweight);
+
+        // Build and execute all tasks.
         foreach ($this->tasks as $task) {
             $task->build();
             $task->execute();
         }
+
+        // Finish progress tracking.
+        $progress->end_progress();
     }
 
+    /**
+     * Gets the progress reporter, which can be used to report progress within
+     * the backup or restore process.
+     *
+     * @return core_backup_progress Progress reporting object
+     */
+    public abstract function get_progress();
+
     /**
      * Destroy all circular references. It helps PHP 5.2 a lot!
      */
index 440521b..e167b89 100644 (file)
@@ -67,6 +67,17 @@ abstract class base_task implements checksumable, executable, loggable {
         return $this->settings;
     }
 
+    /**
+     * Returns the weight of this task, an approximation of the amount of time
+     * it will take. By default this value is 1. It can be increased for longer
+     * tasks.
+     *
+     * @return int Weight
+     */
+    public function get_weight() {
+        return 1;
+    }
+
     public function get_setting($name) {
         // First look in task settings
         $result = null;
@@ -111,6 +122,16 @@ abstract class base_task implements checksumable, executable, loggable {
         return $this->plan->get_logger();
     }
 
+    /**
+     * Gets the progress reporter, which can be used to report progress within
+     * the backup or restore process.
+     *
+     * @return core_backup_progress Progress reporting object
+     */
+    public function get_progress() {
+        return $this->plan->get_progress();
+    }
+
     public function log($message, $level, $a = null, $depth = null, $display = false) {
         backup_helper::log($message, $level, $a, $depth, $display, $this->get_logger());
     }
@@ -149,6 +170,13 @@ abstract class base_task implements checksumable, executable, loggable {
         if ($this->executed) {
             throw new base_task_exception('base_task_already_executed', $this->name);
         }
+
+        // Starts progress based on the weight of this task and number of steps.
+        $progress = $this->get_progress();
+        $progress->start_progress($this->get_name(), count($this->steps), $this->get_weight());
+        $done = 0;
+
+        // Execute all steps.
         foreach ($this->steps as $step) {
             $result = $step->execute();
             // If step returns array, it will be forwarded to plan
@@ -156,11 +184,16 @@ abstract class base_task implements checksumable, executable, loggable {
             if (is_array($result) and !empty($result)) {
                 $this->add_result($result);
             }
+            $done++;
+            $progress->progress($done);
         }
         // Mark as executed if any step has been executed
         if (!empty($this->steps)) {
             $this->executed = true;
         }
+
+        // Finish progress for this task.
+        $progress->end_progress();
     }
 
     /**
index c9dd8fb..e173e0e 100644 (file)
@@ -94,6 +94,16 @@ class restore_plan extends base_plan implements loggable {
         return $this->controller->get_logger();
     }
 
+    /**
+     * Gets the progress reporter, which can be used to report progress within
+     * the backup or restore process.
+     *
+     * @return core_backup_progress Progress reporting object
+     */
+    public function get_progress() {
+        return $this->controller->get_progress();
+    }
+
     public function get_info() {
         return $this->controller->get_info();
     }
index daf1499..1283e7e 100644 (file)
@@ -34,6 +34,10 @@ require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
 class mock_base_plan extends base_plan {
     public function build() {
     }
+
+    public function get_progress() {
+        return null;
+    }
 }
 
 /**
diff --git a/backup/util/progress/core_backup_display_progress.class.php b/backup/util/progress/core_backup_display_progress.class.php
new file mode 100644 (file)
index 0000000..62696cb
--- /dev/null
@@ -0,0 +1,136 @@
+<?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/>.
+
+/**
+ * Progress handler that uses a standard Moodle progress bar to display
+ * progress. The Moodle progress bar cannot show indeterminate progress,
+ * so we do extra output in addition to the bar.
+ *
+ * @package core_backup
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_backup_display_progress extends core_backup_progress {
+    /**
+     * @var int Number of wibble states (state0...stateN-1 classes in CSS)
+     */
+    const WIBBLE_STATES = 13;
+
+    /**
+     * @var progress_bar Current progress bar.
+     */
+    private $bar;
+
+    private $lastwibble, $currentstate = 0, $direction = 1;
+
+    /**
+     * @var bool True to display names
+     */
+    protected $displaynames = false;
+
+    /**
+     * Constructs the progress reporter. This will output HTML code for the
+     * progress bar, and an indeterminate wibbler below it.
+     *
+     * @param bool $startnow If true, outputs HTML immediately.
+     */
+    public function __construct($startnow = true) {
+        if ($startnow) {
+            $this->start_html();
+        }
+    }
+
+    /**
+     * By default, the progress section names do not display because (in backup)
+     * these are usually untranslated and incomprehensible. To make them
+     * display, call this method.
+     *
+     * @param bool $displaynames True to display names
+     */
+    public function set_display_names($displaynames = true) {
+        $this->displaynames = $displaynames;
+    }
+
+    /**
+     * Starts to output progress.
+     *
+     * Called in constructor and in update_progress if required.
+     *
+     * @throws coding_exception If already started
+     */
+    public function start_html() {
+        if ($this->bar) {
+            throw new coding_exception('Already started');
+        }
+        $this->bar = new progress_bar();
+        $this->bar->create();
+        echo html_writer::start_div('wibbler');
+    }
+
+    /**
+     * Finishes output. (Progress can begin again later if there are more
+     * calls to update_progress.)
+     *
+     * Automatically called from update_progress when progress finishes.
+     */
+    public function end_html() {
+        // Finish progress bar.
+        $this->bar->update_full(100, '');
+        $this->bar = null;
+
+        // End wibbler div.
+        echo html_writer::end_div();
+    }
+
+    public function update_progress() {
+        // If finished...
+        if (!$this->is_in_progress_section()) {
+            if ($this->bar) {
+                $this->end_html();
+            }
+        } else {
+            if (!$this->bar) {
+                $this->start_html();
+            }
+            // In case of indeterminate or small progress, update the wibbler
+            // (up to once per second).
+            if (time() != $this->lastwibble) {
+                $this->lastwibble = time();
+                echo html_writer::div('', 'wibble state' . $this->currentstate);
+
+                // Go on to next colour.
+                $this->currentstate += $this->direction;
+                if ($this->currentstate < 0 || $this->currentstate >= self::WIBBLE_STATES) {
+                    $this->direction = -$this->direction;
+                    $this->currentstate += 2 * $this->direction;
+                }
+            }
+
+            // Get progress.
+            list ($min, $max) = $this->get_progress_proportion_range();
+
+            // Update progress bar.
+            $message = '';
+            if ($this->displaynames) {
+                $message = $this->get_current_description();
+            }
+            $this->bar->update_full($min * 100, $message);
+
+            // Flush output.
+            flush();
+        }
+    }
+}
diff --git a/backup/util/progress/core_backup_null_progress.class.php b/backup/util/progress/core_backup_null_progress.class.php
new file mode 100644 (file)
index 0000000..3e02369
--- /dev/null
@@ -0,0 +1,28 @@
+<?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/>.
+
+/**
+ * Progress handler that ignores progress entirely.
+ *
+ * @package core_backup
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_backup_null_progress extends core_backup_progress {
+    public function update_progress() {
+        // Do nothing.
+    }
+}
diff --git a/backup/util/progress/core_backup_progress.class.php b/backup/util/progress/core_backup_progress.class.php
new file mode 100644 (file)
index 0000000..2c092c9
--- /dev/null
@@ -0,0 +1,307 @@
+<?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/>.
+
+/**
+ * Base class for handling progress information during a backup and restore.
+ *
+ * Subclasses should generally override the current_progress function which
+ * summarises all progress information.
+ *
+ * @package core_backup
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class core_backup_progress {
+    /**
+     * @var int Constant indicating that the number of progress calls is unknown.
+     */
+    const INDETERMINATE = -1;
+
+    /**
+     * @var int The number of seconds that can pass without progress() calls.
+     */
+    const TIME_LIMIT_WITHOUT_PROGRESS = 120;
+
+    /**
+     * @var int Time of last progress call.
+     */
+    protected $lastprogresstime;
+
+    /**
+     * @var int Number of progress calls (restricted to ~ 1/second).
+     */
+    protected $count;
+
+    /**
+     * @var array Array of progress descriptions for each stack level.
+     */
+    protected $descriptions = array();
+
+    /**
+     * @var array Array of maximum progress values for each stack level.
+     */
+    protected $maxes = array();
+
+    /**
+     * @var array Array of current progress values.
+     */
+    protected $currents = array();
+
+    /**
+     * @var int Array of counts within parent progress entry (ignored for first)
+     */
+    protected $parentcounts = array();
+
+    /**
+     * Marks the start of an operation that will display progress.
+     *
+     * This can be called multiple times for nested progress sections. It must
+     * be paired with calls to end_progress.
+     *
+     * The progress maximum may be INDETERMINATE if the current operation has
+     * an unknown number of steps. (This is default.)
+     *
+     * Calling this function will always result in a new display, so this
+     * should not be called exceedingly frequently.
+     *
+     * When it is complete by calling end_progress, each start_progress section
+     * automatically adds progress to its parent, as defined by $parentcount.
+     *
+     * @param string $description Description to display
+     * @param int $max Maximum value of progress for this section
+     * @param int $parentcount How many progress points this section counts for
+     * @throws coding_exception If max is invalid
+     */
+    public function start_progress($description, $max = self::INDETERMINATE,
+            $parentcount = 1) {
+        if ($max != self::INDETERMINATE && $max <= 0) {
+            throw new coding_exception(
+                    'start_progress() max value cannot be zero or negative');
+        }
+        if ($parentcount < 1) {
+            throw new coding_exception(
+                    'start_progress() parent progress count must be at least 1');
+        }
+        if (!empty($this->descriptions)) {
+            $prevmax = end($this->maxes);
+            if ($prevmax !== self::INDETERMINATE) {
+                $prevcurrent = end($this->currents);
+                if ($prevcurrent + $parentcount > $prevmax) {
+                    throw new coding_exception(
+                            'start_progress() parent progress would exceed max');
+                }
+            }
+        } else {
+            if ($parentcount != 1) {
+                throw new coding_exception(
+                        'start_progress() progress count must be 1 when no parent');
+            }
+        }
+        $this->descriptions[] = $description;
+        $this->maxes[] = $max;
+        $this->currents[] = 0;
+        $this->parentcounts[] = $parentcount;
+        $this->update_progress();
+        $lastprogresstime = $this->get_time();
+    }
+
+    /**
+     * Marks the end of an operation that will display progress.
+     *
+     * This must be paired with each start_progress call.
+     *
+     * If there is a parent progress section, its progress will be increased
+     * automatically to reflect the end of the child section.
+     *
+     * @throws coding_exception If progress hasn't been started
+     */
+    public function end_progress() {
+        if (!count($this->descriptions)) {
+            throw new coding_exception('end_progress() without start_progress()');
+        }
+        array_pop($this->descriptions);
+        array_pop($this->maxes);
+        array_pop($this->currents);
+        $parentcount = array_pop($this->parentcounts);
+        if (!empty($this->descriptions)) {
+            $lastmax = end($this->maxes);
+            if ($lastmax != self::INDETERMINATE) {
+                $lastvalue = end($this->currents);
+                $this->currents[key($this->currents)] = $lastvalue + $parentcount;
+            }
+        }
+        $this->update_progress();
+    }
+
+    /**
+     * Indicates that progress has occurred.
+     *
+     * The progress value should indicate the total progress so far, from 0
+     * to the value supplied for $max (inclusive) in start_progress.
+     *
+     * You do not need to call this function for every value. It is OK to skip
+     * values. It is also OK to call this function as often as desired; it
+     * doesn't do anything if called more than once per second.
+     *
+     * It must be INDETERMINATE if start_progress was called with $max set to
+     * INDETERMINATE. Otherwise it must not be indeterminate.
+     *
+     * @param int $progress Progress so far
+     * @throws coding_exception If progress value is invalid
+     */
+    public function progress($progress = self::INDETERMINATE) {
+        // Ignore too-frequent progress calls (more than once per second).
+        $now = $this->get_time();
+        if ($now === $this->lastprogresstime) {
+            return;
+        }
+
+        // Check we are inside a progress section.
+        $max = end($this->maxes);
+        if ($max === false) {
+            throw new coding_exception(
+                    'progress() without start_progress');
+        }
+
+        // Check and apply new progress.
+        if ($progress === self::INDETERMINATE) {
+            // Indeterminate progress.
+            if ($max !== self::INDETERMINATE) {
+                throw new coding_exception(
+                        'progress() INDETERMINATE, expecting value');
+            }
+        } else {
+            // Determinate progress.
+            $current = end($this->currents);
+            if ($max === self::INDETERMINATE) {
+                throw new coding_exception(
+                        'progress() with value, expecting INDETERMINATE');
+            } else if ($progress < 0 || $progress > $max) {
+                throw new coding_exception(
+                        'progress() value out of range');
+            } else if ($progress < $current) {
+                throw new coding_Exception(
+                        'progress() value may not go backwards');
+            }
+            $this->currents[key($this->currents)] = $progress;
+        }
+
+        // Update progress.
+        $this->count++;
+        $this->lastprogresstime = $now;
+        set_time_limit(self::TIME_LIMIT_WITHOUT_PROGRESS);
+        $this->update_progress();
+    }
+
+    /**
+     * Gets time (this is provided so that unit tests can override it).
+     *
+     * @return int Current system time
+     */
+    protected function get_time() {
+        return time();
+    }
+
+    /**
+     * Called whenever new progress should be displayed.
+     */
+    protected abstract function update_progress();
+
+    /**
+     * @return bool True if currently inside a progress section
+     */
+    public function is_in_progress_section() {
+        return !empty($this->descriptions);
+    }
+
+    /**
+     * @return string Current progress section description
+     */
+    public function get_current_description() {
+        $description = end($this->descriptions);
+        if ($description === false) {
+            throw new coding_exception('Not inside progress section');
+        }
+        return $description;
+    }
+
+    /**
+     * Obtains current progress in a way suitable for drawing a progress bar.
+     *
+     * Progress is returned as a minimum and maximum value. If there is no
+     * indeterminate progress, these values will be identical. If there is
+     * intermediate progress, these values can be different. (For example, if
+     * the top level progress sections is indeterminate, then the values will
+     * always be 0.0 and 1.0.)
+     *
+     * @return array Minimum and maximum possible progress proportions
+     */
+    public function get_progress_proportion_range() {
+        // If there is no progress underway, we must have finished.
+        if (empty($this->currents)) {
+            return array(1.0, 1.0);
+        }
+        $count = count($this->currents);
+        $min = 0.0;
+        $max = 1.0;
+        for ($i = 0; $i < $count; $i++) {
+            // Get max value at that section - if it's indeterminate we can tell
+            // no more.
+            $sectionmax = $this->maxes[$i];
+            if ($sectionmax === self::INDETERMINATE) {
+                return array($min, $max);
+            }
+
+            // Special case if current value is max (this should only happen
+            // just before ending a section).
+            $sectioncurrent = $this->currents[$i];
+            if ($sectioncurrent === $sectionmax) {
+                return array($max, $max);
+            }
+
+            // Using the current value at that section, we know we are somewhere
+            // between 'current' and the next 'current' value which depends on
+            // the parentcount of the nested section (if any).
+            $newmin = ($sectioncurrent / $sectionmax) * ($max - $min) + $min;
+            $nextcurrent = $sectioncurrent + 1;
+            if ($i + 1 < $count) {
+                $weight = $this->parentcounts[$i + 1];
+                $nextcurrent = $sectioncurrent + $weight;
+            }
+            $newmax = ($nextcurrent / $sectionmax) * ($max - $min) + $min;
+            $min = $newmin;
+            $max = $newmax;
+        }
+
+        // If there was nothing indeterminate, we use the min value as current.
+        return array($min, $min);
+    }
+
+    /**
+     * Obtains current indeterminate progress in a way suitable for adding to
+     * the progress display.
+     *
+     * This returns the number of indeterminate calls (at any level) during the
+     * lifetime of this progress reporter, whether or not there is a current
+     * indeterminate step. (The number will not be ridiculously high because
+     * progress calls are limited to one per second.)
+     *
+     * @return int Number of indeterminate progress calls
+     */
+    public function get_progress_count() {
+        return $this->count;
+    }
+}
diff --git a/backup/util/progress/tests/progress_test.php b/backup/util/progress/tests/progress_test.php
new file mode 100644 (file)
index 0000000..3c673f7
--- /dev/null
@@ -0,0 +1,363 @@
+<?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/>.
+
+/**
+ * Unit tests for the progress classes.
+ *
+ * @package core_backup
+ * @category phpunit
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+// Include all the needed stuff.
+global $CFG;
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
+
+/**
+ * Progress tests.
+ */
+class backup_progress_testcase extends basic_testcase {
+
+    /**
+     * Tests for basic use with simple numeric progress.
+     */
+    public function test_basic() {
+        $progress = new core_backup_mock_progress();
+
+        // Check values of empty progress things.
+        $this->assertFalse($progress->is_in_progress_section());
+
+        // Start progress counting, check basic values and check that update
+        // gets called.
+        $progress->start_progress('hello', 10);
+        $this->assertTrue($progress->was_update_called());
+        $this->assertTrue($progress->is_in_progress_section());
+        $this->assertEquals('hello', $progress->get_current_description());
+
+        // Check numeric position and indeterminate count.
+        $this->assert_min_max(0.0, 0.0, $progress);
+        $this->assertEquals(0, $progress->get_progress_count());
+
+        // Make some progress and check that the time limit gets added.
+        $progress->step_time();
+        $progress->progress(2);
+        $this->assertTrue($progress->was_update_called());
+        $this->assertEquals(120, ini_get('max_execution_time'));
+
+        // Check the new value.
+        $this->assert_min_max(0.2, 0.2, $progress);
+
+        // Do another progress run at same time, it should be ignored.
+        $progress->progress(3);
+        $this->assertFalse($progress->was_update_called());
+        $this->assert_min_max(0.2, 0.2, $progress);
+
+        // End the section. This should cause an update.
+        $progress->end_progress();
+        $this->assertTrue($progress->was_update_called());
+
+        // Because there are no sections left open, it thinks we finished.
+        $this->assert_min_max(1.0, 1.0, $progress);
+
+        // There was 1 progress call.
+        $this->assertEquals(1, $progress->get_progress_count());
+
+        // Clear the time limit, otherwise phpunit complains.
+        set_time_limit(0);
+    }
+
+    /**
+     * Tests progress that is nested and/or indeterminate.
+     */
+    public function test_nested() {
+        // Outer progress goes from 0 to 10.
+        $progress = new core_backup_mock_progress();
+        $progress->start_progress('hello', 10);
+
+        // Get up to 4, check position.
+        $progress->step_time();
+        $progress->progress(4);
+        $this->assert_min_max(0.4, 0.4, $progress);
+        $this->assertEquals('hello', $progress->get_current_description());
+
+        // Now start indeterminate progress.
+        $progress->start_progress('world');
+        $this->assert_min_max(0.4, 0.5, $progress);
+        $this->assertEquals('world', $progress->get_current_description());
+
+        // Do some indeterminate progress and count it (once per second).
+        $progress->step_time();
+        $progress->progress();
+        $this->assertEquals(2, $progress->get_progress_count());
+        $progress->progress();
+        $this->assertEquals(2, $progress->get_progress_count());
+        $progress->step_time();
+        $progress->progress();
+        $this->assertEquals(3, $progress->get_progress_count());
+        $this->assert_min_max(0.4, 0.5, $progress);
+
+        // Exit the indeterminate section.
+        $progress->end_progress();
+        $this->assert_min_max(0.5, 0.5, $progress);
+
+        $progress->step_time();
+        $progress->progress(7);
+        $this->assert_min_max(0.7, 0.7, $progress);
+
+        // Enter a numbered section (this time with a range of 5).
+        $progress->start_progress('frogs', 5);
+        $this->assert_min_max(0.7, 0.7, $progress);
+        $progress->step_time();
+        $progress->progress(1);
+        $this->assert_min_max(0.72, 0.72, $progress);
+        $progress->step_time();
+        $progress->progress(3);
+        $this->assert_min_max(0.76, 0.76, $progress);
+
+        // Now enter another indeterminate section.
+        $progress->start_progress('and');
+        $this->assert_min_max(0.76, 0.78, $progress);
+
+        // Make some progress, should increment indeterminate count.
+        $progress->step_time();
+        $progress->progress();
+        $this->assertEquals(7, $progress->get_progress_count());
+
+        // Enter numbered section, won't make any difference to values.
+        $progress->start_progress('zombies', 2);
+        $progress->step_time();
+        $progress->progress(1);
+        $this->assert_min_max(0.76, 0.78, $progress);
+        $this->assertEquals(8, $progress->get_progress_count());
+
+        // Leaving it will make no difference too.
+        $progress->end_progress();
+
+        // Leaving the indeterminate section will though.
+        $progress->end_progress();
+        $this->assert_min_max(0.78, 0.78, $progress);
+
+        // Leave the two numbered sections.
+        $progress->end_progress();
+        $this->assert_min_max(0.8, 0.8, $progress);
+        $progress->end_progress();
+        $this->assertFalse($progress->is_in_progress_section());
+
+        set_time_limit(0);
+    }
+
+    /**
+     * Tests the feature for 'weighting' nested progress.
+     */
+    public function test_nested_weighted() {
+        $progress = new core_backup_mock_progress();
+        $progress->start_progress('', 10);
+
+        // First nested child has 2 units of its own and is worth 1 unit.
+        $progress->start_progress('', 2);
+        $progress->step_time();
+        $progress->progress(1);
+        $this->assert_min_max(0.05, 0.05, $progress);
+        $progress->end_progress();
+        $this->assert_min_max(0.1, 0.1, $progress);
+
+        // Next child has 2 units of its own but is worth 3 units.
+        $progress->start_progress('weighted', 2, 3);
+        $progress->step_time();
+        $progress->progress(1);
+        $this->assert_min_max(0.25, 0.25, $progress);
+        $progress->end_progress();
+        $this->assert_min_max(0.4, 0.4, $progress);
+
+        // Next indeterminate child is worth 6 units.
+        $progress->start_progress('', core_backup_progress::INDETERMINATE, 6);
+        $progress->step_time();
+        $progress->progress();
+        $this->assert_min_max(0.4, 1.0, $progress);
+        $progress->end_progress();
+        $this->assert_min_max(1.0, 1.0, $progress);
+
+        set_time_limit(0);
+    }
+
+    /**
+     * I had some issues with real use in backup/restore, this test is intended
+     * to be similar.
+     */
+    public function test_realistic() {
+        $progress = new core_backup_mock_progress();
+        $progress->start_progress('parent', 100);
+        $progress->start_progress('child', 1);
+        $progress->progress(1);
+        $this->assert_min_max(0.01, 0.01, $progress);
+        $progress->end_progress();
+        $this->assert_min_max(0.01, 0.01, $progress);
+
+        // Clear the time limit, otherwise phpunit complains.
+        set_time_limit(0);
+    }
+
+    /**
+     * Tests for any exceptions due to invalid calls.
+     */
+    public function test_exceptions() {
+        $progress = new core_backup_mock_progress();
+
+        // Check errors when empty.
+        try {
+            $progress->progress();
+            $this->fail();
+        } catch (coding_exception $e) {
+            $this->assertEquals(1, preg_match('~without start_progress~', $e->getMessage()));
+        }
+        try {
+            $progress->end_progress();
+            $this->fail();
+        } catch (coding_exception $e) {
+            $this->assertEquals(1, preg_match('~without start_progress~', $e->getMessage()));
+        }
+        try {
+            $progress->get_current_description();
+            $this->fail();
+        } catch (coding_exception $e) {
+            $this->assertEquals(1, preg_match('~Not inside progress~', $e->getMessage()));
+        }
+        try {
+            $progress->start_progress('', 1, 7);
+            $this->fail();
+        } catch (coding_exception $e) {
+            $this->assertEquals(1, preg_match('~must be 1~', $e->getMessage()));
+        }
+
+        // Check invalid start (0).
+        try {
+            $progress->start_progress('hello', 0);
+            $this->fail();
+        } catch (coding_exception $e) {
+            $this->assertEquals(1, preg_match('~cannot be zero or negative~', $e->getMessage()));
+        }
+
+        // Indeterminate when value expected.
+        $progress->start_progress('hello', 10);
+        try {
+            $progress->progress(core_backup_progress::INDETERMINATE);
+            $this->fail();
+        } catch (coding_exception $e) {
+            $this->assertEquals(1, preg_match('~expecting value~', $e->getMessage()));
+        }
+
+        // Value when indeterminate expected.
+        $progress->start_progress('hello');
+        try {
+            $progress->progress(4);
+            $this->fail();
+        } catch (coding_exception $e) {
+            $this->assertEquals(1, preg_match('~expecting INDETERMINATE~', $e->getMessage()));
+        }
+
+        // Illegal values.
+        $progress->start_progress('hello', 10);
+        try {
+            $progress->progress(-2);
+            $this->fail();
+        } catch (coding_exception $e) {
+            $this->assertEquals(1, preg_match('~out of range~', $e->getMessage()));
+        }
+        try {
+            $progress->progress(11);
+            $this->fail();
+        } catch (coding_exception $e) {
+            $this->assertEquals(1, preg_match('~out of range~', $e->getMessage()));
+        }
+
+        // You are allowed two with the same value...
+        $progress->progress(4);
+        $progress->step_time();
+        $progress->progress(4);
+        $progress->step_time();
+
+        // ...but not to go backwards.
+        try {
+            $progress->progress(3);
+            $this->fail();
+        } catch (coding_exception $e) {
+            $this->assertEquals(1, preg_match('~backwards~', $e->getMessage()));
+        }
+
+        // When you go forward, you can't go further than there is room.
+        try {
+            $progress->start_progress('', 1, 7);
+            $this->fail();
+        } catch (coding_exception $e) {
+            $this->assertEquals(1, preg_match('~would exceed max~', $e->getMessage()));
+        }
+
+        // Clear the time limit, otherwise phpunit complains.
+        set_time_limit(0);
+    }
+
+    /**
+     * Checks the current progress values are as expected.
+     *
+     * @param number $min Expected min progress
+     * @param number $max Expected max progress
+     * @param core_backup_mock_progress $progress
+     */
+    private function assert_min_max($min, $max, core_backup_mock_progress $progress) {
+        $this->assertEquals(array($min, $max),
+                $progress->get_progress_proportion_range());
+    }
+}
+
+/**
+ * Helper class that records when update_progress is called and allows time
+ * stepping.
+ */
+class core_backup_mock_progress extends core_backup_progress {
+    private $updatecalled = false;
+    private $time = 1;
+
+    /**
+     * Checks if update was called since the last call to this function.
+     *
+     * @return boolean True if update was called
+     */
+    public function was_update_called() {
+        if ($this->updatecalled) {
+            $this->updatecalled = false;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Steps the current time by 1 second.
+     */
+    public function step_time() {
+        $this->time++;
+    }
+
+    protected function update_progress() {
+        $this->updatecalled = true;
+    }
+
+    protected function get_time() {
+        return $this->time;
+    }
+}
index 7d70be9..773a156 100644 (file)
@@ -69,7 +69,7 @@ class backup_base_optigroup_testcase extends basic_testcase {
     /**
      * Incorrect creation tests (attributes and final elements)
      */
-    function itest_wrong_creation() {
+    function test_wrong_creation() {
 
         // Create instance with invalid name
         try {
index b9a2277..f89837c 100644 (file)
@@ -140,7 +140,7 @@ class core_backup_renderer extends plugin_renderer_base {
                     }
                     if (empty($table)) {
                         $table = new html_table();
-                        $table->head = array('Module', 'Title', 'Userinfo');
+                        $table->head = array(get_string('module','backup'), get_string('title','backup'), get_string('userinfo','backup'));
                         $table->colclasses = array('modulename', 'moduletitle', 'userinfoincluded');
                         $table->align = array('left','left', 'center');
                         $table->attributes = array('class'=>'activitytable generaltable');
index 6f65533..f892b8c 100644 (file)
@@ -174,22 +174,20 @@ abstract class restore_search_base implements renderable {
         $this->totalcount = 0;
         $contextlevel = $this->get_itemcontextlevel();
         list($sql, $params) = $this->get_searchsql();
-        $blocksz = 5000;
-        $offs = 0;
-        // Get total number, to avoid some incorrect iterations
+        // Get total number, to avoid some incorrect iterations.
         $countsql = preg_replace('/ORDER BY.*/', '', $sql);
         $totalcourses = $DB->count_records_sql("SELECT COUNT(*) FROM ($countsql) sel", $params);
-        // User to be checked is always the same (usually null, get it form first element)
-        $firstcap = reset($this->requiredcapabilities);
-        $userid = isset($firstcap['user']) ? $firstcap['user'] : null;
-        // Extract caps to check, this saves us a bunch of iterations
-        $requiredcaps = array();
-        foreach ($this->requiredcapabilities as $cap) {
-            $requiredcaps[] = $cap['capability'];
-        }
-        // Iterate while we have records and haven't reached $this->maxresults.
-        while ($totalcourses > $offs and $this->totalcount < $this->maxresults) {
-            $resultset = $DB->get_records_sql($sql, $params, $offs, $blocksz);
+        if ($totalcourses > 0) {
+            // User to be checked is always the same (usually null, get it from first element).
+            $firstcap = reset($this->requiredcapabilities);
+            $userid = isset($firstcap['user']) ? $firstcap['user'] : null;
+            // Extract caps to check, this saves us a bunch of iterations.
+            $requiredcaps = array();
+            foreach ($this->requiredcapabilities as $cap) {
+                $requiredcaps[] = $cap['capability'];
+            }
+            // Iterate while we have records and haven't reached $this->maxresults.
+            $resultset = $DB->get_recordset_sql($sql, $params);
             foreach ($resultset as $result) {
                 context_helper::preload_from_record($result);
                 $classname = context_helper::get_class_for_level($contextlevel);
@@ -208,7 +206,7 @@ abstract class restore_search_base implements renderable {
                 $this->totalcount++;
                 $this->results[$result->id] = $result;
             }
-            $offs += $blocksz;
+            $resultset->close();
         }
 
         return $this->totalcount;
index f8b85d7..6732194 100644 (file)
@@ -13,8 +13,9 @@ Feature: Backup Moodle courses
   @javascript
   Scenario: Backup a course providing options
     When I backup "Course 1" course using this options:
+      | Filename | test_backup.mbz |
     Then I should see "Restore"
-    And I click on "Restore" "link" in the ".backup-files-table" "css_element"
+    And I click on "Restore" "link" in the "test_backup.mbz" "table_row"
     And I should see "URL of backup"
     And I should see "Anonymize user information"
 
@@ -27,11 +28,11 @@ Feature: Backup Moodle courses
       | setting_section_section_5_userinfo | 0 |
       | setting_section_section_5_included | 0 |
     Then I should see "Restore"
-    And I click on "Restore" "link" in the ".backup-files-table" "css_element"
+    And I click on "Restore" "link" in the "test_backup.mbz" "table_row"
     And I should not see "Section 3"
     And I press "Continue"
     And I click on "Continue" "button" in the ".bcs-current-course" "css_element"
     And "//div[contains(concat(' ', normalize-space(@class), ' '), ' fitem ')][contains(., 'Include calendar events')]/descendant::img" "xpath_element" should exists
     And I check "Include course logs"
     And I press "Cancel"
-    And I click on "Cancel" "button" in the ".confirmation-dialogue" "css_element"
+    And I click on "Cancel" "button" in the "Cancel backup" "dialogue"
index f415f07..f046af1 100644 (file)
@@ -100,13 +100,6 @@ Feature: Restore Moodle 2 course backups
     And I follow "Edit settings"
     And I expand all fieldsets
     And the "id_format" field should match "Social format" value
-    And I fill the moodle form with:
-      | id_format | SCORM format |
-    And I press "Save changes"
-    And I should see "Adding a new SCORM package"
-    And I follow "Edit settings"
-    And I expand all fieldsets
-    And the "id_format" field should match "SCORM format" value
     And I press "Cancel"
 
   @javascript
index 6d7b054..39c2c4c 100644 (file)
@@ -72,7 +72,7 @@ class core_badges_observer {
     /**
      * Triggered when 'course_completed' event is triggered.
      *
-     * @param   \core\event\course_completed $event
+     * @param \core\event\course_completed $event
      */
     public static function course_criteria_review(\core\event\course_completed $event) {
         global $DB, $CFG;
@@ -105,4 +105,36 @@ class core_badges_observer {
             }
         }
     }
+
+    /**
+     * Triggered when 'user_updated' event happens.
+     *
+     * @param \core\event\user_updated $event event generated when user profile is updated.
+     */
+    public static function profile_criteria_review(\core\event\user_updated $event) {
+        global $DB, $CFG;
+
+        if (!empty($CFG->enablebadges)) {
+            require_once($CFG->dirroot.'/lib/badgeslib.php');
+            $userid = $event->objectid;
+
+            if ($rs = $DB->get_records('badge_criteria', array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE))) {
+                foreach ($rs as $r) {
+                    $badge = new badge($r->badgeid);
+                    if (!$badge->is_active() || $badge->is_issued($userid)) {
+                        continue;
+                    }
+
+                    if ($badge->criteria[BADGE_CRITERIA_TYPE_PROFILE]->review($userid)) {
+                        $badge->criteria[BADGE_CRITERIA_TYPE_PROFILE]->mark_complete($userid);
+
+                        if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
+                            $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
+                            $badge->issue($userid);
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
index 5436f01..e9c4ab3 100644 (file)
 require_once(dirname(dirname(__FILE__)) . '/config.php');
 require_once($CFG->libdir . '/badgeslib.php');
 
-$json = required_param('badge', PARAM_RAW);
+$json = optional_param('badge', null, PARAM_RAW);
+// Redirect to homepage if users are trying to access external badge through old url.
+if ($json) {
+    redirect($CFG->wwwroot, get_string('invalidrequest', 'error'), 3);
+}
+
+$hash = required_param('hash', PARAM_ALPHANUM);
+$userid = required_param('user', PARAM_INT);
+
+$PAGE->set_url(new moodle_url('/badges/external.php', array('hash' => $hash, 'user' => $userid)));
+
+// Using the same setting as user profile page.
+if (!empty($CFG->forceloginforprofiles)) {
+    require_login();
+    if (isguestuser()) {
+        $SESSION->wantsurl = $PAGE->url->out(false);
+        redirect(get_login_url());
+    }
+} else if (!empty($CFG->forcelogin)) {
+    require_login();
+}
+
+// Get all external badges of a user.
+$out = get_backpack_settings($userid);
+$badges = $out->badges;
+
+// Loop through the badges and check if supplied badge hash exists in user external badges.
+foreach ($badges as $b) {
+    if ($hash == hash("md5", $b->hostedUrl)) {
+        $badge = $b;
+        break;
+    }
+}
+
+// If we didn't find the badge, a user might be trying to replace userid parameter.
+if (is_null($badge)) {
+    print_error(get_string('error:externalbadgedoesntexist', 'badges'));
+}
 
 $PAGE->set_context(context_system::instance());
 $output = $PAGE->get_renderer('core', 'badges');
 
-$badge = new external_badge(unserialize($json));
+$badge = new external_badge($badge, $userid);
 
-$PAGE->set_url('/badges/external.php');
 $PAGE->set_pagelayout('base');
 $PAGE->set_title(get_string('issuedbadge', 'badges'));
 
index 1f52453..875d025 100644 (file)
@@ -53,11 +53,13 @@ $PAGE->set_heading($title);
 $PAGE->set_pagelayout('mydashboard');
 
 $backpack = $DB->get_record('badge_backpack', array('userid' => $USER->id));
+$badgescache = cache::make('core', 'externalbadges');
 
 if ($disconnect && $backpack) {
     require_sesskey();
     $DB->delete_records('badge_external', array('backpackid' => $backpack->id));
     $DB->delete_records('badge_backpack', array('userid' => $USER->id));
+    $badgescache->delete($USER->id);
     redirect(new moodle_url('/badges/mybackpack.php'));
 }
 
@@ -103,6 +105,7 @@ if ($backpack) {
                 $DB->insert_record('badge_external', $obj);
             }
         }
+        $badgescache->delete($USER->id);
         redirect(new moodle_url('/badges/mybadges.php'));
     }
 } else {
index 088b443..3075dc1 100644 (file)
@@ -81,9 +81,10 @@ class core_badges_renderer extends plugin_renderer_base {
                 $url = new moodle_url('badge.php', array('hash' => $badge->uniquehash));
             } else {
                 if (!$external) {
-                    $url = new moodle_url($CFG->wwwroot . '/badges/badge.php', array('hash' => $badge->uniquehash));
+                    $url = new moodle_url('/badges/badge.php', array('hash' => $badge->uniquehash));
                 } else {
-                    $url = new moodle_url($CFG->wwwroot . '/badges/external.php', array('badge' => serialize($badge)));
+                    $hash = hash('md5', $badge->hostedUrl);
+                    $url = new moodle_url('/badges/external.php', array('hash' => $hash, 'user' => $userid));
                 }
             }
             $actions = html_writer::tag('div', $push . $download . $status, array('class' => 'badge-actions'));
@@ -276,6 +277,7 @@ class core_badges_renderer extends plugin_renderer_base {
     protected function render_issued_badge(issued_badge $ibadge) {
         global $USER, $CFG, $DB;
         $issued = $ibadge->issued;
+        $userinfo = $ibadge->recipient;
         $badge = new badge($ibadge->badgeid);
         $today_date = date('Y-m-d');
         $today = strtotime($today_date);
@@ -286,7 +288,7 @@ class core_badges_renderer extends plugin_renderer_base {
         $imagetable = new html_table();
         $imagetable->attributes = array('class' => 'clearfix badgeissuedimage');
         $imagetable->data[] = array(html_writer::empty_tag('img', array('src' => $issued['badge']['image'])));
-        if ($USER->id == $ibadge->recipient && !empty($CFG->enablebadges)) {
+        if ($USER->id == $userinfo->id && !empty($CFG->enablebadges)) {
             $imagetable->data[] = array($this->output->single_button(
                         new moodle_url('/badges/badge.php', array('hash' => $ibadge->hash, 'bake' => true)),
                         get_string('download'),
@@ -307,11 +309,20 @@ class core_badges_renderer extends plugin_renderer_base {
         $datatable = new html_table();
         $datatable->attributes = array('class' => 'badgeissuedinfo');
         $datatable->colclasses = array('bfield', 'bvalue');
+
+        // Recipient information.
+        $datatable->data[] = array($this->output->heading(get_string('recipientdetails', 'badges'), 3), '');
+        $datatable->data[] = array(get_string('name'), fullname($userinfo));
+        if (empty($userinfo->backpackemail)) {
+            $datatable->data[] = array(get_string('email'), obfuscate_mailto($userinfo->accountemail));
+        } else {
+            $datatable->data[] = array(get_string('email'), obfuscate_mailto($userinfo->backpackemail));
+        }
+
         $datatable->data[] = array($this->output->heading(get_string('issuerdetails', 'badges'), 3), '');
         $datatable->data[] = array(get_string('issuername', 'badges'), $badge->issuername);
         if (isset($badge->issuercontact) && !empty($badge->issuercontact)) {
-            $datatable->data[] = array(get_string('contact', 'badges'),
-                html_writer::tag('a', $badge->issuercontact, array('href' => 'mailto:' . $badge->issuercontact)));
+            $datatable->data[] = array(get_string('contact', 'badges'), obfuscate_mailto($badge->issuercontact));
         }
         $datatable->data[] = array($this->output->heading(get_string('badgedetails', 'badges'), 3), '');
         $datatable->data[] = array(get_string('name'), $badge->name);
@@ -347,7 +358,7 @@ class core_badges_renderer extends plugin_renderer_base {
 
         // Print evidence.
         $agg = $badge->get_aggregation_methods();
-        $evidence = $badge->get_criteria_completions($ibadge->recipient);
+        $evidence = $badge->get_criteria_completions($userinfo->id);
         $eids = array_map(create_function('$o', 'return $o->critid;'), $evidence);
         unset($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]);
 
@@ -378,6 +389,7 @@ class core_badges_renderer extends plugin_renderer_base {
         $issued = $ibadge->issued;
         $assertion = $issued->assertion;
         $issuer = $assertion->badge->issuer;
+        $userinfo = $ibadge->recipient;
         $table = new html_table();
 
         $imagetable = new html_table();
@@ -387,13 +399,29 @@ class core_badges_renderer extends plugin_renderer_base {
         $datatable = new html_table();
         $datatable->attributes = array('class' => 'badgeissuedinfo');
         $datatable->colclasses = array('bfield', 'bvalue');
+
+        // Recipient information.
+        $datatable->data[] = array($this->output->heading(get_string('recipientdetails', 'badges'), 3), '');
+        // Technically, we should alway have a user at this point, but added an extra check just in case.
+        if ($userinfo) {
+            $datatable->data[] = array(get_string('name'), fullname($userinfo));
+            if (!$ibadge->valid) {
+                $notify = $this->output->notification(get_string('recipientvalidationproblem', 'badges'), 'notifynotice');
+                $datatable->data[] = array(get_string('email'), obfuscate_mailto($userinfo->email) . $notify);
+            } else {
+                $datatable->data[] = array(get_string('email'), obfuscate_mailto($userinfo->email));
+            }
+        } else {
+            $notify = $this->output->notification(get_string('recipientidentificationproblem', 'badges'), 'notifynotice');
+            $datatable->data[] = array(get_string('name'), $notify);
+        }
+
         $datatable->data[] = array($this->output->heading(get_string('issuerdetails', 'badges'), 3), '');
         $datatable->data[] = array(get_string('issuername', 'badges'), $issuer->name);
         $datatable->data[] = array(get_string('issuerurl', 'badges'),
                 html_writer::tag('a', $issuer->origin, array('href' => $issuer->origin)));
         if (isset($issuer->contact)) {
-            $datatable->data[] = array(get_string('contact', 'badges'),
-                html_writer::tag('a', $issuer->contact, array('href' => 'mailto:' . $issuer->contact)));
+            $datatable->data[] = array(get_string('contact', 'badges'), obfuscate_mailto($issuer->contact));
         }
         $datatable->data[] = array($this->output->heading(get_string('badgedetails', 'badges'), 3), '');
         $datatable->data[] = array(get_string('name'), $assertion->badge->name);
@@ -875,7 +903,7 @@ class issued_badge implements renderable {
     public $issued;
 
     /** @var badge recipient */
-    public $recipient = 0;
+    public $recipient;
 
     /** @var badge visibility to others */
     public $visible = 0;
@@ -901,7 +929,12 @@ class issued_badge implements renderable {
                 WHERE ' . $DB->sql_compare_text('uniquehash', 40) . ' = ' . $DB->sql_compare_text(':hash', 40),
                 array('hash' => $hash), IGNORE_MISSING);
         if ($rec) {
-            $this->recipient = $rec->userid;
+            // Get a recipient from database.
+            $user = $DB->get_record_sql('SELECT u.id, u.lastname, u.firstname,
+                                                u.email AS accountemail, b.email AS backpackemail
+                        FROM {user} u LEFT JOIN {badge_backpack} b ON u.id = b.userid
+                        WHERE u.id = :userid', array('userid' => $rec->userid));
+            $this->recipient = $user;
             $this->visible = $rec->visible;
             $this->badgeid = $rec->badgeid;
         }
@@ -915,13 +948,51 @@ class external_badge implements renderable {
     /** @var issued badge */
     public $issued;
 
+    /** @var User ID */
+    public $recipient;
+
+    /** @var validation of external badge */
+    public $valid = true;
+
     /**
      * Initializes the badge to display
      *
-     * @param string $json External badge information.
+     * @param object $badge External badge information.
+     * @param int $recipient User id.
      */
-    public function __construct($json) {
-        $this->issued = $json;
+    public function __construct($badge, $recipient) {
+        global $DB;
+        // At this point a user has connected a backpack. So, we are going to get
+        // their backpack email rather than their account email.
+        $user = $DB->get_record_sql('SELECT u.lastname, u.firstname, b.email
+                    FROM {user} u INNER JOIN {badge_backpack} b ON u.id = b.userid
+                    WHERE userid = :userid', array('userid' => $recipient), IGNORE_MISSING);
+
+        $this->issued = $badge;
+        $this->recipient = $user;
+
+        // Check if recipient is valid.
+        // There is no way to be 100% sure that a badge belongs to a user.
+        // Backpack does not return any recipient information.
+        // All we can do is compare that backpack email hashed using salt
+        // provided in the assertion matches a badge recipient from the assertion.
+        if ($user) {
+            if (validate_email($badge->assertion->recipient) && $badge->assertion->recipient == $user->email) {
+                // If we have email, compare emails.
+                $this->valid = true;
+            } else if ($badge->assertion->recipient == 'sha256$' . hash('sha256', $user->email)) {
+                // If recipient is hashed, but no salt, compare hashes without salt.
+                $this->valid = true;
+            } else if ($badge->assertion->recipient == 'sha256$' . hash('sha256', $user->email . $badge->assertion->salt)) {
+                // If recipient is hashed, compare hashes.
+                $this->valid = true;
+            } else {
+                // Otherwise, we cannot be sure that this user is a recipient.
+                $this->valid = false;
+            }
+        } else {
+            $this->valid = false;
+        }
     }
 }
 
@@ -1016,7 +1087,7 @@ class badge_user_collection extends badge_collection implements renderable {
         parent::__construct($badges);
 
         if (!empty($CFG->badges_allowexternalbackpack)) {
-            $this->backpack = get_backpack_settings($userid);
+            $this->backpack = get_backpack_settings($userid, true);
         }
     }
 }
index fa7b7cb..a3b2878 100644 (file)
@@ -260,4 +260,23 @@ class core_badgeslib_testcase extends advanced_testcase {
         $this->assertDebuggingCalled('Error baking badge image!');
         $this->assertTrue($badge->is_issued($this->user->id));
     }
+
+    /**
+     * Test badges observer when user_updated event is fired.
+     */
+    public function test_badges_observer_profile_criteria_review() {
+        $badge = new badge($this->coursebadge);
+        $this->assertFalse($badge->is_issued($this->user->id));
+
+        $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
+        $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
+        $criteria_overall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE, 'badgeid' => $badge->id));
+        $criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address'));
+
+        $this->user->address = 'Test address';
+        user_update_user($this->user, false);
+        // Check if badge is awarded.
+        $this->assertDebuggingCalled('Error baking badge image!');
+        $this->assertTrue($badge->is_issued($this->user->id));
+    }
 }
index a9e8dc9..6d8589a 100644 (file)
@@ -26,9 +26,9 @@
 $string['adminview'] = 'Admin view';
 $string['allcourses'] = 'Admin user sees all courses';
 $string['configadminview'] = 'What should the admin see in the course list block?';
-$string['confighideallcourseslink'] = 'Hide "All courses" link at the bottom of the block. Link hiding does not affects Admin\'s view';
+$string['confighideallcourseslink'] = 'Remove the \'All courses\' link under the list of courses. (This setting does not affect the admin view.)';
 $string['course_list:addinstance'] = 'Add a new courses block';
 $string['course_list:myaddinstance'] = 'Add a new courses block to My home';
-$string['hideallcourseslink'] = 'Hide All courses link';
+$string['hideallcourseslink'] = 'Hide \'All courses\' link';
 $string['owncourses'] = 'Admin user sees own courses';
 $string['pluginname'] = 'Courses';
index 567250b..6287bad 100644 (file)
@@ -38,14 +38,14 @@ Feature: View my courses in navigation block
     And I log out
     And I log in as "student1"
     When I follow "My home"
-    Then I should not see "cat1" in the "div.block_navigation .type_system" "css_element"
-    And I should not see "cat2" in the "div.block_navigation .type_system" "css_element"
-    And I should see "c1" in the "div.block_navigation .type_system" "css_element"
-    And I should see "c31" in the "div.block_navigation .type_system" "css_element"
-    And I should see "c331" in the "div.block_navigation .type_system" "css_element"
-    And I should not see "c2" in the "div.block_navigation .type_system" "css_element"
-    And I should not see "c32" in the "div.block_navigation .type_system" "css_element"
-    And I should not see "c332" in the "div.block_navigation .type_system" "css_element"
+    Then I should not see "cat1" in the "Navigation" "block"
+    And I should not see "cat2" in the "Navigation" "block"
+    And I should see "c1" in the "Navigation" "block"
+    And I should see "c31" in the "Navigation" "block"
+    And I should see "c331" in the "Navigation" "block"
+    And I should not see "c2" in the "Navigation" "block"
+    And I should not see "c32" in the "Navigation" "block"
+    And I should not see "c332" in the "Navigation" "block"
 
   @javascript
   Scenario: The nested list of enrolled courses is shown
@@ -54,18 +54,18 @@ Feature: View my courses in navigation block
     And I log out
     And I log in as "student1"
     When I follow "My home"
-    Then I should see "cat1" in the "div.block_navigation .type_system" "css_element"
-    And I should see "cat3" in the "div.block_navigation .type_system" "css_element"
-    And I should not see "cat2" in the "div.block_navigation .type_system" "css_element"
+    Then I should see "cat1" in the "Navigation" "block"
+    And I should see "cat3" in the "Navigation" "block"
+    And I should not see "cat2" in the "Navigation" "block"
     And I expand "cat3" node
     And I wait "2" seconds
-    And I should see "cat31" in the "div.block_navigation .type_system" "css_element"
-    And I should see "cat33" in the "div.block_navigation .type_system" "css_element"
-    And I should not see "cat32" in the "div.block_navigation .type_system" "css_element"
+    And I should see "cat31" in the "Navigation" "block"
+    And I should see "cat33" in the "Navigation" "block"
+    And I should not see "cat32" in the "Navigation" "block"
     And I expand "cat31" node
     And I wait "2" seconds
-    And I should see "c31" in the "div.block_navigation .type_system" "css_element"
+    And I should see "c31" in the "Navigation" "block"
     And I expand "cat33" node
     And I wait "2" seconds
-    And I should see "c331" in the "div.block_navigation .type_system" "css_element"
-    And I should not see "c332" in the "div.block_navigation .type_system" "css_element"
+    And I should see "c331" in the "Navigation" "block"
+    And I should not see "c332" in the "Navigation" "block"
index d743f70..97da6fb 100644 (file)
@@ -595,8 +595,9 @@ class blog_listing {
             $userid = $USER->id;
         }
 
+        $allnamefields = get_all_user_name_fields(true, 'u');
         // The query used to locate blog entries is complicated.  It will be built from the following components:
-        $requiredfields = "p.*, u.firstname, u.lastname, u.email";  // the SELECT clause
+        $requiredfields = "p.*, $allnamefields, u.email";  // the SELECT clause
         $tables = array('p' => 'post', 'u' => 'user');   // components of the FROM clause (table_id => table_name)
         $conditions = array('u.deleted = 0', 'p.userid = u.id', '(p.module = \'blog\' OR p.module = \'blog_external\')');  // components of the WHERE clause (conjunction)
 
index da8fe67..6de94e2 100644 (file)
@@ -271,7 +271,7 @@ class cache implements cache_loader {
      *      In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
      * @param int $strictness One of IGNORE_MISSING | MUST_EXIST
      * @return mixed|false The data from the cache or false if the key did not exist within the cache.
-     * @throws moodle_exception
+     * @throws coding_exception
      */
     public function get($key, $strictness = IGNORE_MISSING) {
         // 1. Parse the key.
@@ -329,7 +329,7 @@ class cache implements cache_loader {
         }
         // 5. Validate strictness.
         if ($strictness === MUST_EXIST && $result === false) {
-            throw new moodle_exception('Requested key did not exist in any cache stores and could not be loaded.');
+            throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.');
         }
         // 6. Set it to the store if we got it from the loader/datasource.