Merge branch 'MDL-53043' of https://github.com/andrewhancox/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 19 Apr 2016 22:53:07 +0000 (00:53 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 19 Apr 2016 22:53:07 +0000 (00:53 +0200)
642 files changed:
admin/settings/competency.php [new file with mode: 0644]
admin/settings/server.php
admin/settings/top.php
admin/tool/cohortroles/classes/api.php [new file with mode: 0644]
admin/tool/cohortroles/classes/cohort_role_assignment.php [new file with mode: 0644]
admin/tool/cohortroles/classes/form/assign_role_cohort.php [new file with mode: 0644]
admin/tool/cohortroles/classes/output/cohort_role_assignments_table.php [new file with mode: 0644]
admin/tool/cohortroles/classes/output/renderer.php [new file with mode: 0644]
admin/tool/cohortroles/classes/task/cohort_role_sync.php [new file with mode: 0644]
admin/tool/cohortroles/db/install.xml [new file with mode: 0644]
admin/tool/cohortroles/db/tasks.php [new file with mode: 0644]
admin/tool/cohortroles/index.php [new file with mode: 0644]
admin/tool/cohortroles/lang/en/tool_cohortroles.php [new file with mode: 0644]
admin/tool/cohortroles/settings.php [new file with mode: 0644]
admin/tool/cohortroles/templates/cohort-in-list.mustache [new file with mode: 0644]
admin/tool/cohortroles/tests/api_test.php [new file with mode: 0644]
admin/tool/cohortroles/version.php [new file with mode: 0644]
admin/tool/log/store/standard/db/install.xml
admin/tool/log/store/standard/db/upgrade.php
admin/tool/log/store/standard/version.php
admin/tool/lp/amd/build/actionselector.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/competencies.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/competency_outcomes.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/competency_rule.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/competency_rule_all.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/competency_rule_points.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/competencyactions.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/competencydialogue.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/competencypicker.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/competencypicker_user_plans.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/competencyruleconfig.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/competencytree.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/course_competency_settings.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/dialogue.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/dragdrop-reorder.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/event_base.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/evidence_delete.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/form-cohort-selector.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/form-user-selector.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/frameworkactions.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/frameworks_datasource.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/grade_dialogue.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/grade_user_competency_inline.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/menubar.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/parentcompetency_form.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/planactions.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/scaleconfig.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/scalevalues.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/templateactions.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/tree.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/user_competency_course_navigation.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/user_competency_info.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/user_competency_workflow.min.js [new file with mode: 0644]
admin/tool/lp/amd/build/user_evidence_actions.min.js [new file with mode: 0644]
admin/tool/lp/amd/src/actionselector.js [new file with mode: 0644]
admin/tool/lp/amd/src/competencies.js [new file with mode: 0644]
admin/tool/lp/amd/src/competency_outcomes.js [new file with mode: 0644]
admin/tool/lp/amd/src/competency_rule.js [new file with mode: 0644]
admin/tool/lp/amd/src/competency_rule_all.js [new file with mode: 0644]
admin/tool/lp/amd/src/competency_rule_points.js [new file with mode: 0644]
admin/tool/lp/amd/src/competencyactions.js [new file with mode: 0644]
admin/tool/lp/amd/src/competencydialogue.js [new file with mode: 0644]
admin/tool/lp/amd/src/competencypicker.js [new file with mode: 0644]
admin/tool/lp/amd/src/competencypicker_user_plans.js [new file with mode: 0644]
admin/tool/lp/amd/src/competencyruleconfig.js [new file with mode: 0644]
admin/tool/lp/amd/src/competencytree.js [new file with mode: 0644]
admin/tool/lp/amd/src/course_competency_settings.js [new file with mode: 0644]
admin/tool/lp/amd/src/dialogue.js [new file with mode: 0644]
admin/tool/lp/amd/src/dragdrop-reorder.js [new file with mode: 0644]
admin/tool/lp/amd/src/event_base.js [new file with mode: 0644]
admin/tool/lp/amd/src/evidence_delete.js [new file with mode: 0644]
admin/tool/lp/amd/src/form-cohort-selector.js [new file with mode: 0644]
admin/tool/lp/amd/src/form-user-selector.js [new file with mode: 0644]
admin/tool/lp/amd/src/frameworkactions.js [new file with mode: 0644]
admin/tool/lp/amd/src/frameworks_datasource.js [new file with mode: 0644]
admin/tool/lp/amd/src/grade_dialogue.js [new file with mode: 0644]
admin/tool/lp/amd/src/grade_user_competency_inline.js [new file with mode: 0644]
admin/tool/lp/amd/src/menubar.js [new file with mode: 0644]
admin/tool/lp/amd/src/parentcompetency_form.js [new file with mode: 0644]
admin/tool/lp/amd/src/planactions.js [new file with mode: 0644]
admin/tool/lp/amd/src/scaleconfig.js [new file with mode: 0644]
admin/tool/lp/amd/src/scalevalues.js [new file with mode: 0644]
admin/tool/lp/amd/src/templateactions.js [new file with mode: 0644]
admin/tool/lp/amd/src/tree.js [new file with mode: 0644]
admin/tool/lp/amd/src/user_competency_course_navigation.js [new file with mode: 0644]
admin/tool/lp/amd/src/user_competency_info.js [new file with mode: 0644]
admin/tool/lp/amd/src/user_competency_workflow.js [new file with mode: 0644]
admin/tool/lp/amd/src/user_evidence_actions.js [new file with mode: 0644]
admin/tool/lp/classes/course_competencies_form_element.php [new file with mode: 0644]
admin/tool/lp/classes/course_competency_rule_form_element.php [new file with mode: 0644]
admin/tool/lp/classes/course_competency_statistics.php [new file with mode: 0644]
admin/tool/lp/classes/external.php [new file with mode: 0644]
admin/tool/lp/classes/external/cohort_summary_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/external/competency_path_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/external/competency_summary_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/external/course_competency_statistics_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/external/course_module_summary_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/external/course_summary_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/external/path_node_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/external/template_statistics_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/external/user_competency_summary_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/external/user_competency_summary_in_course_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/external/user_competency_summary_in_plan_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/external/user_evidence_competency_summary_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/external/user_evidence_summary_exporter.php [new file with mode: 0644]
admin/tool/lp/classes/form/competency.php [new file with mode: 0644]
admin/tool/lp/classes/form/competency_framework.php [new file with mode: 0644]
admin/tool/lp/classes/form/framework_autocomplete.php [new file with mode: 0644]
admin/tool/lp/classes/form/persistent.php [new file with mode: 0644]
admin/tool/lp/classes/form/plan.php [new file with mode: 0644]
admin/tool/lp/classes/form/template.php [new file with mode: 0644]
admin/tool/lp/classes/form/template_cohorts.php [new file with mode: 0644]
admin/tool/lp/classes/form/template_plans.php [new file with mode: 0644]
admin/tool/lp/classes/form/user_evidence.php [new file with mode: 0644]
admin/tool/lp/classes/output/competency_summary.php [new file with mode: 0644]
admin/tool/lp/classes/output/course_competencies_page.php [new file with mode: 0644]
admin/tool/lp/classes/output/manage_competencies_page.php [new file with mode: 0644]
admin/tool/lp/classes/output/manage_competency_frameworks_page.php [new file with mode: 0644]
admin/tool/lp/classes/output/manage_templates_page.php [new file with mode: 0644]
admin/tool/lp/classes/output/plan_page.php [new file with mode: 0644]
admin/tool/lp/classes/output/plans_page.php [new file with mode: 0644]
admin/tool/lp/classes/output/related_competencies.php [new file with mode: 0644]
admin/tool/lp/classes/output/renderer.php [new file with mode: 0644]
admin/tool/lp/classes/output/template_cohorts_page.php [new file with mode: 0644]
admin/tool/lp/classes/output/template_cohorts_table.php [new file with mode: 0644]
admin/tool/lp/classes/output/template_competencies_page.php [new file with mode: 0644]
admin/tool/lp/classes/output/template_plans_page.php [new file with mode: 0644]
admin/tool/lp/classes/output/template_plans_table.php [new file with mode: 0644]
admin/tool/lp/classes/output/user_competency_course_navigation.php [new file with mode: 0644]
admin/tool/lp/classes/output/user_competency_summary.php [new file with mode: 0644]
admin/tool/lp/classes/output/user_competency_summary_in_course.php [new file with mode: 0644]
admin/tool/lp/classes/output/user_competency_summary_in_plan.php [new file with mode: 0644]
admin/tool/lp/classes/output/user_evidence_list_page.php [new file with mode: 0644]
admin/tool/lp/classes/output/user_evidence_page.php [new file with mode: 0644]
admin/tool/lp/classes/page_helper.php [new file with mode: 0644]
admin/tool/lp/classes/template_statistics.php [new file with mode: 0644]
admin/tool/lp/classes/url_resolver.php [new file with mode: 0644]
admin/tool/lp/competencies.php [new file with mode: 0644]
admin/tool/lp/competencyframeworks.php [new file with mode: 0644]
admin/tool/lp/coursecompetencies.php [new file with mode: 0644]
admin/tool/lp/db/services.php [new file with mode: 0644]
admin/tool/lp/editcompetency.php [new file with mode: 0644]
admin/tool/lp/editcompetencyframework.php [new file with mode: 0644]
admin/tool/lp/editplan.php [new file with mode: 0644]
admin/tool/lp/edittemplate.php [new file with mode: 0644]
admin/tool/lp/lang/en/tool_lp.php [new file with mode: 0644]
admin/tool/lp/learningplans.php [new file with mode: 0644]
admin/tool/lp/lib.php [new file with mode: 0644]
admin/tool/lp/pix/competency.png [new file with mode: 0644]
admin/tool/lp/pix/competency.svg [new file with mode: 0644]
admin/tool/lp/pix/url.png [new file with mode: 0644]
admin/tool/lp/pix/url.svg [new file with mode: 0644]
admin/tool/lp/plan.php [new file with mode: 0644]
admin/tool/lp/plans.php [new file with mode: 0644]
admin/tool/lp/settings.php [new file with mode: 0644]
admin/tool/lp/styles.css [new file with mode: 0644]
admin/tool/lp/template_cohorts.php [new file with mode: 0644]
admin/tool/lp/template_plans.php [new file with mode: 0644]
admin/tool/lp/templatecompetencies.php [new file with mode: 0644]
admin/tool/lp/templates/action_selector.mustache [new file with mode: 0644]
admin/tool/lp/templates/comment_area.mustache [new file with mode: 0644]
admin/tool/lp/templates/competencies_move_tree.mustache [new file with mode: 0644]
admin/tool/lp/templates/competencies_tree.mustache [new file with mode: 0644]
admin/tool/lp/templates/competencies_tree_root.mustache [new file with mode: 0644]
admin/tool/lp/templates/competency_grader.mustache [new file with mode: 0644]
admin/tool/lp/templates/competency_path.mustache [new file with mode: 0644]
admin/tool/lp/templates/competency_picker.mustache [new file with mode: 0644]
admin/tool/lp/templates/competency_picker_competencyform.mustache [new file with mode: 0644]
admin/tool/lp/templates/competency_picker_user_plans.mustache [new file with mode: 0644]
admin/tool/lp/templates/competency_rule_config.mustache [new file with mode: 0644]
admin/tool/lp/templates/competency_rule_points.mustache [new file with mode: 0644]
admin/tool/lp/templates/competency_summary.mustache [new file with mode: 0644]
admin/tool/lp/templates/course_competencies_page.mustache [new file with mode: 0644]
admin/tool/lp/templates/course_competency_settings.mustache [new file with mode: 0644]
admin/tool/lp/templates/course_competency_statistics.mustache [new file with mode: 0644]
admin/tool/lp/templates/evidence_summary.mustache [new file with mode: 0644]
admin/tool/lp/templates/form-cohort-selector-suggestion.mustache [new file with mode: 0644]
admin/tool/lp/templates/form-user-selector-suggestion.mustache [new file with mode: 0644]
admin/tool/lp/templates/linked_courses_summary.mustache [new file with mode: 0644]
admin/tool/lp/templates/loading.mustache [new file with mode: 0644]
admin/tool/lp/templates/manage_competencies_page.mustache [new file with mode: 0644]
admin/tool/lp/templates/manage_competency_frameworks_page.mustache [new file with mode: 0644]
admin/tool/lp/templates/manage_templates_page.mustache [new file with mode: 0644]
admin/tool/lp/templates/no_frameworks_warning.mustache [new file with mode: 0644]
admin/tool/lp/templates/plan_page.mustache [new file with mode: 0644]
admin/tool/lp/templates/plans_page.mustache [new file with mode: 0644]
admin/tool/lp/templates/related_competencies.mustache [new file with mode: 0644]
admin/tool/lp/templates/scale_configuration_page.mustache [new file with mode: 0644]
admin/tool/lp/templates/template_competencies_page.mustache [new file with mode: 0644]
admin/tool/lp/templates/template_statistics.mustache [new file with mode: 0644]
admin/tool/lp/templates/user_competency_course_navigation.mustache [new file with mode: 0644]
admin/tool/lp/templates/user_competency_summary.mustache [new file with mode: 0644]
admin/tool/lp/templates/user_competency_summary_in_course.mustache [new file with mode: 0644]
admin/tool/lp/templates/user_competency_summary_in_plan.mustache [new file with mode: 0644]
admin/tool/lp/templates/user_evidence_list_page.mustache [new file with mode: 0644]
admin/tool/lp/templates/user_evidence_page.mustache [new file with mode: 0644]
admin/tool/lp/templates/user_summary.mustache [new file with mode: 0644]
admin/tool/lp/tests/behat/behat_tool_lp.php [new file with mode: 0644]
admin/tool/lp/tests/behat/behat_tool_lp_data_generators.php [new file with mode: 0644]
admin/tool/lp/tests/behat/framework_crud.feature [new file with mode: 0644]
admin/tool/lp/tests/behat/plan_crud.feature [new file with mode: 0644]
admin/tool/lp/tests/behat/plan_workflow.feature [new file with mode: 0644]
admin/tool/lp/tests/behat/template_crud.feature [new file with mode: 0644]
admin/tool/lp/tests/behat/user_evidence_comp_link.feature [new file with mode: 0644]
admin/tool/lp/tests/behat/user_evidence_crud.feature [new file with mode: 0644]
admin/tool/lp/tests/externallib_test.php [new file with mode: 0644]
admin/tool/lp/user_competency.php [new file with mode: 0644]
admin/tool/lp/user_competency_in_course.php [new file with mode: 0644]
admin/tool/lp/user_competency_in_plan.php [new file with mode: 0644]
admin/tool/lp/user_evidence.php [new file with mode: 0644]
admin/tool/lp/user_evidence_edit.php [new file with mode: 0644]
admin/tool/lp/user_evidence_list.php [new file with mode: 0644]
admin/tool/lp/version.php [new file with mode: 0644]
admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder-debug.js [new file with mode: 0644]
admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder-min.js [new file with mode: 0644]
admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder.js [new file with mode: 0644]
admin/tool/lp/yui/src/dragdrop-reorder/build.json [new file with mode: 0644]
admin/tool/lp/yui/src/dragdrop-reorder/js/dragdropreorder.js [new file with mode: 0644]
admin/tool/lp/yui/src/dragdrop-reorder/meta/dragdrop-reorder.json [new file with mode: 0644]
admin/tool/lpmigrate/classes/form/migrate_framework.php [new file with mode: 0644]
admin/tool/lpmigrate/classes/framework_mapper.php [new file with mode: 0644]
admin/tool/lpmigrate/classes/framework_processor.php [new file with mode: 0644]
admin/tool/lpmigrate/classes/output/migrate_framework_results.php [new file with mode: 0644]
admin/tool/lpmigrate/classes/output/renderer.php [new file with mode: 0644]
admin/tool/lpmigrate/db/access.php [new file with mode: 0644]
admin/tool/lpmigrate/frameworks.php [new file with mode: 0644]
admin/tool/lpmigrate/lang/en/tool_lpmigrate.php [new file with mode: 0644]
admin/tool/lpmigrate/settings.php [new file with mode: 0644]
admin/tool/lpmigrate/templates/migrate_frameworks_results.mustache [new file with mode: 0644]
admin/tool/lpmigrate/tests/processor_test.php [new file with mode: 0644]
admin/tool/lpmigrate/version.php [new file with mode: 0644]
admin/tool/mobile/classes/api.php [new file with mode: 0644]
admin/tool/mobile/classes/external.php [new file with mode: 0644]
admin/tool/mobile/db/services.php [new file with mode: 0644]
admin/tool/mobile/lang/en/tool_mobile.php [new file with mode: 0644]
admin/tool/mobile/tests/externallib_test.php [new file with mode: 0644]
admin/tool/mobile/version.php [new file with mode: 0644]
admin/tool/monitor/classes/notification_task.php
admin/tool/monitor/tests/eventobservers_test.php
admin/webservice/documentation.php
admin/webservice/forms.php
admin/webservice/testclient.php
availability/condition/grade/tests/behat/availability_grade.feature
backup/moodle2/backup_activity_task.class.php
backup/moodle2/backup_course_task.class.php
backup/moodle2/backup_stepslib.php
backup/moodle2/backup_tool_plugin.class.php [new file with mode: 0644]
backup/moodle2/restore_activity_task.class.php
backup/moodle2/restore_course_task.class.php
backup/moodle2/restore_stepslib.php
backup/moodle2/restore_tool_plugin.class.php [new file with mode: 0644]
blocks/course_overview/renderer.php
blocks/lp/block_lp.php [new file with mode: 0644]
blocks/lp/classes/output/competencies_to_review_page.php [new file with mode: 0644]
blocks/lp/classes/output/plans_to_review_page.php [new file with mode: 0644]
blocks/lp/classes/output/renderer.php [new file with mode: 0644]
blocks/lp/classes/output/summary.php [new file with mode: 0644]
blocks/lp/competencies_to_review.php [new file with mode: 0644]
blocks/lp/db/access.php [new file with mode: 0644]
blocks/lp/lang/en/block_lp.php [new file with mode: 0644]
blocks/lp/plans_to_review.php [new file with mode: 0644]
blocks/lp/styles.css [new file with mode: 0644]
blocks/lp/templates/competencies_to_review_page.mustache [new file with mode: 0644]
blocks/lp/templates/plans_to_review_page.mustache [new file with mode: 0644]
blocks/lp/templates/summary.mustache [new file with mode: 0644]
blocks/lp/version.php [new file with mode: 0644]
blocks/navigation/styles.css
cache/stores/memcached/addinstanceform.php
cache/stores/memcached/lang/en/cachestore_memcached.php
cache/stores/memcached/lib.php
cache/stores/memcached/tests/memcached_test.php
cohort/classes/output/cohortidnumber.php
cohort/classes/output/cohortname.php
comment/lib.php
competency/classes/api.php [new file with mode: 0644]
competency/classes/competency.php [new file with mode: 0644]
competency/classes/competency_framework.php [new file with mode: 0644]
competency/classes/competency_rule.php [new file with mode: 0644]
competency/classes/competency_rule_all.php [new file with mode: 0644]
competency/classes/competency_rule_points.php [new file with mode: 0644]
competency/classes/course_competency.php [new file with mode: 0644]
competency/classes/course_competency_settings.php [new file with mode: 0644]
competency/classes/course_module_competency.php [new file with mode: 0644]
competency/classes/evidence.php [new file with mode: 0644]
competency/classes/external.php [new file with mode: 0644]
competency/classes/external/comment_area_exporter.php [new file with mode: 0644]
competency/classes/external/competency_exporter.php [new file with mode: 0644]
competency/classes/external/competency_framework_exporter.php [new file with mode: 0644]
competency/classes/external/course_competency_exporter.php [new file with mode: 0644]
competency/classes/external/course_competency_settings_exporter.php [new file with mode: 0644]
competency/classes/external/course_module_competency_exporter.php [new file with mode: 0644]
competency/classes/external/evidence_exporter.php [new file with mode: 0644]
competency/classes/external/exporter.php [new file with mode: 0644]
competency/classes/external/persistent_exporter.php [new file with mode: 0644]
competency/classes/external/plan_competency_exporter.php [new file with mode: 0644]
competency/classes/external/plan_exporter.php [new file with mode: 0644]
competency/classes/external/related_competency_exporter.php [new file with mode: 0644]
competency/classes/external/stored_file_exporter.php [new file with mode: 0644]
competency/classes/external/template_competency_exporter.php [new file with mode: 0644]
competency/classes/external/template_exporter.php [new file with mode: 0644]
competency/classes/external/user_competency_course_exporter.php [new file with mode: 0644]
competency/classes/external/user_competency_exporter.php [new file with mode: 0644]
competency/classes/external/user_competency_plan_exporter.php [new file with mode: 0644]
competency/classes/external/user_evidence_competency_exporter.php [new file with mode: 0644]
competency/classes/external/user_evidence_exporter.php [new file with mode: 0644]
competency/classes/external/user_summary_exporter.php [new file with mode: 0644]
competency/classes/invalid_persistent_exception.php [new file with mode: 0644]
competency/classes/persistent.php [new file with mode: 0644]
competency/classes/plan.php [new file with mode: 0644]
competency/classes/plan_competency.php [new file with mode: 0644]
competency/classes/related_competency.php [new file with mode: 0644]
competency/classes/template.php [new file with mode: 0644]
competency/classes/template_cohort.php [new file with mode: 0644]
competency/classes/template_competency.php [new file with mode: 0644]
competency/classes/url.php [new file with mode: 0644]
competency/classes/user_competency.php [new file with mode: 0644]
competency/classes/user_competency_course.php [new file with mode: 0644]
competency/classes/user_competency_plan.php [new file with mode: 0644]
competency/classes/user_evidence.php [new file with mode: 0644]
competency/classes/user_evidence_competency.php [new file with mode: 0644]
competency/lib.php [new file with mode: 0644]
competency/tests/api_test.php [new file with mode: 0644]
competency/tests/competency_rule_test.php [new file with mode: 0644]
competency/tests/course_competency_settings_test.php [new file with mode: 0644]
competency/tests/course_competency_test.php [new file with mode: 0644]
competency/tests/event_test.php [new file with mode: 0644]
competency/tests/exporter_test.php [new file with mode: 0644]
competency/tests/external_test.php [new file with mode: 0644]
competency/tests/generator/lib.php [new file with mode: 0644]
competency/tests/generator_test.php [new file with mode: 0644]
competency/tests/hooks_test.php [new file with mode: 0644]
competency/tests/lib_test.php [new file with mode: 0644]
competency/tests/persistent_test.php [new file with mode: 0644]
competency/tests/plan_test.php [new file with mode: 0644]
competency/tests/task_test.php [new file with mode: 0644]
competency/tests/template_test.php [new file with mode: 0644]
competency/tests/user_evidence_competency_test.php [new file with mode: 0644]
completion/tests/behat/restrict_activity_by_grade.feature
completion/tests/behat/restrict_section_availability.feature
config-dist.php
course/classes/output/course_module_name.php
course/edit.php
course/externallib.php
course/format/lib.php
course/format/singleactivity/lib.php
course/lib.php
course/modlib.php
course/moodleform_mod.php
course/pending.php
course/renderer.php
course/reset_form.php
course/tests/courselib_test.php
course/tests/externallib_test.php
grade/grading/form/guide/renderer.php
grade/grading/form/guide/tests/behat/edit_guide.feature
grade/grading/form/rubric/tests/behat/edit_rubric.feature
grade/grading/tests/behat/behat_grading.php
grade/report/singleview/tests/behat/bulk_insert_grades.feature
grade/tests/behat/grade_scales.feature
grade/tests/behat/grade_single_item_scales.feature
install/lang/ca/install.php
install/lang/cs/install.php
lang/en/admin.php
lang/en/cache.php
lang/en/competency.php [new file with mode: 0644]
lang/en/moodle.php
lang/en/role.php
lang/en/search.php
lang/en/tag.php
lib/ajax/service.php
lib/amd/build/form-autocomplete.min.js
lib/amd/build/form-course-selector.min.js
lib/amd/build/tag.min.js
lib/amd/build/tooltip.min.js [new file with mode: 0644]
lib/amd/src/form-autocomplete.js
lib/amd/src/form-course-selector.js
lib/amd/src/tag.js
lib/amd/src/tooltip.js [new file with mode: 0644]
lib/behat/behat_base.php
lib/behat/lib.php
lib/blocklib.php
lib/classes/component.php
lib/classes/event/competency_comment_created.php [new file with mode: 0644]
lib/classes/event/competency_comment_deleted.php [new file with mode: 0644]
lib/classes/event/competency_created.php [new file with mode: 0644]
lib/classes/event/competency_deleted.php [new file with mode: 0644]
lib/classes/event/competency_evidence_created.php [new file with mode: 0644]
lib/classes/event/competency_framework_created.php [new file with mode: 0644]
lib/classes/event/competency_framework_deleted.php [new file with mode: 0644]
lib/classes/event/competency_framework_updated.php [new file with mode: 0644]
lib/classes/event/competency_framework_viewed.php [new file with mode: 0644]
lib/classes/event/competency_plan_approved.php [new file with mode: 0644]
lib/classes/event/competency_plan_completed.php [new file with mode: 0644]
lib/classes/event/competency_plan_created.php [new file with mode: 0644]
lib/classes/event/competency_plan_deleted.php [new file with mode: 0644]
lib/classes/event/competency_plan_reopened.php [new file with mode: 0644]
lib/classes/event/competency_plan_review_request_cancelled.php [new file with mode: 0644]
lib/classes/event/competency_plan_review_requested.php [new file with mode: 0644]
lib/classes/event/competency_plan_review_started.php [new file with mode: 0644]
lib/classes/event/competency_plan_review_stopped.php [new file with mode: 0644]
lib/classes/event/competency_plan_unapproved.php [new file with mode: 0644]
lib/classes/event/competency_plan_unlinked.php [new file with mode: 0644]
lib/classes/event/competency_plan_updated.php [new file with mode: 0644]
lib/classes/event/competency_plan_viewed.php [new file with mode: 0644]
lib/classes/event/competency_template_created.php [new file with mode: 0644]
lib/classes/event/competency_template_deleted.php [new file with mode: 0644]
lib/classes/event/competency_template_updated.php [new file with mode: 0644]
lib/classes/event/competency_template_viewed.php [new file with mode: 0644]
lib/classes/event/competency_updated.php [new file with mode: 0644]
lib/classes/event/competency_user_competency_plan_viewed.php [new file with mode: 0644]
lib/classes/event/competency_user_competency_rated.php [new file with mode: 0644]
lib/classes/event/competency_user_competency_rated_in_course.php [new file with mode: 0644]
lib/classes/event/competency_user_competency_rated_in_plan.php [new file with mode: 0644]
lib/classes/event/competency_user_competency_review_request_cancelled.php [new file with mode: 0644]
lib/classes/event/competency_user_competency_review_requested.php [new file with mode: 0644]
lib/classes/event/competency_user_competency_review_started.php [new file with mode: 0644]
lib/classes/event/competency_user_competency_review_stopped.php [new file with mode: 0644]
lib/classes/event/competency_user_competency_viewed.php [new file with mode: 0644]
lib/classes/event/competency_user_competency_viewed_in_course.php [new file with mode: 0644]
lib/classes/event/competency_user_competency_viewed_in_plan.php [new file with mode: 0644]
lib/classes/event/competency_user_evidence_created.php [new file with mode: 0644]
lib/classes/event/competency_user_evidence_deleted.php [new file with mode: 0644]
lib/classes/event/competency_user_evidence_updated.php [new file with mode: 0644]
lib/classes/event/competency_viewed.php [new file with mode: 0644]
lib/classes/plugin_manager.php
lib/classes/task/complete_plans_task.php [new file with mode: 0644]
lib/classes/task/manager.php
lib/classes/task/sync_plans_from_template_cohorts_task.php [new file with mode: 0644]
lib/db/access.php
lib/db/caches.php
lib/db/events.php
lib/db/install.xml
lib/db/messages.php
lib/db/services.php
lib/db/tag.php
lib/db/tasks.php
lib/db/upgrade.php
lib/deprecatedlib.php
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/src/editor/js/autosave.js
lib/external/externallib.php
lib/external/tests/external_test.php
lib/externallib.php
lib/filestorage/file_storage.php
lib/form/autocomplete.php
lib/form/course.php
lib/form/tests/behat/modgrade_validation.feature
lib/formslib.php
lib/grade/grade_scale.php
lib/moodlelib.php
lib/outputrequirementslib.php
lib/pagelib.php
lib/phpunit/bootstrap.php
lib/templates/form_autocomplete_selection.mustache
lib/tests/adhoc_task_test.php
lib/tests/behat/behat_forms.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_hooks.php
lib/tests/component_test.php
lib/tests/externallib_test.php
lib/tests/fixtures/unoconv-source.docx [new file with mode: 0644]
lib/tests/fixtures/unoconv-source.html [new file with mode: 0644]
lib/tests/unoconv_test.php [new file with mode: 0644]
lib/upgrade.txt
mod/assign/amd/build/grading_actions.min.js [new file with mode: 0644]
mod/assign/amd/build/grading_form_change_checker.min.js [new file with mode: 0644]
mod/assign/amd/build/grading_navigation.min.js [new file with mode: 0644]
mod/assign/amd/build/grading_navigation_user_info.min.js [new file with mode: 0644]
mod/assign/amd/build/grading_panel.min.js [new file with mode: 0644]
mod/assign/amd/build/grading_review_panel.min.js [new file with mode: 0644]
mod/assign/amd/build/participant_selector.min.js [new file with mode: 0644]
mod/assign/amd/src/grading_actions.js [new file with mode: 0644]
mod/assign/amd/src/grading_form_change_checker.js [new file with mode: 0644]
mod/assign/amd/src/grading_navigation.js [new file with mode: 0644]
mod/assign/amd/src/grading_navigation_user_info.js [new file with mode: 0644]
mod/assign/amd/src/grading_panel.js [new file with mode: 0644]
mod/assign/amd/src/grading_review_panel.js [new file with mode: 0644]
mod/assign/amd/src/participant_selector.js [new file with mode: 0644]
mod/assign/classes/output/grading_app.php [new file with mode: 0644]
mod/assign/db/services.php
mod/assign/externallib.php
mod/assign/feedback/editpdf/classes/document_services.php
mod/assign/feedback/editpdf/classes/event/observer.php [new file with mode: 0644]
mod/assign/feedback/editpdf/classes/renderer.php
mod/assign/feedback/editpdf/classes/task/convert_submissions.php [new file with mode: 0644]
mod/assign/feedback/editpdf/db/events.php [new file with mode: 0644]
mod/assign/feedback/editpdf/db/install.xml
mod/assign/feedback/editpdf/db/tasks.php [new file with mode: 0644]
mod/assign/feedback/editpdf/db/upgrade.php
mod/assign/feedback/editpdf/lang/en/assignfeedback_editpdf.php
mod/assign/feedback/editpdf/locallib.php
mod/assign/feedback/editpdf/styles.css
mod/assign/feedback/editpdf/tests/behat/annotate_pdf.feature
mod/assign/feedback/editpdf/tests/behat/behat_assignfeedback_editpdf.php
mod/assign/feedback/editpdf/tests/behat/group_annotations.feature
mod/assign/feedback/editpdf/version.php
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/globals.js
mod/assign/feedback/editpdf/yui/src/editor/meta/editor.json
mod/assign/feedbackplugin.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/styles.css
mod/assign/templates/attempt_history_chooser.mustache [new file with mode: 0644]
mod/assign/templates/grading_actions.mustache [new file with mode: 0644]
mod/assign/templates/grading_app.mustache [new file with mode: 0644]
mod/assign/templates/grading_navigation.mustache [new file with mode: 0644]
mod/assign/templates/grading_navigation_no_users.mustache [new file with mode: 0644]
mod/assign/templates/grading_navigation_user_info.mustache [new file with mode: 0644]
mod/assign/templates/grading_navigation_user_selector.mustache [new file with mode: 0644]
mod/assign/templates/grading_navigation_user_summary.mustache [new file with mode: 0644]
mod/assign/templates/grading_panel.mustache [new file with mode: 0644]
mod/assign/templates/grading_save_in_progress.mustache [new file with mode: 0644]
mod/assign/templates/list_participant_user_summary.mustache [new file with mode: 0644]
mod/assign/templates/loading.mustache [new file with mode: 0644]
mod/assign/templates/popout_button.mustache [new file with mode: 0644]
mod/assign/templates/review_panel.mustache [new file with mode: 0644]
mod/assign/tests/base_test.php
mod/assign/tests/behat/allow_another_attempt.feature
mod/assign/tests/behat/comment_inline.feature
mod/assign/tests/behat/display_error_message_onbadformat.feature
mod/assign/tests/behat/display_grade.feature
mod/assign/tests/behat/edit_previous_feedback.feature
mod/assign/tests/behat/filter_by_marker.feature
mod/assign/tests/behat/grading_status.feature
mod/assign/tests/behat/grant_extension.feature
mod/assign/tests/behat/group_submission.feature
mod/assign/tests/behat/outcome_grading.feature
mod/assign/tests/behat/prevent_submission_changes.feature
mod/assign/tests/behat/quickgrading.feature
mod/assign/tests/behat/reopen_locked_submission.feature
mod/assign/tests/behat/rescale_grades.feature
mod/assign/tests/behat/steps_blind_marking.feature
mod/assign/tests/behat/submission_comments.feature
mod/assign/tests/behat/submit_without_group.feature
mod/assign/version.php
mod/feedback/analysis_course.php
mod/feedback/classes/templates_table.php [new file with mode: 0644]
mod/feedback/delete_template.php
mod/feedback/delete_template_form.php [deleted file]
mod/feedback/edit.php
mod/feedback/edit_form.php
mod/feedback/import.php
mod/feedback/lang/en/feedback.php
mod/feedback/styles.css
mod/feedback/tests/behat/behat_mod_feedback.php
mod/feedback/tests/behat/export_import.feature [new file with mode: 0644]
mod/feedback/tests/behat/templates.feature [new file with mode: 0644]
mod/feedback/tests/fixtures/testexport.xml [new file with mode: 0644]
mod/feedback/use_templ.php
mod/feedback/use_templ_form.php
mod/forum/externallib.php
mod/forum/lib.php
mod/forum/tests/externallib_test.php
mod/forum/tests/lib_test.php
mod/glossary/view.php
mod/lti/classes/plugininfo/ltisource.php
mod/lti/lib.php
mod/lti/locallib.php
mod/lti/mod_form.php
mod/lti/source/upgrade.txt [new file with mode: 0644]
mod/lti/tests/behat/addtool.feature [new file with mode: 0644]
mod/lti/tests/fixtures/tool_provider.html [new file with mode: 0644]
mod/resource/mod_form.php
mod/upgrade.txt
mod/url/mod_form.php
mod/wiki/classes/external.php
mod/wiki/classes/search/collaborative_page.php [new file with mode: 0644]
mod/wiki/db/services.php
mod/wiki/db/tag.php
mod/wiki/lang/en/wiki.php
mod/wiki/locallib.php
mod/wiki/tests/behat/edit_tags.feature
mod/wiki/tests/externallib_test.php
mod/wiki/tests/generator/lib.php
mod/wiki/tests/generator_test.php
mod/wiki/tests/lib_test.php
mod/wiki/tests/search_test.php [new file with mode: 0644]
mod/wiki/version.php
phpunit.xml.dist
pix/t/editinline.png [new file with mode: 0644]
pix/t/editinline.svg [new file with mode: 0644]
question/export_form.php
question/import_form.php
report/competency/amd/build/grading_popup.min.js [new file with mode: 0644]
report/competency/amd/build/user_course_navigation.min.js [new file with mode: 0644]
report/competency/amd/src/grading_popup.js [new file with mode: 0644]
report/competency/amd/src/user_course_navigation.js [new file with mode: 0644]
report/competency/classes/external.php [new file with mode: 0644]
report/competency/classes/output/renderer.php [new file with mode: 0644]
report/competency/classes/output/report.php [new file with mode: 0644]
report/competency/classes/output/user_course_navigation.php [new file with mode: 0644]
report/competency/db/services.php [new file with mode: 0644]
report/competency/index.php [new file with mode: 0644]
report/competency/lang/en/report_competency.php [new file with mode: 0644]
report/competency/lib.php [new file with mode: 0644]
report/competency/styles.css [new file with mode: 0644]
report/competency/templates/report.mustache [new file with mode: 0644]
report/competency/templates/user_competency_summary.mustache [new file with mode: 0644]
report/competency/templates/user_course_navigation.mustache [new file with mode: 0644]
report/competency/version.php [new file with mode: 0644]
search/classes/engine.php
search/classes/manager.php
search/classes/output/form/search.php
search/engine/solr/classes/engine.php
search/engine/solr/classes/schema.php
search/engine/solr/lang/en/search_solr.php
search/engine/solr/setup_schema.php
search/engine/solr/tests/engine_test.php
search/index.php
search/tests/fixtures/testable_core_search.php
search/tests/manager_test.php
tag/classes/external.php
tag/classes/index_builder.php [new file with mode: 0644]
tag/classes/manage_table.php
tag/classes/tag.php
tag/lib.php
tag/manage.php
tag/tests/behat/edit_tag.feature
tag/tests/behat/standard_tags.feature
tag/tests/external_test.php
tag/tests/taglib_test.php
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/forms.less
theme/bootstrapbase/style/moodle.css
user/externallib.php
user/lib.php
user/tests/externallib_test.php
version.php
webservice/lib.php
webservice/renderer.php
webservice/tests/lib_test.php
webservice/wsdoc.php

diff --git a/admin/settings/competency.php b/admin/settings/competency.php
new file mode 100644 (file)
index 0000000..1fffa73
--- /dev/null
@@ -0,0 +1,50 @@
+<?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/>.
+
+/**
+ * File.
+ *
+ * @package    core_competency
+ * @copyright  2016 Frédéric Massart - FMCorz.net
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+// Save processing when the user will not be able to access anything.
+if (has_capability('moodle/site:config', $systemcontext)) {
+
+    $parentname = 'competencies';
+
+    // Settings page.
+    $settings = new admin_settingpage('competencysettings', new lang_string('competenciessettings', 'core_competency'),
+        'moodle/site:config', false);
+    $ADMIN->add($parentname, $settings);
+
+    // Load the full tree of settings.
+    if ($ADMIN->fulltree) {
+        $setting = new admin_setting_configcheckbox('core_competency/enabled',
+            new lang_string('enablecompetencies', 'core_competency'),
+            new lang_string('enablecompetencies_desc', 'core_competency'), 1);
+        $settings->add($setting);
+
+        $setting = new admin_setting_configcheckbox('core_competency/pushcourseratingstouserplans',
+            new lang_string('pushcourseratingstouserplans', 'core_competency'),
+            new lang_string('pushcourseratingstouserplans_desc', 'core_competency'), 1);
+        $settings->add($setting);
+    }
+
+}
index 573ed4b..548031b 100644 (file)
@@ -12,6 +12,7 @@ $temp->add(new admin_setting_configexecutable('pathtodu', new lang_string('patht
 $temp->add(new admin_setting_configexecutable('aspellpath', new lang_string('aspellpath', 'admin'), new lang_string('edhelpaspellpath'), ''));
 $temp->add(new admin_setting_configexecutable('pathtodot', new lang_string('pathtodot', 'admin'), new lang_string('pathtodot_help', 'admin'), ''));
 $temp->add(new admin_setting_configexecutable('pathtogs', new lang_string('pathtogs', 'admin'), new lang_string('pathtogs_help', 'admin'), '/usr/bin/gs'));
+$temp->add(new admin_setting_configexecutable('pathtounoconv', new lang_string('pathtounoconv', 'admin'), new lang_string('pathtounoconv_help', 'admin'), '/usr/bin/unoconv'));
 $ADMIN->add('server', $temp);
 
 
index 4736f06..223af0c 100644 (file)
@@ -30,6 +30,7 @@ if ($hassiteconfig) {
 $ADMIN->add('root', new admin_category('users', new lang_string('users','admin')));
 $ADMIN->add('root', new admin_category('courses', new lang_string('courses','admin')));
 $ADMIN->add('root', new admin_category('grades', new lang_string('grades')));
+$ADMIN->add('root', new admin_category('competencies', new lang_string('competencies', 'core_competency')));
 $ADMIN->add('root', new admin_category('badges', new lang_string('badges'), empty($CFG->enablebadges)));
 $ADMIN->add('root', new admin_category('location', new lang_string('location','admin')));
 $ADMIN->add('root', new admin_category('language', new lang_string('language')));
diff --git a/admin/tool/cohortroles/classes/api.php b/admin/tool/cohortroles/classes/api.php
new file mode 100644 (file)
index 0000000..e610e35
--- /dev/null
@@ -0,0 +1,228 @@
+<?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/>.
+
+/**
+ * Class exposing the api for the cohortroles tool.
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_cohortroles;
+
+use stdClass;
+use context_system;
+use core_competency\invalid_persistent_exception;
+
+/**
+ * Class for doing things with cohort roles.
+ *
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class api {
+
+    /**
+     * Create a cohort role assignment from a record containing all the data for the class.
+     *
+     * Requires moodle/role:manage capability at the system context.
+     *
+     * @param stdClass $record Record containing all the data for an instance of the class.
+     * @return competency
+     */
+    public static function create_cohort_role_assignment(stdClass $record) {
+        $cohortroleassignment = new cohort_role_assignment(0, $record);
+        $context = context_system::instance();
+
+        // First we do a permissions check.
+        require_capability('moodle/role:manage', $context);
+
+        // Validate before we check for existing records.
+        if (!$cohortroleassignment->is_valid()) {
+            throw new invalid_persistent_exception($cohortroleassignment->get_errors());
+        }
+
+        $existing = cohort_role_assignment::get_record((array) $record);
+        if (!empty($existing)) {
+            return false;
+        } else {
+            // OK - all set.
+            $cohortroleassignment->create();
+        }
+        return $cohortroleassignment;
+    }
+
+    /**
+     * Delete a cohort role assignment by id.
+     *
+     * Requires moodle/role:manage capability at the system context.
+     *
+     * @param int $id The record to delete. This will also remove this role from the user for all users in the system.
+     * @return boolean
+     */
+    public static function delete_cohort_role_assignment($id) {
+        $cohortroleassignment = new cohort_role_assignment($id);
+        $context = context_system::instance();
+
+        // First we do a permissions check.
+        require_capability('moodle/role:manage', $context);
+
+        // OK - all set.
+        return $cohortroleassignment->delete();
+    }
+
+    /**
+     * Perform a search based on the provided filters and return a paginated list of records.
+     *
+     * Requires moodle/role:manage capability at the system context.
+     *
+     * @param string $sort The column to sort on
+     * @param string $order ('ASC' or 'DESC')
+     * @param int $skip Number of records to skip (pagination)
+     * @param int $limit Max of records to return (pagination)
+     * @return array of cohort_role_assignment
+     */
+    public static function list_cohort_role_assignments($sort = '', $order = 'ASC', $skip = 0, $limit = 0) {
+        $context = context_system::instance();
+
+        // First we do a permissions check.
+        require_capability('moodle/role:manage', $context);
+
+        // OK - all set.
+        return cohort_role_assignment::get_records(array(), $sort, $order, $skip, $limit);
+    }
+
+    /**
+     * Perform a search based on the provided filters and return a paginated list of records.
+     *
+     * Requires moodle/role:manage capability at system context.
+     *
+     * @return int
+     */
+    public static function count_cohort_role_assignments() {
+        $context = context_system::instance();
+
+        // First we do a permissions check.
+        require_capability('moodle/role:manage', $context);
+
+        // OK - all set.
+        return cohort_role_assignment::count_records();
+    }
+
+    /**
+     * Sync all roles - adding and deleting role assignments as required.
+     *
+     * Slow. Should only be called from a background task.
+     *
+     * Requires moodle/role:manage capability at the system context.
+     *
+     * @return array('rolesadded' => array of (useridassignedto, useridassignedover, roleid),
+     *               'rolesremoved' => array of (useridassignedto, useridassignedover, roleid))
+     */
+    public static function sync_all_cohort_roles() {
+        global $DB;
+
+        $context = context_system::instance();
+
+        // First we do a permissions check.
+        require_capability('moodle/role:manage', $context);
+
+        // Ok ready to go.
+        $rolesadded = array();
+        $rolesremoved = array();
+
+        // Get all cohort role assignments and group them by user and role.
+        $all = cohort_role_assignment::get_records(array(), 'userid, roleid');
+        // We build an better structure to loop on.
+        $info = array();
+        foreach ($all as $cra) {
+            if (!isset($info[$cra->get_userid()])) {
+                $info[$cra->get_userid()] = array();
+            }
+            if (!isset($info[$cra->get_userid()][$cra->get_roleid()])) {
+                $info[$cra->get_userid()][$cra->get_roleid()] = array();
+            }
+            array_push($info[$cra->get_userid()][$cra->get_roleid()], $cra->get_cohortid());
+        }
+        // Then for each user+role combo - find user context in the cohort without a role assigned.
+
+        foreach ($info as $userid => $roles) {
+            foreach ($roles as $roleid => $cohorts) {
+                list($cohortsql, $params) = $DB->get_in_or_equal($cohorts, SQL_PARAMS_NAMED);
+
+                $params['usercontext'] = CONTEXT_USER;
+                $params['roleid'] = $roleid;
+                $params['userid'] = $userid;
+
+                $sql = 'SELECT u.id AS userid, ra.id, ctx.id AS contextid
+                          FROM {user} u
+                          JOIN {cohort_members} cm ON u.id = cm.userid
+                          JOIN {context} ctx ON u.id = ctx.instanceid AND ctx.contextlevel = :usercontext
+                          LEFT JOIN {role_assignments} ra ON ra.contextid = ctx.id
+                           AND ra.roleid = :roleid
+                           AND ra.userid = :userid
+                         WHERE cm.cohortid ' . $cohortsql . '
+                           AND ra.id IS NULL';
+
+                $toadd = $DB->get_records_sql($sql, $params);
+
+                foreach ($toadd as $add) {
+                    role_assign($roleid, $userid, $add->contextid, 'tool_cohortroles');
+                    $rolesadded[] = array(
+                        'useridassignedto' => $userid,
+                        'useridassignedover' => $add->userid,
+                        'roleid' => $roleid
+                    );
+                }
+            }
+        }
+
+        // And for each user+role combo - find user context not in the cohort with a role assigned.
+        // If the role was assigned by this component, unassign the role.
+        foreach ($info as $userid => $roles) {
+            foreach ($roles as $roleid => $cohorts) {
+                // Now we are looking for entries NOT in the cohort.
+                list($cohortsql, $params) = $DB->get_in_or_equal($cohorts, SQL_PARAMS_NAMED);
+
+                $params['usercontext'] = CONTEXT_USER;
+                $params['roleid'] = $roleid;
+                $params['userid'] = $userid;
+                $params['component'] = 'tool_cohortroles';
+
+                $sql = 'SELECT u.id as userid, ra.id, ctx.id AS contextid
+                          FROM {user} u
+                          JOIN {context} ctx ON u.id = ctx.instanceid AND ctx.contextlevel = :usercontext
+                          JOIN {role_assignments} ra ON ra.contextid = ctx.id AND ra.roleid = :roleid AND ra.userid = :userid
+                     LEFT JOIN {cohort_members} cm ON u.id = cm.userid
+                           AND cm.cohortid ' . $cohortsql . '
+                         WHERE ra.component = :component AND cm.cohortid IS NULL';
+
+                $toremove = $DB->get_records_sql($sql, $params);
+                foreach ($toremove as $remove) {
+                    role_unassign($roleid, $userid, $remove->contextid, 'tool_cohortroles');
+                    $rolesremoved[] = array(
+                        'useridassignedto' => $userid,
+                        'useridassignedover' => $remove->userid,
+                        'roleid' => $roleid
+                    );
+                }
+            }
+        }
+
+        return array('rolesadded' => $rolesadded, 'rolesremoved' => $rolesremoved);
+    }
+
+}
diff --git a/admin/tool/cohortroles/classes/cohort_role_assignment.php b/admin/tool/cohortroles/classes/cohort_role_assignment.php
new file mode 100644 (file)
index 0000000..08ac464
--- /dev/null
@@ -0,0 +1,107 @@
+<?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/>.
+
+/**
+ * Class for cohort_role_assignment persistence.
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_cohortroles;
+
+use lang_string;
+use core_competency\persistent;
+
+/**
+ * Class for loading/storing cohort_role_assignments from the DB.
+ *
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cohort_role_assignment extends persistent {
+
+    /** Table name for user_competency persistency */
+    const TABLE = 'tool_cohortroles';
+
+    /**
+     * Return the definition of the properties of this model.
+     *
+     * @return array
+     */
+    protected static function define_properties() {
+        return array(
+            'userid' => array(
+                'type' => PARAM_INT,
+            ),
+            'roleid' => array(
+                'type' => PARAM_INT,
+            ),
+            'cohortid' => array(
+                'type' => PARAM_INT,
+            )
+        );
+    }
+
+    /**
+     * Validate the user ID.
+     *
+     * @param int $value The value.
+     * @return true|lang_string
+     */
+    protected function validate_userid($value) {
+        global $DB;
+
+        if (!$DB->record_exists('user', array('id' => $value))) {
+            return new lang_string('invaliduserid', 'error');
+        }
+
+        return true;
+    }
+
+    /**
+     * Validate the role ID.
+     *
+     * @param int $value The value.
+     * @return true|lang_string
+     */
+    protected function validate_roleid($value) {
+        global $DB;
+
+        if (!$DB->record_exists('role', array('id' => $value))) {
+            return new lang_string('invalidroleid', 'error');
+        }
+
+        return true;
+    }
+
+    /**
+     * Validate the cohort ID.
+     *
+     * @param int $value The value.
+     * @return true|lang_string
+     */
+    protected function validate_cohortid($value) {
+        global $DB;
+
+        if (!$DB->record_exists('cohort', array('id' => $value))) {
+            return new lang_string('invalidcohortid', 'error');
+        }
+
+        return true;
+    }
+
+}
diff --git a/admin/tool/cohortroles/classes/form/assign_role_cohort.php b/admin/tool/cohortroles/classes/form/assign_role_cohort.php
new file mode 100644 (file)
index 0000000..7cc59ae
--- /dev/null
@@ -0,0 +1,86 @@
+<?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/>.
+
+/**
+ * Assign role to cohort form.
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_cohortroles\form;
+defined('MOODLE_INTERNAL') || die();
+
+use moodleform;
+use context_system;
+
+require_once($CFG->libdir . '/formslib.php');
+
+/**
+ * Assign role to cohort form.
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assign_role_cohort extends moodleform {
+
+    /**
+     * Form definition.
+     */
+    public function definition() {
+        global $PAGE;
+
+        $mform = $this->_form;
+        $roles = get_roles_for_contextlevels(CONTEXT_USER);
+
+        if (empty($roles)) {
+            $output = $PAGE->get_renderer('tool_cohortroles');
+            $warning = $output->notify_problem(get_string('noassignableroles', 'tool_cohortroles'));
+            $mform->addElement('html', $warning);
+            return;
+        }
+
+        $options = array(
+            'ajax' => 'tool_lp/form-user-selector',
+            'multiple' => true
+        );
+        $mform->addElement('autocomplete', 'userids', get_string('selectusers', 'tool_cohortroles'), array(), $options);
+        $mform->addRule('userids', null, 'required');
+
+        $names = role_get_names();
+        $options = array();
+        foreach ($roles as $idx => $roleid) {
+            $options[$roleid] = $names[$roleid]->localname;
+        }
+
+        $mform->addElement('select', 'roleid', get_string('selectrole', 'tool_cohortroles'), $options);
+        $mform->addRule('roleid', null, 'required');
+
+        $context = context_system::instance();
+        $options = array(
+            'ajax' => 'tool_lp/form-cohort-selector',
+            'multiple' => true,
+            'data-contextid' => $context->id,
+            'data-includes' => 'all'
+        );
+        $mform->addElement('autocomplete', 'cohortids', get_string('selectcohorts', 'tool_cohortroles'), array(), $options);
+        $mform->addRule('cohortids', null, 'required');
+        $mform->addElement('submit', 'submit', get_string('assign', 'tool_cohortroles'));
+    }
+
+}
diff --git a/admin/tool/cohortroles/classes/output/cohort_role_assignments_table.php b/admin/tool/cohortroles/classes/output/cohort_role_assignments_table.php
new file mode 100644 (file)
index 0000000..08c8c3f
--- /dev/null
@@ -0,0 +1,231 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Cohort role assignments table
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_cohortroles\output;
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/tablelib.php');
+
+use context_helper;
+use context_system;
+use html_writer;
+use moodle_url;
+use table_sql;
+
+/**
+ * Cohort role assignments table.
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cohort_role_assignments_table extends table_sql {
+
+    /**
+     * Sets up the table.
+     *
+     * @param string $uniqueid Unique id of table.
+     * @param moodle_url $url The base URL.
+     */
+    public function __construct($uniqueid, $url) {
+        global $CFG;
+        parent::__construct($uniqueid);
+        $context = context_system::instance();
+
+        $this->context = $context;
+
+        $this->rolenames = role_get_names();
+
+        // This object should not be used without the right permissions.
+        require_capability('moodle/role:manage', $context);
+
+        $this->useridfield = 'userid';
+
+        // Define columns in the table.
+        $this->define_table_columns();
+
+        $this->define_baseurl($url);
+        // Define configs.
+        $this->define_table_configs();
+    }
+
+    /**
+     * Role name column.
+     *
+     * @param array $data Row data.
+     * @return string
+     */
+    protected function col_rolename($data) {
+        return $this->rolenames[$data->roleid]->localname;
+    }
+
+    /**
+     * Cohort name column.
+     *
+     * @param array $data Row data.
+     * @return string
+     */
+    protected function col_cohortname($data) {
+        global $OUTPUT;
+
+        $record = (object) array(
+            'id' => $data->cohortid,
+            'idnumber' => $data->cohortidnumber,
+            'description' => $data->cohortdescription,
+            'visible' => $data->cohortvisible,
+            'name' => $data->cohortname
+        );
+        $context = context_helper::instance_by_id($data->cohortcontextid);
+
+        $exporter = new \tool_lp\external\cohort_summary_exporter($record, array('context' => $context));
+        $cohort = $exporter->export($OUTPUT);
+
+        $html = $OUTPUT->render_from_template('tool_cohortroles/cohort-in-list', $cohort);
+        return $html;
+    }
+
+    /**
+     * Actions column.
+     *
+     * @param array $data Row data.
+     * @return string
+     */
+    protected function col_actions($data) {
+        global $OUTPUT;
+
+        $action = new \confirm_action(get_string('removecohortroleassignmentconfirm', 'tool_cohortroles'));
+        $url = new moodle_url($this->baseurl);
+        $url->params(array('removecohortroleassignment' => $data->id, 'sesskey' => sesskey()));
+        $pix = new \pix_icon('t/delete', get_string('removecohortroleassignment', 'tool_cohortroles'));
+        return $OUTPUT->action_link($url, '', $action, null, $pix);
+    }
+
+    /**
+     * Setup the headers for the table.
+     */
+    protected function define_table_columns() {
+        $extrafields = get_extra_user_fields($this->context);
+
+        // Define headers and columns.
+        $cols = array(
+            'cohortname' => get_string('cohort', 'cohort'),
+            'rolename' => get_string('role'),
+            'fullname' => get_string('name'),
+        );
+
+        // Add headers for extra user fields.
+        foreach ($extrafields as $field) {
+            if (get_string_manager()->string_exists($field, 'moodle')) {
+                $cols[$field] = get_string($field);
+            } else {
+                $cols[$field] = $field;
+            }
+        }
+
+        // Add remaining headers.
+        $cols = array_merge($cols, array('actions' => get_string('actions')));
+
+        $this->define_columns(array_keys($cols));
+        $this->define_headers(array_values($cols));
+    }
+
+    /**
+     * Define table configs.
+     */
+    protected function define_table_configs() {
+        $this->collapsible(false);
+        $this->sortable(true, 'lastname', SORT_ASC);
+        $this->pageable(true);
+        $this->no_sorting('actions');
+    }
+
+    /**
+     * Builds the SQL query.
+     *
+     * @param bool $count When true, return the count SQL.
+     * @return array containing sql to use and an array of params.
+     */
+    protected function get_sql_and_params($count = false) {
+        $fields = 'uca.id, uca.cohortid, uca.userid, uca.roleid, ';
+        $fields .= 'c.name as cohortname, c.idnumber as cohortidnumber, c.contextid as cohortcontextid, ';
+        $fields .= 'c.visible as cohortvisible, c.description as cohortdescription, ';
+
+        // Add extra user fields that we need for the graded user.
+        $extrafields = get_extra_user_fields($this->context);
+        foreach ($extrafields as $field) {
+            $fields .= 'u.' . $field . ', ';
+        }
+        $fields .= get_all_user_name_fields(true, 'u');
+
+        if ($count) {
+            $select = "COUNT(1)";
+        } else {
+            $select = "$fields";
+        }
+
+        $sql = "SELECT $select
+                   FROM {tool_cohortroles} uca
+                   JOIN {user} u ON u.id = uca.userid
+                   JOIN {cohort} c ON c.id = uca.cohortid";
+        $params = array();
+
+        // Add order by if needed.
+        if (!$count && $sqlsort = $this->get_sql_sort()) {
+            $sql .= " ORDER BY " . $sqlsort;
+        }
+
+        return array($sql, $params);
+    }
+
+    /**
+     * Override the default implementation to set a decent heading level.
+     */
+    public function print_nothing_to_display() {
+        global $OUTPUT;
+        echo $this->render_reset_button();
+        $this->print_initials_bar();
+        echo $OUTPUT->heading(get_string('nothingtodisplay'), 4);
+    }
+
+    /**
+     * Query the DB.
+     *
+     * @param int $pagesize size of page for paginated displayed table.
+     * @param bool $useinitialsbar do you want to use the initials bar.
+     */
+    public function query_db($pagesize, $useinitialsbar = true) {
+        global $DB;
+
+        list($countsql, $countparams) = $this->get_sql_and_params(true);
+        list($sql, $params) = $this->get_sql_and_params();
+        $total = $DB->count_records_sql($countsql, $countparams);
+        $this->pagesize($pagesize, $total);
+        $this->rawdata = $DB->get_records_sql($sql, $params, $this->get_page_start(), $this->get_page_size());
+
+        // Set initial bars.
+        if ($useinitialsbar) {
+            $this->initialbars($total > $pagesize);
+        }
+    }
+}
diff --git a/admin/tool/cohortroles/classes/output/renderer.php b/admin/tool/cohortroles/classes/output/renderer.php
new file mode 100644 (file)
index 0000000..561052f
--- /dev/null
@@ -0,0 +1,76 @@
+<?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/>.
+
+/**
+ * Renderer class for cohort roles
+ *
+ * @package    tool_cohortroles
+ * @copyright  2016 Issam Taboubi <issam.taboubi@umontreal.ca>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_cohortroles\output;
+
+defined('MOODLE_INTERNAL') || die();
+
+use plugin_renderer_base;
+use renderable;
+
+/**
+ * Renderer class for cohort roles
+ *
+ * @package    tool_cohortroles
+ * @copyright  2016 Issam Taboubi <issam.taboubi@umontreal.ca>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class renderer extends plugin_renderer_base {
+
+    /**
+     * Output a nofication.
+     *
+     * @param string $message the message to print out
+     * @return string HTML fragment.
+     * @see \core\output\notification
+     */
+    public function notify_message($message) {
+        $n = new \core\output\notification($message, \core\output\notification::NOTIFY_INFO);
+        return $this->render($n);
+    }
+
+    /**
+     * Output an error notification.
+     *
+     * @param string $message the message to print out
+     * @return string HTML fragment.
+     * @see \core\output\notification
+     */
+    public function notify_problem($message) {
+        $n = new \core\output\notification($message, \core\output\notification::NOTIFY_ERROR);
+        return $this->render($n);
+    }
+
+    /**
+     * Output a success notification.
+     *
+     * @param string $message the message to print out
+     * @return string HTML fragment.
+     * @see \core\output\notification
+     */
+    public function notify_success($message) {
+        $n = new \core\output\notification($message, \core\output\notification::NOTIFY_SUCCESS);
+        return $this->render($n);
+    }
+}
diff --git a/admin/tool/cohortroles/classes/task/cohort_role_sync.php b/admin/tool/cohortroles/classes/task/cohort_role_sync.php
new file mode 100644 (file)
index 0000000..a3d628c
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Scheduled task for syncing cohort roles.
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_cohortroles\task;
+
+use core\task\scheduled_task;
+use tool_cohortroles\api;
+
+/**
+ * Scheduled task for syncing cohort roles.
+ *
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cohort_role_sync extends scheduled_task {
+
+    /**
+     * Get name.
+     * @return string
+     */
+    public function get_name() {
+        // Shown in admin screens.
+        return get_string('taskname', 'tool_cohortroles');
+    }
+
+    /**
+     * Execute.
+     */
+    public function execute() {
+        mtrace('Sync cohort roles...');
+        $result = api::sync_all_cohort_roles();
+
+        mtrace('Added ' . count($result['rolesadded']));
+        mtrace('Removed ' . count($result['rolesremoved']));
+    }
+}
diff --git a/admin/tool/cohortroles/db/install.xml b/admin/tool/cohortroles/db/install.xml
new file mode 100644 (file)
index 0000000..538ec1f
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<XMLDB PATH="admin/tool/cohortroles/db" VERSION="20151201" COMMENT="XMLDB file for Moodle admin/tool/cohortroles"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
+>
+  <TABLES>
+    <TABLE NAME="tool_cohortroles" COMMENT="Mapping of users to cohort role assignments.">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="cohortid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The cohort to sync"/>
+        <FIELD NAME="roleid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The role to assign"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The user to sync"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The time this record was created"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The time this record was modified."/>
+        <FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Who last modified this record?"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="cohortuserrole" UNIQUE="true" FIELDS="cohortid, roleid, userid"/>
+      </INDEXES>
+    </TABLE>
+  </TABLES>
+</XMLDB>
diff --git a/admin/tool/cohortroles/db/tasks.php b/admin/tool/cohortroles/db/tasks.php
new file mode 100644 (file)
index 0000000..e5f5a62
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Tasks definitions.
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$tasks = array(
+    array(
+        'classname' => 'tool_cohortroles\task\cohort_role_sync',
+        'blocking' => 0,
+        'minute' => 'R',
+        'hour' => '*',
+        'day' => '*',
+        'dayofweek' => '*',
+        'month' => '*'
+    ),
+);
diff --git a/admin/tool/cohortroles/index.php b/admin/tool/cohortroles/index.php
new file mode 100644 (file)
index 0000000..a202be6
--- /dev/null
@@ -0,0 +1,98 @@
+<?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/>.
+
+/**
+ * Assign roles for a user.
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(dirname(__FILE__) . '/../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/tablelib.php');
+
+$removeid = optional_param('removecohortroleassignment', 0, PARAM_INT);
+
+admin_externalpage_setup('toolcohortroles');
+$context = context_system::instance();
+
+$pageurl = new moodle_url('/admin/tool/cohortroles/index.php');
+
+$output = $PAGE->get_renderer('tool_cohortroles');
+
+echo $output->header();
+$title = get_string('assignroletocohort', 'tool_cohortroles');
+echo $output->heading($title);
+
+$form = new tool_cohortroles\form\assign_role_cohort();
+
+if ($removeid) {
+    require_sesskey();
+
+    $result = \tool_cohortroles\api::delete_cohort_role_assignment($removeid);
+    if ($result) {
+        $notification = get_string('cohortroleassignmentremoved', 'tool_cohortroles');
+        echo $output->notify_success($notification);
+    } else {
+        $notification = get_string('cohortroleassignmentnotremoved', 'tool_cohortroles');
+        echo $output->notify_problem($notification);
+    }
+    echo $output->continue_button(new moodle_url($pageurl));
+} else if ($data = $form->get_data()) {
+    require_sesskey();
+    // We must create them all or none.
+    $saved = 0;
+    foreach ($data->userids as $userid) {
+        if (empty($data->cohortids)) {
+            $data->cohortids = array();
+        }
+        foreach ($data->cohortids as $cohortid) {
+            $params = (object) array('userid' => $userid, 'cohortid' => $cohortid, 'roleid' => $data->roleid);
+            $result = \tool_cohortroles\api::create_cohort_role_assignment($params);
+            if ($result) {
+                $saved++;
+            }
+        }
+    }
+    if ($saved == 0) {
+        $notification = get_string('nocohortroleassignmentssaved', 'tool_cohortroles');
+        echo $output->notify_problem($notification);
+    } else if ($saved == 1) {
+        $notification = get_string('onecohortroleassignmentsaved', 'tool_cohortroles');
+        echo $output->notify_success($notification);
+    } else {
+        $notification = get_string('acohortroleassignmentssaved', 'tool_cohortroles', $saved);
+        echo $output->notify_success($notification);
+    }
+
+    echo $output->continue_button(new moodle_url($pageurl));
+} else {
+    $form->display();
+
+    $title = get_string('existingcohortroles', 'tool_cohortroles');
+    echo $output->heading($title);
+    $url = new moodle_url('/admin/tool/cohortroles/index.php');
+    $table = new tool_cohortroles\output\cohort_role_assignments_table(uniqid(), $url);
+    echo $table->out(50, true);
+
+    echo $output->spacer();
+    echo $output->notify_message(get_string('backgroundsync', 'tool_cohortroles'));
+}
+
+echo $output->footer();
+
diff --git a/admin/tool/cohortroles/lang/en/tool_cohortroles.php b/admin/tool/cohortroles/lang/en/tool_cohortroles.php
new file mode 100644 (file)
index 0000000..e735f7e
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Strings for component 'tool_userroles', language 'en'
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['acohortroleassignmentssaved'] = '{$a} cohort role assignments were saved.';
+$string['assign'] = 'Assign';
+$string['assignroletocohort'] = 'Assign role to users in the context of all members of these cohorts';
+$string['backgroundsync'] = 'Note: Roles assigned on this page will not take affect immediately, the changes to role assignments will be made by a background task.';
+$string['cohortroleassignmentremoved'] = 'The cohort role assignment was removed.';
+$string['cohortroleassignmentnotremoved'] = 'The cohort role assignment was not removed.';
+$string['cohortroles'] = 'Cohort roles';
+$string['existingcohortroles'] = 'Existing cohort role assignments';
+$string['managecohortroles'] = 'Assign user roles for entire cohort';
+$string['noassignableroles'] = 'No roles can be assigned at a user context level. <a href="../../roles/manage.php">Manage roles</a>';
+$string['nocohortroleassignmentssaved'] = 'No cohort role assignments were saved.';
+$string['onecohortroleassignmentsaved'] = 'One cohort role assignment was saved.';
+$string['pluginname'] = 'Cohort roles management';
+$string['removecohortroleassignment'] = 'Remove cohort role assignment';
+$string['removecohortroleassignmentconfirm'] = 'Are you sure you want to remove this cohort role assignment? This role will be removed for this user in all other user contexts.';
+$string['selectcohorts'] = 'Select cohorts';
+$string['selectrole'] = 'Select role';
+$string['selectusers'] = 'Select users to assign role';
+$string['taskname'] = 'Sync cohort role assignments';
+$string['thisuserroles'] = 'Roles assigned relative to this user';
diff --git a/admin/tool/cohortroles/settings.php b/admin/tool/cohortroles/settings.php
new file mode 100644 (file)
index 0000000..98613c1
--- /dev/null
@@ -0,0 +1,27 @@
+<?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/>.
+
+/**
+ * Link to user roles management.
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+$str = get_string('managecohortroles', 'tool_cohortroles');
+$ADMIN->add('roles', new admin_externalpage('toolcohortroles', $str, '/admin/tool/cohortroles/index.php', 'moodle/role:manage'));
diff --git a/admin/tool/cohortroles/templates/cohort-in-list.mustache b/admin/tool/cohortroles/templates/cohort-in-list.mustache
new file mode 100644 (file)
index 0000000..971065a
--- /dev/null
@@ -0,0 +1,43 @@
+{{!
+    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/>.
+}}
+{{!
+    @template tool_cohortroles/cohort-in-list
+
+    Moodle template for displaying a cohort in a list.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * id cohort id field
+    * name cohort name field
+    * idnumber cohort idnumber field
+    * description cohort description field
+    * visible cohort visible field
+
+    Example context (json):
+    { "id": "1",
+      "name": "Cohort 1",
+      "visible": true,
+      "idnumber": "014",
+      "description": "Some users"
+    }
+}}
+{{> tool_lp/form-cohort-selector-suggestion }}
diff --git a/admin/tool/cohortroles/tests/api_test.php b/admin/tool/cohortroles/tests/api_test.php
new file mode 100644 (file)
index 0000000..27c4cd8
--- /dev/null
@@ -0,0 +1,243 @@
+<?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/>.
+
+/**
+ * API tests.
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use tool_cohortroles\api;
+
+/**
+ * API tests.
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_cohortroles_api_testcase extends advanced_testcase {
+    /** @var stdClass $cohort */
+    protected $cohort = null;
+
+    /** @var stdClass $userassignto */
+    protected $userassignto = null;
+
+    /** @var stdClass $userassignover */
+    protected $userassignover = null;
+
+    /** @var stdClass $role */
+    protected $role = null;
+
+    /**
+     * Setup function- we will create a course and add an assign instance to it.
+     */
+    protected function setUp() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Create some users.
+        $this->cohort = $this->getDataGenerator()->create_cohort();
+        $this->userassignto = $this->getDataGenerator()->create_user();
+        $this->userassignover = $this->getDataGenerator()->create_user();
+        $this->roleid = create_role('Sausage Roll', 'sausageroll', 'mmmm');
+        cohort_add_member($this->cohort->id, $this->userassignover->id);
+    }
+
+
+    public function test_create_cohort_role_assignment_without_permission() {
+        $this->setExpectedException('required_capability_exception');
+        $this->setUser($this->userassignto);
+        $params = (object) array(
+            'userid' => $this->userassignto->id,
+            'roleid' => $this->roleid,
+            'cohortid' => $this->cohort->id
+        );
+        api::create_cohort_role_assignment($params);
+    }
+
+    public function test_create_cohort_role_assignment_with_invalid_data() {
+        $this->setExpectedException('core_competency\invalid_persistent_exception');
+        $this->setAdminUser();
+        $params = (object) array(
+            'userid' => $this->userassignto->id,
+            'roleid' => -8,
+            'cohortid' => $this->cohort->id
+        );
+        api::create_cohort_role_assignment($params);
+    }
+
+    public function test_create_cohort_role_assignment() {
+        $this->setAdminUser();
+        $params = (object) array(
+            'userid' => $this->userassignto->id,
+            'roleid' => $this->roleid,
+            'cohortid' => $this->cohort->id
+        );
+        $result = api::create_cohort_role_assignment($params);
+        $this->assertNotEmpty($result->get_id());
+        $this->assertEquals($result->get_userid(), $this->userassignto->id);
+        $this->assertEquals($result->get_roleid(), $this->roleid);
+        $this->assertEquals($result->get_cohortid(), $this->cohort->id);
+    }
+
+    public function test_delete_cohort_role_assignment_without_permission() {
+        $this->setAdminUser();
+        $params = (object) array(
+            'userid' => $this->userassignto->id,
+            'roleid' => $this->roleid,
+            'cohortid' => $this->cohort->id
+        );
+        $result = api::create_cohort_role_assignment($params);
+        $this->setExpectedException('required_capability_exception');
+        $this->setUser($this->userassignto);
+        api::delete_cohort_role_assignment($result->get_id());
+    }
+
+    public function test_delete_cohort_role_assignment_with_invalid_data() {
+        $this->setAdminUser();
+        $params = (object) array(
+            'userid' => $this->userassignto->id,
+            'roleid' => $this->roleid,
+            'cohortid' => $this->cohort->id
+        );
+        $result = api::create_cohort_role_assignment($params);
+        $this->setExpectedException('dml_missing_record_exception');
+        api::delete_cohort_role_assignment($result->get_id() + 1);
+    }
+
+    public function test_delete_cohort_role_assignment() {
+        $this->setAdminUser();
+        $params = (object) array(
+            'userid' => $this->userassignto->id,
+            'roleid' => $this->roleid,
+            'cohortid' => $this->cohort->id
+        );
+        $result = api::create_cohort_role_assignment($params);
+        $worked = api::delete_cohort_role_assignment($result->get_id());
+        $this->assertTrue($worked);
+    }
+
+    public function test_list_cohort_role_assignments() {
+        $this->setAdminUser();
+        $params = (object) array(
+            'userid' => $this->userassignto->id,
+            'roleid' => $this->roleid,
+            'cohortid' => $this->cohort->id
+        );
+        $result = api::create_cohort_role_assignment($params);
+
+        $list = api::list_cohort_role_assignments();
+        $list[0]->is_valid();
+        $this->assertEquals($list[0], $result);
+    }
+
+    public function test_count_cohort_role_assignments() {
+        $this->setAdminUser();
+        $params = (object) array(
+            'userid' => $this->userassignto->id,
+            'roleid' => $this->roleid,
+            'cohortid' => $this->cohort->id
+        );
+        $result = api::create_cohort_role_assignment($params);
+
+        $count = api::count_cohort_role_assignments();
+        $this->assertEquals($count, 1);
+    }
+
+    public function test_sync_all_cohort_roles() {
+        $this->setAdminUser();
+        $params = (object) array(
+            'userid' => $this->userassignto->id,
+            'roleid' => $this->roleid,
+            'cohortid' => $this->cohort->id
+        );
+        $result = api::create_cohort_role_assignment($params);
+
+        // Verify roles are assigned when users enter the cohort.
+        $sync = api::sync_all_cohort_roles();
+
+        $rolesadded = array(array(
+            'useridassignedto' => $this->userassignto->id,
+            'useridassignedover' => $this->userassignover->id,
+            'roleid' => $this->roleid
+        ));
+        $rolesremoved = array();
+        $expected = array('rolesadded' => $rolesadded,
+                          'rolesremoved' => $rolesremoved);
+        $this->assertEquals($sync, $expected);
+
+        // Verify roles are removed when users leave the cohort.
+        cohort_remove_member($this->cohort->id, $this->userassignover->id);
+        $sync = api::sync_all_cohort_roles();
+
+        $rolesadded = array();
+        $rolesremoved = array(array(
+            'useridassignedto' => $this->userassignto->id,
+            'useridassignedover' => $this->userassignover->id,
+            'roleid' => $this->roleid
+        ));
+        $expected = array('rolesadded' => $rolesadded,
+                          'rolesremoved' => $rolesremoved);
+        $this->assertEquals($sync, $expected);
+
+        // Verify roles assigned by any other component are not removed.
+        $usercontext = context_user::instance($this->userassignover->id);
+        role_assign($this->roleid, $this->userassignto->id, $usercontext->id);
+        $sync = api::sync_all_cohort_roles();
+
+        $rolesadded = array();
+        $rolesremoved = array();
+        $expected = array('rolesadded' => $rolesadded,
+                          'rolesremoved' => $rolesremoved);
+        $this->assertEquals($sync, $expected);
+
+        // Remove manual role assignment.
+        role_unassign($this->roleid, $this->userassignto->id, $usercontext->id);
+        // Add someone to the cohort again...
+        cohort_add_member($this->cohort->id, $this->userassignover->id);
+        $sync = api::sync_all_cohort_roles();
+        $rolesadded = array(array(
+            'useridassignedto' => $this->userassignto->id,
+            'useridassignedover' => $this->userassignover->id,
+            'roleid' => $this->roleid
+        ));
+        $rolesremoved = array();
+        $expected = array('rolesadded' => $rolesadded,
+                          'rolesremoved' => $rolesremoved);
+        $this->assertEquals($sync, $expected);
+
+        // Verify no fatal errors when a cohort is deleted.
+        cohort_delete_cohort($this->cohort);
+        $sync = api::sync_all_cohort_roles();
+
+        $rolesadded = array();
+        $rolesremoved = array(array(
+            'useridassignedto' => $this->userassignto->id,
+            'useridassignedover' => $this->userassignover->id,
+            'roleid' => $this->roleid
+        ));
+        $expected = array('rolesadded' => $rolesadded,
+                          'rolesremoved' => $rolesremoved);
+        $this->assertEquals($sync, $expected);
+    }
+
+}
diff --git a/admin/tool/cohortroles/version.php b/admin/tool/cohortroles/version.php
new file mode 100644 (file)
index 0000000..bf9fb21
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Plugin version info
+ *
+ * @package    tool_cohortroles
+ * @copyright  2015 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+$plugin->version   = 2015111018; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2014110400; // Requires this Moodle version.
+$plugin->component = 'tool_cohortroles'; // Full name of the plugin (used for diagnostics).
+
+$plugin->dependencies = array(
+    'tool_lp' => ANY_VERSION
+);
index 9586c03..22d80d4 100644 (file)
@@ -30,6 +30,7 @@
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
       </KEYS>
       <INDEXES>
         <INDEX NAME="timecreated" UNIQUE="false" FIELDS="timecreated"/>
index b844b62..4e23361 100644 (file)
@@ -25,7 +25,9 @@
 defined('MOODLE_INTERNAL') || die();
 
 function xmldb_logstore_standard_upgrade($oldversion) {
-    global $CFG;
+    global $CFG, $DB;
+
+    $dbman = $DB->get_manager();
 
     // Moodle v2.8.0 release upgrade line.
     // Put any upgrade step following this.
@@ -36,5 +38,20 @@ function xmldb_logstore_standard_upgrade($oldversion) {
     // Moodle v3.0.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2016041200) {
+        // This could take a long time. Unfortunately, no way to know how long, and no way to do progress, so setting for 1 hour.
+        upgrade_set_timeout(3600);
+
+        // Define key contextid (foreign) to be added to logstore_standard_log.
+        $table = new xmldb_table('logstore_standard_log');
+        $key = new xmldb_key('contextid', XMLDB_KEY_FOREIGN, array('contextid'), 'context', array('id'));
+
+        // Launch add key contextid.
+        $dbman->add_key($table, $key);
+
+        // Standard savepoint reached.
+        upgrade_plugin_savepoint(true, 2016041200, 'logstore', 'standard');
+    }
+
     return true;
 }
index 73c131e..2e482f4 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2015111600; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version = 2016041200; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires = 2015111000; // Requires this Moodle version.
 $plugin->component = 'logstore_standard'; // Full name of the plugin (used for diagnostics).
diff --git a/admin/tool/lp/amd/build/actionselector.min.js b/admin/tool/lp/amd/build/actionselector.min.js
new file mode 100644 (file)
index 0000000..90062ca
Binary files /dev/null and b/admin/tool/lp/amd/build/actionselector.min.js differ
diff --git a/admin/tool/lp/amd/build/competencies.min.js b/admin/tool/lp/amd/build/competencies.min.js
new file mode 100644 (file)
index 0000000..cf57df2
Binary files /dev/null and b/admin/tool/lp/amd/build/competencies.min.js differ
diff --git a/admin/tool/lp/amd/build/competency_outcomes.min.js b/admin/tool/lp/amd/build/competency_outcomes.min.js
new file mode 100644 (file)
index 0000000..2da1745
Binary files /dev/null and b/admin/tool/lp/amd/build/competency_outcomes.min.js differ
diff --git a/admin/tool/lp/amd/build/competency_rule.min.js b/admin/tool/lp/amd/build/competency_rule.min.js
new file mode 100644 (file)
index 0000000..8939576
Binary files /dev/null and b/admin/tool/lp/amd/build/competency_rule.min.js differ
diff --git a/admin/tool/lp/amd/build/competency_rule_all.min.js b/admin/tool/lp/amd/build/competency_rule_all.min.js
new file mode 100644 (file)
index 0000000..d95f6a0
Binary files /dev/null and b/admin/tool/lp/amd/build/competency_rule_all.min.js differ
diff --git a/admin/tool/lp/amd/build/competency_rule_points.min.js b/admin/tool/lp/amd/build/competency_rule_points.min.js
new file mode 100644 (file)
index 0000000..39eaef7
Binary files /dev/null and b/admin/tool/lp/amd/build/competency_rule_points.min.js differ
diff --git a/admin/tool/lp/amd/build/competencyactions.min.js b/admin/tool/lp/amd/build/competencyactions.min.js
new file mode 100644 (file)
index 0000000..76bb677
Binary files /dev/null and b/admin/tool/lp/amd/build/competencyactions.min.js differ
diff --git a/admin/tool/lp/amd/build/competencydialogue.min.js b/admin/tool/lp/amd/build/competencydialogue.min.js
new file mode 100644 (file)
index 0000000..b22cdb3
Binary files /dev/null and b/admin/tool/lp/amd/build/competencydialogue.min.js differ
diff --git a/admin/tool/lp/amd/build/competencypicker.min.js b/admin/tool/lp/amd/build/competencypicker.min.js
new file mode 100644 (file)
index 0000000..61eec30
Binary files /dev/null and b/admin/tool/lp/amd/build/competencypicker.min.js differ
diff --git a/admin/tool/lp/amd/build/competencypicker_user_plans.min.js b/admin/tool/lp/amd/build/competencypicker_user_plans.min.js
new file mode 100644 (file)
index 0000000..faf4bd4
Binary files /dev/null and b/admin/tool/lp/amd/build/competencypicker_user_plans.min.js differ
diff --git a/admin/tool/lp/amd/build/competencyruleconfig.min.js b/admin/tool/lp/amd/build/competencyruleconfig.min.js
new file mode 100644 (file)
index 0000000..8a0f0a2
Binary files /dev/null and b/admin/tool/lp/amd/build/competencyruleconfig.min.js differ
diff --git a/admin/tool/lp/amd/build/competencytree.min.js b/admin/tool/lp/amd/build/competencytree.min.js
new file mode 100644 (file)
index 0000000..4955f99
Binary files /dev/null and b/admin/tool/lp/amd/build/competencytree.min.js differ
diff --git a/admin/tool/lp/amd/build/course_competency_settings.min.js b/admin/tool/lp/amd/build/course_competency_settings.min.js
new file mode 100644 (file)
index 0000000..5fe246e
Binary files /dev/null and b/admin/tool/lp/amd/build/course_competency_settings.min.js differ
diff --git a/admin/tool/lp/amd/build/dialogue.min.js b/admin/tool/lp/amd/build/dialogue.min.js
new file mode 100644 (file)
index 0000000..7538d6a
Binary files /dev/null and b/admin/tool/lp/amd/build/dialogue.min.js differ
diff --git a/admin/tool/lp/amd/build/dragdrop-reorder.min.js b/admin/tool/lp/amd/build/dragdrop-reorder.min.js
new file mode 100644 (file)
index 0000000..77c33ba
Binary files /dev/null and b/admin/tool/lp/amd/build/dragdrop-reorder.min.js differ
diff --git a/admin/tool/lp/amd/build/event_base.min.js b/admin/tool/lp/amd/build/event_base.min.js
new file mode 100644 (file)
index 0000000..c9e4818
Binary files /dev/null and b/admin/tool/lp/amd/build/event_base.min.js differ
diff --git a/admin/tool/lp/amd/build/evidence_delete.min.js b/admin/tool/lp/amd/build/evidence_delete.min.js
new file mode 100644 (file)
index 0000000..aebb3f1
Binary files /dev/null and b/admin/tool/lp/amd/build/evidence_delete.min.js differ
diff --git a/admin/tool/lp/amd/build/form-cohort-selector.min.js b/admin/tool/lp/amd/build/form-cohort-selector.min.js
new file mode 100644 (file)
index 0000000..b582a15
Binary files /dev/null and b/admin/tool/lp/amd/build/form-cohort-selector.min.js differ
diff --git a/admin/tool/lp/amd/build/form-user-selector.min.js b/admin/tool/lp/amd/build/form-user-selector.min.js
new file mode 100644 (file)
index 0000000..d228806
Binary files /dev/null and b/admin/tool/lp/amd/build/form-user-selector.min.js differ
diff --git a/admin/tool/lp/amd/build/frameworkactions.min.js b/admin/tool/lp/amd/build/frameworkactions.min.js
new file mode 100644 (file)
index 0000000..32c694d
Binary files /dev/null and b/admin/tool/lp/amd/build/frameworkactions.min.js differ
diff --git a/admin/tool/lp/amd/build/frameworks_datasource.min.js b/admin/tool/lp/amd/build/frameworks_datasource.min.js
new file mode 100644 (file)
index 0000000..cfc5c70
Binary files /dev/null and b/admin/tool/lp/amd/build/frameworks_datasource.min.js differ
diff --git a/admin/tool/lp/amd/build/grade_dialogue.min.js b/admin/tool/lp/amd/build/grade_dialogue.min.js
new file mode 100644 (file)
index 0000000..6bfa3df
Binary files /dev/null and b/admin/tool/lp/amd/build/grade_dialogue.min.js differ
diff --git a/admin/tool/lp/amd/build/grade_user_competency_inline.min.js b/admin/tool/lp/amd/build/grade_user_competency_inline.min.js
new file mode 100644 (file)
index 0000000..ac0c28f
Binary files /dev/null and b/admin/tool/lp/amd/build/grade_user_competency_inline.min.js differ
diff --git a/admin/tool/lp/amd/build/menubar.min.js b/admin/tool/lp/amd/build/menubar.min.js
new file mode 100644 (file)
index 0000000..e5ed4ae
Binary files /dev/null and b/admin/tool/lp/amd/build/menubar.min.js differ
diff --git a/admin/tool/lp/amd/build/parentcompetency_form.min.js b/admin/tool/lp/amd/build/parentcompetency_form.min.js
new file mode 100644 (file)
index 0000000..0161053
Binary files /dev/null and b/admin/tool/lp/amd/build/parentcompetency_form.min.js differ
diff --git a/admin/tool/lp/amd/build/planactions.min.js b/admin/tool/lp/amd/build/planactions.min.js
new file mode 100644 (file)
index 0000000..d590b7e
Binary files /dev/null and b/admin/tool/lp/amd/build/planactions.min.js differ
diff --git a/admin/tool/lp/amd/build/scaleconfig.min.js b/admin/tool/lp/amd/build/scaleconfig.min.js
new file mode 100644 (file)
index 0000000..db0ffd8
Binary files /dev/null and b/admin/tool/lp/amd/build/scaleconfig.min.js differ
diff --git a/admin/tool/lp/amd/build/scalevalues.min.js b/admin/tool/lp/amd/build/scalevalues.min.js
new file mode 100644 (file)
index 0000000..53c3286
Binary files /dev/null and b/admin/tool/lp/amd/build/scalevalues.min.js differ
diff --git a/admin/tool/lp/amd/build/templateactions.min.js b/admin/tool/lp/amd/build/templateactions.min.js
new file mode 100644 (file)
index 0000000..02fd09b
Binary files /dev/null and b/admin/tool/lp/amd/build/templateactions.min.js differ
diff --git a/admin/tool/lp/amd/build/tree.min.js b/admin/tool/lp/amd/build/tree.min.js
new file mode 100644 (file)
index 0000000..e03f666
Binary files /dev/null and b/admin/tool/lp/amd/build/tree.min.js differ
diff --git a/admin/tool/lp/amd/build/user_competency_course_navigation.min.js b/admin/tool/lp/amd/build/user_competency_course_navigation.min.js
new file mode 100644 (file)
index 0000000..2168ebd
Binary files /dev/null and b/admin/tool/lp/amd/build/user_competency_course_navigation.min.js differ
diff --git a/admin/tool/lp/amd/build/user_competency_info.min.js b/admin/tool/lp/amd/build/user_competency_info.min.js
new file mode 100644 (file)
index 0000000..8d95549
Binary files /dev/null and b/admin/tool/lp/amd/build/user_competency_info.min.js differ
diff --git a/admin/tool/lp/amd/build/user_competency_workflow.min.js b/admin/tool/lp/amd/build/user_competency_workflow.min.js
new file mode 100644 (file)
index 0000000..821222f
Binary files /dev/null and b/admin/tool/lp/amd/build/user_competency_workflow.min.js differ
diff --git a/admin/tool/lp/amd/build/user_evidence_actions.min.js b/admin/tool/lp/amd/build/user_evidence_actions.min.js
new file mode 100644 (file)
index 0000000..e479a60
Binary files /dev/null and b/admin/tool/lp/amd/build/user_evidence_actions.min.js differ
diff --git a/admin/tool/lp/amd/src/actionselector.js b/admin/tool/lp/amd/src/actionselector.js
new file mode 100644 (file)
index 0000000..d36f06f
--- /dev/null
@@ -0,0 +1,193 @@
+// 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/>.
+
+/**
+ * Action selector.
+ *
+ * To handle 'save' events use: actionselector.on('save')
+ * This will receive the information to display in popup.
+ * The actions have the format [{'text': sometext, 'value' : somevalue}].
+ *
+ * @package    tool_lp
+ * @copyright  2016 Serge Gauthier - <serge.gauthier.2@umontreal.ca>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery',
+        'core/notification',
+        'core/ajax',
+        'core/templates',
+        'tool_lp/dialogue',
+        'tool_lp/event_base'],
+        function($, Notification, Ajax, Templates, Dialogue, EventBase) {
+
+    /**
+     * Action selector class.
+     * @param {String} title The title of popup.
+     * @param {String} message The message to display.
+     * @param {object} actions The actions that can be selected.
+     * @param {String} confirm Text for confirm button.
+     * @param {String} cancel Text for cancel button.
+     */
+    var ActionSelector = function(title, message, actions, confirm, cancel) {
+        var self = this;
+
+        EventBase.prototype.constructor.apply(this, []);
+        self._title = title;
+        self._message = message;
+        self._actions = actions;
+        self._confirm = confirm;
+        self._cancel = cancel;
+        self._selectedValue = null;
+        self._reset();
+    };
+
+    ActionSelector.prototype = Object.create(EventBase.prototype);
+
+    /** @type {String} The value that was selected. */
+    ActionSelector.prototype._selectedValue = null;
+    /** @type {Dialogue} The reference to the dialogue. */
+    ActionSelector.prototype._popup = null;
+    /** @type {String} The title of popup. */
+    ActionSelector.prototype._title = null;
+    /** @type {String} The message in popup. */
+    ActionSelector.prototype._message = null;
+    /** @type {object} The information for radion buttons. */
+    ActionSelector.prototype._actions = null;
+    /** @type {String} The text for confirm button. */
+    ActionSelector.prototype._confirm = null;
+    /** @type {String} The text for cancel button. */
+    ActionSelector.prototype._cancel = null;
+
+    /**
+     * Hook to executed after the view is rendered.
+     *
+     * @method _afterRender
+     */
+    ActionSelector.prototype._afterRender = function() {
+        var self = this;
+
+        // Confirm button is disabled until a choice is done.
+        self._find('[data-action="action-selector-confirm"]').attr('disabled', 'disabled');
+
+        // Add listener for radio buttons change.
+        self._find('[data-region="action-selector-radio-buttons"]').change(function() {
+            self._selectedValue = $("input[type='radio']:checked").val();
+            self._find('[data-action="action-selector-confirm"]').removeAttr('disabled');
+            self._refresh.bind(self);
+        }.bind(self));
+
+        // Add listener for cancel.
+        self._find('[data-action="action-selector-cancel"]').click(function(e) {
+            e.preventDefault();
+            self.close();
+        }.bind(self));
+
+        // Add listener for confirm.
+        self._find('[data-action="action-selector-confirm"]').click(function(e) {
+            e.preventDefault();
+            if (!self._selectedValue.length) {
+                return;
+            }
+            self._trigger('save', { action: self._selectedValue });
+            self.close();
+        }.bind(self));
+    };
+
+    /**
+     * Close the dialogue.
+     *
+     * @method close
+     */
+    ActionSelector.prototype.close = function() {
+        var self = this;
+        self._popup.close();
+        self._reset();
+    };
+
+    /**
+     * Opens the action selector.
+     *
+     * @method display
+     * @return {Promise}
+     */
+    ActionSelector.prototype.display = function() {
+        var self = this;
+        return self._render().then(function(html) {
+            self._popup = new Dialogue(
+                self._title,
+                html,
+                self._afterRender.bind(self)
+            );
+        }.bind(self)).fail(Notification.exception);
+    };
+
+    /**
+     * Find a node in the dialogue.
+     *
+     * @param {String} selector
+     * @method _find
+     */
+    ActionSelector.prototype._find = function(selector) {
+        return $(this._popup.getContent()).find(selector);
+    };
+
+    /**
+     * Refresh the view.
+     *
+     * @method _refresh
+     * @return {Promise}
+     */
+    ActionSelector.prototype._refresh = function() {
+        var self = this;
+        return self._render().then(function(html) {
+            self._find('[data-region="action-selector"]').replaceWith(html);
+            self._afterRender();
+        }.bind(self));
+    };
+
+    /**
+     * Render the dialogue.
+     *
+     * @method _render
+     * @return {Promise}
+     */
+    ActionSelector.prototype._render = function() {
+        var self = this;
+        var choices = [];
+        for (var i in self._actions) {
+            choices.push(self._actions[i]);
+        }
+        var content = {'message': self._message, 'choices' : choices,
+            'confirm' : self._confirm, 'cancel' : self._cancel};
+
+        return Templates.render('tool_lp/action_selector', content);
+    };
+
+    /**
+     * Reset the dialogue properties.
+     *
+     * This does not reset everything, just enough to reset the UI.
+     *
+     * @method _reset
+     */
+    ActionSelector.prototype._reset = function() {
+        this._popup = null;
+        this._selectedValue = '';
+    };
+
+    return /** @alias module:tool_lp/actionselector */ ActionSelector;
+
+});
diff --git a/admin/tool/lp/amd/src/competencies.js b/admin/tool/lp/amd/src/competencies.js
new file mode 100644 (file)
index 0000000..6e1973c
--- /dev/null
@@ -0,0 +1,338 @@
+// 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/>.
+
+/**
+ * Handle add/remove competency links.
+ *
+ * @module     tool_lp/competencies
+ * @package    tool_lp
+ * @copyright  2015 Damyon Wiese <damyon@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery',
+        'core/notification',
+        'core/ajax',
+        'core/templates',
+        'core/str',
+        'tool_lp/competencypicker',
+        'tool_lp/dragdrop-reorder'],
+       function($, notification, ajax, templates, str, Picker, dragdrop) {
+
+    /**
+     * Constructor
+     *
+     * @param {Number} itemid
+     * @param {String} itemtype
+     */
+    var competencies = function(itemid, itemtype, pagectxid) {
+        this.itemid = itemid;
+        this.itemtype = itemtype;
+        this.pageContextId = pagectxid;
+        this.pickerInstance = null;
+
+        $('[data-region="actions"] button').prop('disabled', false);
+        this.registerEvents();
+        this.registerDragDrop();
+    };
+
+    /**
+     * Initialise the drag/drop code.
+     * @method registerDragDrop
+     */
+    competencies.prototype.registerDragDrop = function() {
+        var localthis = this;
+        // Init this module.
+        str.get_string('movecompetency', 'tool_lp').done(
+            function(movestring) {
+                dragdrop.dragdrop('movecompetency',
+                                  movestring,
+                                  { identifier: 'movecompetency', component: 'tool_lp'},
+                                  { identifier: 'movecompetencyafter', component: 'tool_lp'},
+                                  'drag-samenode',
+                                  'drag-parentnode',
+                                  'drag-handlecontainer',
+                                  function(drag, drop) {
+                                      localthis.handleDrop.call(localthis, drag, drop);
+                                  });
+            }
+        ).fail(notification.exception);
+
+    };
+
+    /**
+     * Handle a drop from a drag/drop operation.
+     *
+     * @method handleDrop
+     * @param {DOMNode} drag The dragged node.
+     * @param {DOMNode} drop The dropped on node.
+     */
+    competencies.prototype.handleDrop = function(drag, drop) {
+        var fromid = $(drag).data('id');
+        var toid = $(drop).data('id');
+        var localthis = this;
+        var requests = [];
+
+        if (localthis.itemtype == 'course') {
+            requests = ajax.call([
+                {
+                    methodname: 'core_competency_reorder_course_competency',
+                    args: { courseid: localthis.itemid, competencyidfrom: fromid, competencyidto: toid }
+                }
+            ]);
+        } else if (localthis.itemtype == 'template') {
+            requests = ajax.call([
+                {
+                    methodname: 'core_competency_reorder_template_competency',
+                    args: { templateid: localthis.itemid, competencyidfrom: fromid, competencyidto: toid }
+                }
+            ]);
+        } else if (localthis.itemtype == 'plan') {
+            requests = ajax.call([
+                {
+                    methodname: 'core_competency_reorder_plan_competency',
+                    args: { planid: localthis.itemid, competencyidfrom: fromid, competencyidto: toid }
+                }
+            ]);
+        } else {
+            return null;
+        }
+
+        requests[0].fail(notification.exception);
+    };
+
+    /**
+     * Pick a competency
+     *
+     * @method pickCompetency
+     */
+    competencies.prototype.pickCompetency = function() {
+        var self = this;
+        var requests;
+        var pagerender;
+        var pageregion;
+        var pageContextIncludes;
+
+        if (!self.pickerInstance) {
+            if (self.itemtype === 'template' || self.itemtype === 'course') {
+                pageContextIncludes = 'parents';
+            }
+            self.pickerInstance = new Picker(self.pageContextId, false, pageContextIncludes);
+            self.pickerInstance.on('save', function(e, data) {
+                var compIds = data.competencyIds;
+
+                if (self.itemtype === "course") {
+                    requests = [];
+
+                    $.each(compIds, function(index, compId) {
+                        requests.push({
+                            methodname: 'core_competency_add_competency_to_course',
+                            args: { courseid: self.itemid, competencyid: compId }
+                        });
+                    });
+                    requests.push({
+                        methodname: 'tool_lp_data_for_course_competencies_page',
+                        args: { courseid: self.itemid }
+                    });
+
+                    pagerender = 'tool_lp/course_competencies_page';
+                    pageregion = 'coursecompetenciespage';
+
+                } else if (self.itemtype === "template") {
+                    requests = [];
+
+                    $.each(compIds, function(index, compId) {
+                        requests.push({
+                            methodname: 'core_competency_add_competency_to_template',
+                            args: { templateid: self.itemid, competencyid: compId }
+                        });
+                    });
+                    requests.push({
+                        methodname: 'tool_lp_data_for_template_competencies_page',
+                        args: { templateid: self.itemid, pagecontext: { contextid: self.pageContextId }}
+                    });
+                    pagerender = 'tool_lp/template_competencies_page';
+                    pageregion = 'templatecompetenciespage';
+                } else if (self.itemtype === "plan") {
+                    requests = [];
+
+                    $.each(compIds, function(index, compId) {
+                        requests.push({
+                            methodname: 'core_competency_add_competency_to_plan',
+                            args: { planid: self.itemid, competencyid: compId }
+                        });
+                    });
+                    requests.push({
+                         methodname: 'tool_lp_data_for_plan_page',
+                         args: { planid: self.itemid}
+                    });
+                    pagerender = 'tool_lp/plan_page';
+                    pageregion = 'plan-page';
+                }
+
+                ajax.call(requests)[requests.length - 1].then(function(context) {
+                    return templates.render(pagerender, context).done(function(html, js) {
+                        $('[data-region="' + pageregion + '"]').replaceWith(html);
+                        templates.runTemplateJS(js);
+                    });
+                }, notification.exception);
+            });
+        }
+
+        self.pickerInstance.display();
+    };
+
+    /**
+     * Delete the link between competency and course, template or plan. Reload the page.
+     *
+     * @method doDelete
+     * @param {int} deleteid The id of record to delete.
+     */
+    competencies.prototype.doDelete = function(deleteid) {
+        var localthis = this;
+        var requests = [],
+            pagerender = '',
+            pageregion = '';
+
+        // Delete the link and reload the page template.
+        if (localthis.itemtype == 'course') {
+            requests = ajax.call([
+                { methodname: 'core_competency_remove_competency_from_course',
+                    args: { courseid: localthis.itemid, competencyid: deleteid } },
+                { methodname: 'tool_lp_data_for_course_competencies_page',
+                    args: { courseid: localthis.itemid } }
+            ]);
+            pagerender = 'tool_lp/course_competencies_page';
+            pageregion = 'coursecompetenciespage';
+        } else if (localthis.itemtype == 'template') {
+            requests = ajax.call([
+                { methodname: 'core_competency_remove_competency_from_template',
+                    args: { templateid: localthis.itemid, competencyid: deleteid } },
+                { methodname: 'tool_lp_data_for_template_competencies_page',
+                    args: { templateid: localthis.itemid, pagecontext: { contextid: localthis.pageContextId } } }
+            ]);
+            pagerender = 'tool_lp/template_competencies_page';
+            pageregion = 'templatecompetenciespage';
+        } else if (localthis.itemtype == 'plan') {
+            requests = ajax.call([
+                { methodname: 'core_competency_remove_competency_from_plan',
+                    args: { planid: localthis.itemid, competencyid: deleteid } },
+                { methodname: 'tool_lp_data_for_plan_page',
+                    args: { planid: localthis.itemid } }
+            ]);
+            pagerender = 'tool_lp/plan_page';
+            pageregion = 'plan-page';
+        }
+
+        requests[1].done(function(context) {
+            templates.render(pagerender, context).done(function(html, js) {
+                $('[data-region="' + pageregion + '"]').replaceWith(html);
+                templates.runTemplateJS(js);
+            }).fail(notification.exception);
+        }).fail(notification.exception);
+
+    };
+
+    /**
+     * Show a confirm dialogue before deleting a competency.
+     *
+     * @method deleteHandler
+     * @param {int} deleteid The id of record to delete.
+     */
+    competencies.prototype.deleteHandler = function(deleteid) {
+        var localthis = this;
+        var requests = [];
+        var message;
+
+        if (localthis.itemtype == 'course') {
+            message = 'unlinkcompetencycourse';
+        } else if (localthis.itemtype == 'template') {
+            message = 'unlinkcompetencytemplate';
+        } else if (localthis.itemtype == 'plan') {
+            message = 'unlinkcompetencyplan';
+        } else {
+            return;
+        }
+
+        requests = ajax.call([{
+            methodname: 'core_competency_read_competency',
+            args: { id: deleteid }
+        }]);
+
+        requests[0].done(function(competency) {
+            str.get_strings([
+                { key: 'confirm', component: 'moodle' },
+                { key: message, component: 'tool_lp', param: competency.shortname },
+                { key: 'confirm', component: 'moodle' },
+                { key: 'cancel', component: 'moodle' }
+            ]).done(function (strings) {
+                notification.confirm(
+                    strings[0], // Confirm.
+                    strings[1], // Unlink the competency X from the course?
+                    strings[2], // Confirm.
+                    strings[3], // Cancel.
+                    function() {
+                        localthis.doDelete(deleteid);
+                    }
+                );
+            }).fail(notification.exception);
+        }).fail(notification.exception);
+    };
+
+    /**
+     * Register the javascript event handlers for this page.
+     *
+     * @method registerEvents
+     */
+    competencies.prototype.registerEvents = function() {
+        var localthis = this;
+
+        if (localthis.itemtype == 'course') {
+            // Course completion rule handling.
+            $('[data-region="coursecompetenciespage"]').on('change', 'select[data-field="ruleoutcome"]', function(e){
+                var requests = [];
+                var pagerender = 'tool_lp/course_competencies_page';
+                var pageregion = 'coursecompetenciespage';
+                var coursecompetencyid = $(e.target).data('id');
+                var ruleoutcome = $(e.target).val();
+                requests = ajax.call([
+                    { methodname: 'core_competency_set_course_competency_ruleoutcome',
+                      args: { coursecompetencyid: coursecompetencyid, ruleoutcome: ruleoutcome } },
+                    { methodname: 'tool_lp_data_for_course_competencies_page',
+                      args: { courseid: localthis.itemid } }
+                ]);
+
+                requests[1].done(function(context) {
+                    templates.render(pagerender, context).done(function(html, js) {
+                        $('[data-region="' + pageregion + '"]').replaceWith(html);
+                        templates.runTemplateJS(js);
+                    }).fail(notification.exception);
+                }).fail(notification.exception);
+            });
+        }
+
+        $('[data-region="actions"] button').click(function(e) {
+            e.preventDefault();
+            localthis.pickCompetency();
+        });
+        $('[data-action="delete-competency-link"]').click(function(e) {
+            e.preventDefault();
+
+            var deleteid = $(e.target).closest('[data-id]').data('id');
+            localthis.deleteHandler(deleteid);
+        });
+    };
+
+    return /** @alias module:tool_lp/competencies */ competencies;
+});
diff --git a/admin/tool/lp/amd/src/competency_outcomes.js b/admin/tool/lp/amd/src/competency_outcomes.js
new file mode 100644 (file)
index 0000000..1ff679e
--- /dev/null
@@ -0,0 +1,83 @@
+// 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/>.
+
+/**
+ * Competency rule config.
+ *
+ * @package    tool_lp
+ * @copyright  2015 Frédéric Massart - FMCorz.net
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery',
+        'core/str'],
+        function($, Str) {
+
+    var OUTCOME_NONE = 0,
+        OUTCOME_EVIDENCE = 1,
+        OUTCOME_COMPLETE = 2,
+        OUTCOME_RECOMMEND = 3;
+
+    return /** @alias module:tool_lp/competency_outcomes */ {
+
+        NONE: OUTCOME_NONE,
+        EVIDENCE: OUTCOME_EVIDENCE,
+        COMPLETE: OUTCOME_COMPLETE,
+        RECOMMEND: OUTCOME_RECOMMEND,
+
+        /**
+         * Get all the outcomes.
+         *
+         * @return {Object} Indexed by outcome code, contains code and name.
+         * @method getAll
+         */
+        getAll: function() {
+            var self = this;
+            return Str.get_strings([
+                { key: 'competencyoutcome_none', component: 'tool_lp' },
+                { key: 'competencyoutcome_evidence', component: 'tool_lp' },
+                { key: 'competencyoutcome_recommend', component: 'tool_lp' },
+                { key: 'competencyoutcome_complete', component: 'tool_lp' },
+            ]).then(function(strings) {
+                var outcomes = {};
+                outcomes[self.NONE] = { code: self.NONE, name: strings[0] };
+                outcomes[self.EVIDENCE] = { code: self.EVIDENCE, name: strings[1] };
+                outcomes[self.RECOMMEND] = { code: self.RECOMMEND, name: strings[2] };
+                outcomes[self.COMPLETE] = { code: self.COMPLETE, name: strings[3] };
+                return outcomes;
+            });
+        },
+
+        /**
+         * Get the string for an outcome.
+         *
+         * @param  {Number} id The outcome code.
+         * @return {Promise Resolved with the string.
+         * @method getString
+         */
+        getString: function(id) {
+            var self = this,
+                all = self.getAll();
+
+            return all.then(function(outcomes) {
+                if (typeof outcomes[id] === 'undefined') {
+                    return $.Deferred().reject().promise();
+                }
+                return outcomes[id].name;
+            });
+        }
+    };
+
+});
diff --git a/admin/tool/lp/amd/src/competency_rule.js b/admin/tool/lp/amd/src/competency_rule.js
new file mode 100644 (file)
index 0000000..35def4a
--- /dev/null
@@ -0,0 +1,175 @@
+// 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/>.
+
+/**
+ * Competency rule base module.
+ *
+ * @package    tool_lp
+ * @copyright  2015 Frédéric Massart - FMCorz.net
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery'], function($) {
+
+    /**
+     * Competency rule abstract class.
+     *
+     * Any competency rule should extend this object. The event 'change' should be
+     * triggered on the instance when the configuration has changed. This will allow
+     * the components using the rule to gather the config, or check its validity.
+     *
+     * this._triggerChange();
+     *
+     * @param {Tree} tree The competency tree.
+     */
+    var Rule = function(tree) {
+        this._eventNode = $('<div>');
+        this._ready = $.Deferred();
+        this._tree = tree;
+    };
+
+    /** @type {Object} The current competency. */
+    Rule.prototype._competency = null;
+    /** @type {Node} The node we attach the events to. */
+    Rule.prototype._eventNode = null;
+    /** @type {Promise} Resolved when the object is ready. */
+    Rule.prototype._ready = null;
+    /** @type {Tree} The competency tree. */
+    Rule.prototype._tree = null;
+
+    /**
+     * Whether or not the current competency can be configured using this rule.
+     *
+     * @return {Boolean}
+     * @method canConfig
+     */
+    Rule.prototype.canConfig = function() {
+        return this._tree.hasChildren(this._competency.id);
+    };
+
+    /**
+     * The config established by this rule.
+     *
+     * To override in subclasses when relevant.
+     *
+     * @return {String|null}
+     * @method getConfig
+     */
+    Rule.prototype.getConfig = function() {
+        return null;
+    };
+
+    /**
+     * Return the type of the module.
+     *
+     * @return {String}
+     * @method getType
+     */
+    Rule.prototype.getType = function() {
+        throw new Error('Not implemented');
+    };
+
+    /**
+     * The init process.
+     *
+     * Do not override this, instead override _load.
+     *
+     * @return {Promise} Revoled when the plugin is initialised.
+     * @method init
+     */
+    Rule.prototype.init = function() {
+        return this._load();
+    };
+
+    /**
+     * Callback to inject the template.
+     *
+     * @param  {Node} container Node to inject in.
+     * @return {Promise} Resolved when done.
+     * @method injectTemplate
+     */
+    Rule.prototype.injectTemplate = function() {
+        return $.Deferred().reject().promise();
+    };
+
+    /**
+     * Whether or not the current config is valid.
+     *
+     * Plugins should override this.
+     *
+     * @return {Boolean}
+     * @method _isValid
+     */
+    Rule.prototype.isValid = function() {
+        return false;
+    };
+
+    /**
+     * Load the class.
+     *
+     * @return {Promise}
+     * @method _load
+     * @protected
+     */
+    Rule.prototype._load = function() {
+        return $.when();
+    };
+
+    /**
+     * Register an event listener.
+     *
+     * @param {String} type The event type.
+     * @param {Function} handler The event listener.
+     * @method on
+     */
+    Rule.prototype.on = function(type, handler) {
+        this._eventNode.on(type, handler);
+    };
+
+    /**
+     * Sets the current competency.
+     *
+     * @param {Competency} competency
+     * @method setTargetCompetency
+     */
+    Rule.prototype.setTargetCompetency = function(competency) {
+        this._competency = competency;
+    };
+
+    /**
+     * Trigger an event.
+     *
+     * @param {String} type The type of event.
+     * @param {Object} The data to pass to the listeners.
+     * @method _trigger
+     * @protected
+     */
+    Rule.prototype._trigger = function(type, data) {
+        this._eventNode.trigger(type, [data]);
+    };
+
+    /**
+     * Trigger the change event.
+     *
+     * @method _triggerChange
+     * @protected
+     */
+    Rule.prototype._triggerChange = function() {
+        this._trigger('change', this);
+    };
+
+    return /** @alias module:tool_lp/competency_rule */ Rule;
+
+});
diff --git a/admin/tool/lp/amd/src/competency_rule_all.js b/admin/tool/lp/amd/src/competency_rule_all.js
new file mode 100644 (file)
index 0000000..db46330
--- /dev/null
@@ -0,0 +1,60 @@
+// 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/>.
+
+/**
+ * Competency rule all module.
+ *
+ * @package    tool_lp
+ * @copyright  2015 Frédéric Massart - FMCorz.net
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery',
+        'core/str',
+        'tool_lp/competency_rule',
+        ],
+        function($, Str, RuleBase) {
+
+    /**
+     * Competency rule all class.
+     */
+    var Rule = function() {
+        RuleBase.apply(this, arguments);
+    };
+    Rule.prototype = Object.create(RuleBase.prototype);
+
+    /**
+     * Return the type of the module.
+     *
+     * @return {String}
+     * @method getType
+     */
+    Rule.prototype.getType = function() {
+        return 'core_competency\\competency_rule_all';
+    };
+
+    /**
+     * Whether or not the current config is valid.
+     *
+     * @return {Boolean}
+     * @method isValid
+     */
+    Rule.prototype.isValid = function() {
+        return true;
+    };
+
+    return /** @alias module:tool_lp/competency_rule_all */ Rule;
+
+});
diff --git a/admin/tool/lp/amd/src/competency_rule_points.js b/admin/tool/lp/amd/src/competency_rule_points.js
new file mode 100644 (file)
index 0000000..470bb5d
--- /dev/null
@@ -0,0 +1,199 @@
+// 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/>.
+
+/**
+ * Competency rule points module.
+ *
+ * @package    tool_lp
+ * @copyright  2015 Frédéric Massart - FMCorz.net
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery',
+        'core/str',
+        'core/templates',
+        'tool_lp/competency_rule',
+        ],
+        function($, Str, Templates, RuleBase) {
+
+    /**
+     * Competency rule points class.
+     */
+    var Rule = function() {
+        RuleBase.apply(this, arguments);
+    };
+    Rule.prototype = Object.create(RuleBase.prototype);
+
+    /** @type {Node} Reference to the container in which the template was included. */
+    Rule.prototype._container = null;
+    /** @type {Boolean} Whether or not the template was included. */
+    Rule.prototype._templateLoaded = false;
+
+    /**
+     * The config established by this rule.
+     *
+     * @return {String}
+     * @method getConfig
+     */
+    Rule.prototype.getConfig = function() {
+        return JSON.stringify({
+            base: {
+                points: this._getRequiredPoints(),
+            },
+            competencies: this._getCompetenciesConfig()
+        });
+    };
+
+    /**
+     * Gathers the input provided by the user for competencies.
+     *
+     * @return {Array} Containing id, points and required.
+     * @method _getCompetenciesConfig
+     * @protected
+     */
+    Rule.prototype._getCompetenciesConfig = function() {
+        var competencies = [];
+
+        this._container.find('[data-competency]').each(function() {
+            var node = $(this),
+                id = node.data('competency'),
+                points = parseInt(node.find('[name="points"]').val(), 10),
+                required = node.find('[name="required"]').prop('checked');
+
+            competencies.push({
+                id: id,
+                points: points,
+                required: required ? 1 : 0
+            });
+        });
+
+        return competencies;
+    };
+
+    /**
+     * Fetches the required points set by the user.
+     *
+     * @return {Number}
+     * @method _getRequiredPoints
+     * @protected
+     */
+    Rule.prototype._getRequiredPoints = function() {
+        return parseInt(this._container.find('[name="requiredpoints"]').val() || 1, 10);
+    };
+
+    /**
+     * Return the type of the module.
+     *
+     * @return {String}
+     * @method getType
+     */
+    Rule.prototype.getType = function() {
+        return 'core_competency\\competency_rule_points';
+    };
+
+    /**
+     * Callback to inject the template.
+     *
+     * @param  {Node} container Node to inject in.
+     * @return {Promise} Resolved when done.
+     * @method injectTemplate
+     */
+    Rule.prototype.injectTemplate = function(container) {
+        var self = this,
+            children = this._tree.getChildren(this._competency.id),
+            context,
+            config = {
+                base: { points: 2 },
+                competencies: []
+            };
+
+        this._templateLoaded = false;
+
+        // Only pre-load the configuration when the competency is using this rule.
+        if (self._competency.ruletype == self.getType()) {
+            try {
+                config = JSON.parse(self._competency.ruleconfig);
+            } catch (e) {
+            }
+        }
+
+        context = {
+            requiredpoints: (config && config.base) ? config.base.points : 2,
+            competency: self._competency,
+            children: []
+        };
+
+        $.each(children, function(index, child) {
+            var competency = {
+                id: child.id,
+                shortname: child.shortname,
+                required: false,
+                points: 0
+            };
+
+            if (config) {
+                $.each(config.competencies, function(index, comp) {
+                    if (comp.id == competency.id) {
+                        competency.required = comp.required ? true : false;
+                        competency.points = comp.points;
+                    }
+                });
+            }
+
+            context.children.push(competency);
+        });
+
+        return Templates.render('tool_lp/competency_rule_points', context).then(function(html) {
+            self._container = container;
+            container.html(html);
+            container.find('input').change(function() {
+                self._triggerChange();
+            });
+
+            // We're done, let's trigger a change.
+            self._templateLoaded = true;
+            self._triggerChange();
+        });
+    };
+
+    /**
+     * Whether or not the current config is valid.
+     *
+     * @return {Boolean}
+     * @method isValid
+     */
+    Rule.prototype.isValid = function() {
+        if (!this._templateLoaded) {
+            return false;
+        }
+
+        var required = this._getRequiredPoints(),
+            max = 0,
+            valid = true;
+
+        $.each(this._getCompetenciesConfig(), function(index, competency) {
+            if (competency.points < 0) {
+                valid = false;
+            }
+            max += competency.points;
+        });
+
+        valid = valid && max >= required;
+        return valid;
+    };
+
+    return /** @alias module:tool_lp/competency_rule_all */ Rule;
+
+});
diff --git a/admin/tool/lp/amd/src/competencyactions.js b/admin/tool/lp/amd/src/competencyactions.js
new file mode 100644 (file)
index 0000000..2c1c4c3
--- /dev/null
@@ -0,0 +1,861 @@
+// 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/>.
+
+/**
+ * Handle selection changes and actions on the competency tree.
+ *
+ * @module     tool_lp/competencyactions
+ * @package    tool_lp
+ * @copyright  2015 Damyon Wiese <damyon@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery',
+        'core/url',
+        'core/templates',
+        'core/notification',
+        'core/str',
+        'core/ajax',
+        'tool_lp/dragdrop-reorder',
+        'tool_lp/tree',
+        'tool_lp/dialogue',
+        'tool_lp/menubar',
+        'tool_lp/competencypicker',
+        'tool_lp/competency_outcomes',
+        'tool_lp/competencyruleconfig'],
+       function($, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig) {
+
+    // Private variables and functions.
+    /** @var {Object} treeModel - This is an object representing the nodes in the tree. */
+    var treeModel = null;
+    /** @var {Node} moveSource - The start of a drag operation */
+    var moveSource = null;
+    /** @var {Node} moveTarget - The end of a drag operation */
+    var moveTarget = null;
+    /** @var {Number} pageContextId The page context ID. */
+    var pageContextId;
+    /** @type {Object} Picker instance. */
+    var pickerInstance;
+    /** @type {Object} Rule config instance. */
+    var ruleConfigInstance;
+    /** @type {Object} The competency we're picking a relation to. */
+    var relatedTarget;
+    /** @type {Object} Taxonomy constants indexed per level. */
+    var taxonomiesConstants;
+    /** @type {Array} The rules modules. Values are object containing type, namd and amd. */
+    var rulesModules;
+    /** @type {Number} the selected competency ID. */
+    var selectedCompetencyId = null;
+
+    /**
+     * Respond to choosing the "Add" menu item for the selected node in the tree.
+     * @method addHandler
+     */
+    var addHandler = function() {
+        var parent = $('[data-region="competencyactions"]').data('competency');
+
+        var params = {
+            competencyframeworkid : treeModel.getCompetencyFrameworkId(),
+            pagecontextid: pageContextId
+        };
+
+        if (parent !== null) {
+            // We are adding at a sub node.
+            params.parentid = parent.id;
+        }
+
+        var relocate = function() {
+            var queryparams = $.param(params);
+            window.location = url.relativeUrl('/admin/tool/lp/editcompetency.php?' + queryparams);
+        };
+
+        if (parent !== null && treeModel.hasRule(parent.id)) {
+            str.get_strings([
+                { key: 'confirm', component: 'moodle' },
+                { key: 'addingcompetencywillresetparentrule', component: 'tool_lp', param: parent.shortname },
+                { key: 'yes', component: 'core' },
+                { key: 'no', component: 'core' }
+            ]).done(function (strings) {
+                notification.confirm(
+                    strings[0],
+                    strings[1],
+                    strings[2],
+                    strings[3],
+                    relocate
+                );
+            }).fail(notification.exception);
+        } else {
+            relocate();
+        }
+    };
+
+    /**
+     * A source and destination has been chosen - so time to complete a move.
+     * @method doMove
+     */
+    var doMove = function() {
+        var frameworkid = $('[data-region="filtercompetencies"]').data('frameworkid');
+        var requests = ajax.call([{
+            methodname: 'core_competency_set_parent_competency',
+            args: { competencyid: moveSource, parentid: moveTarget }
+        }, {
+            methodname: 'tool_lp_data_for_competencies_manage_page',
+            args: { competencyframeworkid: frameworkid,
+                    search: $('[data-region="filtercompetencies"] input').val() }
+        }]);
+        requests[1].done(reloadPage).fail(notification.exception);
+    };
+
+    /**
+     * Confirms a competency move.
+     *
+     * @method confirmMove
+     */
+    var confirmMove = function() {
+        moveTarget = typeof moveTarget === "undefined" ? 0 : moveTarget;
+        if (moveTarget == moveSource) {
+            // No move to do.
+            return;
+        }
+
+        var targetComp = treeModel.getCompetency(moveTarget) || {},
+            sourceComp = treeModel.getCompetency(moveSource) || {},
+            confirmMessage = 'movecompetencywillresetrules',
+            showConfirm = false;
+
+        // We shouldn't be moving the competency to the same parent.
+        if (sourceComp.parentid == moveTarget) {
+            return;
+        }
+
+        // If we are moving to a child of self.
+        if (targetComp.path && targetComp.path.indexOf('/' + sourceComp.id + '/') >= 0) {
+            confirmMessage = 'movecompetencytochildofselfwillresetrules';
+
+            // Show a confirmation if self has rules, as they'll disappear.
+            showConfirm = showConfirm || treeModel.hasRule(sourceComp.id);
+        }
+
+        // Show a confirmation if the current parent, or the destination have rules.
+        showConfirm = showConfirm || (treeModel.hasRule(targetComp.id) || treeModel.hasRule(sourceComp.parentid));
+
+        // Show confirm, and/or do the things.
+        if (showConfirm) {
+            str.get_strings([
+                { key: 'confirm', component: 'moodle' },
+                { key: confirmMessage, component: 'tool_lp' },
+                { key: 'yes', component: 'moodle' },
+                { key: 'no', component: 'moodle' }
+            ]).done(function (strings) {
+                notification.confirm(
+                    strings[0], // Confirm.
+                    strings[1], // Delete competency X?
+                    strings[2], // Delete.
+                    strings[3], // Cancel.
+                    doMove
+                );
+            }).fail(notification.exception);
+
+        } else {
+            doMove();
+        }
+    };
+
+    /**
+     * A move competency popup was opened - initialise the aria tree in it.
+     * @method initMovePopup
+     * @param {dialogue} popup The tool_lp/dialogue that was created.
+     */
+    var initMovePopup = function(popup) {
+        var body = $(popup.getContent());
+        var treeRoot = body.find('[data-enhance=movetree]');
+        var tree = new Ariatree(treeRoot, false);
+        tree.on('selectionchanged', function(evt, params) {
+            var target = params.selected;
+            moveTarget = $(target).data('id');
+        });
+        treeRoot.show();
+
+        body.on('click', '[data-action="move"]', function() { popup.close(); confirmMove(); });
+        body.on('click', '[data-action="cancel"]', function() { popup.close(); });
+    };
+
+    /**
+     * Turn a flat list of competencies into a tree structure (recursive).
+     * @method addCompetencyChildren
+     * @param {Object} parent The current parent node in the tree
+     * @param {Object[]} competencies The flat list of competencies
+     */
+    var addCompetencyChildren = function(parent, competencies) {
+        var i;
+
+        for (i = 0; i < competencies.length; i++) {
+            if (competencies[i].parentid == parent.id) {
+                parent.haschildren = true;
+                competencies[i].children = [];
+                competencies[i].haschildren = false;
+                parent.children[parent.children.length] = competencies[i];
+                addCompetencyChildren(competencies[i], competencies);
+            }
+        }
+    };
+
+    /**
+     * A node was chosen and "Move" was selected from the menu. Open a popup to select the target.
+     * @method moveHandler
+     */
+    var moveHandler = function(e) {
+        e.preventDefault();
+        var competency = $('[data-region="competencyactions"]').data('competency');
+
+        // Remember what we are moving.
+        moveSource = competency.id;
+
+        // Load data for the template.
+        var requests = ajax.call([
+            {
+                methodname: 'core_competency_search_competencies',
+                args: {
+                    competencyframeworkid: competency.competencyframeworkid,
+                    searchtext: ''
+                }
+            },{
+                methodname: 'core_competency_read_competency_framework',
+                args: {
+                    id: competency.competencyframeworkid
+                }
+            }
+        ]);
+
+        // When all data has arrived, continue.
+        $.when.apply(null, requests).done(function(competencies, framework) {
+
+            // Expand the list of competencies into a tree.
+            var i, competenciestree = [];
+            for (i = 0; i < competencies.length; i++) {
+                var onecompetency = competencies[i];
+                if (onecompetency.parentid == "0") {
+                    onecompetency.children = [];
+                    onecompetency.haschildren = 0;
+                    competenciestree[competenciestree.length] = onecompetency;
+                    addCompetencyChildren(onecompetency, competencies);
+                }
+            }
+
+            str.get_strings([
+                { key: 'movecompetency', component: 'tool_lp', param: competency.shortname },
+                { key: 'move', component: 'tool_lp' },
+                { key: 'cancel', component: 'moodle' }
+            ]).done(function (strings) {
+
+                var context = {
+                    framework: framework,
+                    competencies: competenciestree
+                };
+
+                templates.render('tool_lp/competencies_move_tree', context)
+                   .done(function(tree) {
+                       new Dialogue(
+                           strings[0], // Move competency x.
+                           tree, // The move tree.
+                           initMovePopup
+                       );
+
+                   }).fail(notification.exception);
+
+           }).fail(notification.exception);
+
+        }).fail(notification.exception);
+
+    };
+
+    /**
+     * Edit the selected competency.
+     * @method editHandler
+     */
+    var editHandler = function() {
+        var competency = $('[data-region="competencyactions"]').data('competency');
+
+        var params = {
+            competencyframeworkid : treeModel.getCompetencyFrameworkId(),
+            id : competency.id,
+            parentid: competency.parentid,
+            pagecontextid: pageContextId
+        };
+
+        var queryparams = $.param(params);
+        window.location = url.relativeUrl('/admin/tool/lp/editcompetency.php?' + queryparams);
+    };
+
+    /**
+     * Re-render the page with the latest data.
+     * @method reloadPage
+     */
+    var reloadPage = function(context) {
+        templates.render('tool_lp/manage_competencies_page', context)
+            .done(function(newhtml, newjs) {
+                $('[data-region="managecompetencies"]').replaceWith(newhtml);
+                templates.runTemplateJS(newjs);
+            })
+           .fail(notification.exception);
+    };
+
+    /**
+     * Perform a search and render the page with the new search results.
+     * @method updateSearchHandler
+     */
+    var updateSearchHandler = function(e) {
+        e.preventDefault();
+
+        var frameworkid = $('[data-region="filtercompetencies"]').data('frameworkid');
+
+        var requests = ajax.call([{
+            methodname: 'tool_lp_data_for_competencies_manage_page',
+            args: { competencyframeworkid: frameworkid,
+                    search: $('[data-region="filtercompetencies"] input').val() }
+        }]);
+        requests[0].done(reloadPage).fail(notification.exception);
+    };
+
+    /**
+     * Move a competency "up". This only affects the sort order within the same branch of the tree.
+     * @method moveUpHandler
+     */
+    var moveUpHandler = function() {
+        // We are chaining ajax requests here.
+        var competency = $('[data-region="competencyactions"]').data('competency');
+        var requests = ajax.call([{
+            methodname: 'core_competency_move_up_competency',
+            args: { id: competency.id }
+        }, {
+            methodname: 'tool_lp_data_for_competencies_manage_page',
+            args: { competencyframeworkid: competency.competencyframeworkid,
+                    search: $('[data-region="filtercompetencies"] input').val() }
+        }]);
+        requests[1].done(reloadPage).fail(notification.exception);
+    };
+
+    /**
+     * Move a competency "down". This only affects the sort order within the same branch of the tree.
+     * @method moveDownHandler
+     */
+    var moveDownHandler = function() {
+        // We are chaining ajax requests here.
+        var competency = $('[data-region="competencyactions"]').data('competency');
+        var requests = ajax.call([{
+            methodname: 'core_competency_move_down_competency',
+            args: { id: competency.id }
+        }, {
+            methodname: 'tool_lp_data_for_competencies_manage_page',
+            args: { competencyframeworkid: competency.competencyframeworkid,
+                    search: $('[data-region="filtercompetencies"] input').val() }
+        }]);
+        requests[1].done(reloadPage).fail(notification.exception);
+    };
+
+    /**
+     * Open a dialogue to show all the courses using the selected competency.
+     * @method seeCoursesHandler
+     */
+    var seeCoursesHandler = function() {
+        var competency = $('[data-region="competencyactions"]').data('competency');
+
+        var requests = ajax.call([{
+            methodname: 'tool_lp_list_courses_using_competency',
+            args: { id: competency.id }
+        }]);
+
+        requests[0].done(function(courses) {
+            var context = {
+                courses: courses
+            };
+            templates.render('tool_lp/linked_courses_summary', context).done(function(html) {
+                str.get_string('linkedcourses', 'tool_lp').done(function (linkedcourses) {
+                    new Dialogue(
+                        linkedcourses, // Title.
+                        html, // The linked courses.
+                        initMovePopup
+                    );
+                }).fail(notification.exception);
+            }).fail(notification.exception);
+        }).fail(notification.exception);
+    };
+
+    /**
+     * Open a competencies popup to relate competencies.
+     *
+     * @method relateCompetenciesHandler
+     */
+    var relateCompetenciesHandler = function() {
+        relatedTarget = $('[data-region="competencyactions"]').data('competency');
+
+        if (!pickerInstance) {
+            pickerInstance = new Picker(pageContextId, relatedTarget.competencyframeworkid);
+            pickerInstance.on('save', function(e, data) {
+                var compIds = data.competencyIds;
+
+                var calls = [];
+                $.each(compIds, function(index, value) {
+                    calls.push({
+                        methodname: 'core_competency_add_related_competency',
+                        args: { competencyid: value, relatedcompetencyid: relatedTarget.id }
+                    });
+                });
+
+                calls.push( {
+                    methodname: 'tool_lp_data_for_related_competencies_section',
+                    args: { competencyid: relatedTarget.id }
+                });
+
+                var promises = ajax.call(calls);
+
+                promises[calls.length - 1].then(function(context) {
+                    return templates.render('tool_lp/related_competencies', context).done(function(html, js) {
+                        $('[data-region="relatedcompetencies"]').replaceWith(html);
+                        templates.runTemplateJS(js);
+                        updatedRelatedCompetencies();
+                    });
+                }, notification.exception);
+            });
+        }
+
+        pickerInstance.setDisallowedCompetencyIDs([relatedTarget.id]);
+        pickerInstance.display();
+    };
+
+    var ruleConfigHandler = function(e) {
+        e.preventDefault();
+        relatedTarget = $('[data-region="competencyactions"]').data('competency');
+        ruleConfigInstance.setTargetCompetencyId(relatedTarget.id);
+        ruleConfigInstance.display();
+    };
+
+    var ruleConfigSaveHandler = function(e, config) {
+        var update = {
+            id: relatedTarget.id,
+            shortname: relatedTarget.shortname,
+            idnumber: relatedTarget.idnumber,
+            description: relatedTarget.description,
+            descriptionformat: relatedTarget.descriptionformat,
+            ruletype: config.ruletype,
+            ruleoutcome: config.ruleoutcome,
+            ruleconfig: config.ruleconfig
+        };
+        var promise = ajax.call([{
+            methodname: 'core_competency_update_competency',
+            args: { competency: update }
+        }]);
+        promise[0].then(function(result) {
+            if (result) {
+                relatedTarget.ruletype = config.ruletype;
+                relatedTarget.ruleoutcome = config.ruleoutcome;
+                relatedTarget.ruleconfig = config.ruleconfig;
+                renderCompetencySummary(relatedTarget);
+            }
+        }, notification.exception);
+    };
+
+    /**
+     * Delete a competency.
+     * @method doDelete
+     */
+    var doDelete = function() {
+        // We are chaining ajax requests here.
+        var competency = $('[data-region="competencyactions"]').data('competency');
+        var requests = ajax.call([{
+            methodname: 'core_competency_delete_competency',
+            args: { id: competency.id }
+        }, {
+            methodname: 'tool_lp_data_for_competencies_manage_page',
+            args: { competencyframeworkid: competency.competencyframeworkid,
+                    search: $('[data-region="filtercompetencies"] input').val() }
+        }]);
+        requests[0].done(function(success) {
+            if (success === false) {
+                str.get_strings([
+                { key: 'competencycannotbedeleted', component: 'tool_lp', param: competency.shortname },
+                { key: 'cancel', component: 'moodle' }
+                ]).done(function (strings) {
+                    notification.alert(
+                        null,
+                        strings[0]
+                    );
+                }).fail(notification.exception);
+            }
+        }).fail(notification.exception);
+        requests[1].done(reloadPage).fail(notification.exception);
+    };
+
+    /**
+     * Show a confirm dialogue before deleting a competency.
+     * @method deleteCompetencyHandler
+     */
+    var deleteCompetencyHandler = function() {
+        var competency = $('[data-region="competencyactions"]').data('competency'),
+            confirmMessage = 'deletecompetency';
+
+        if (treeModel.hasRule(competency.parentid)) {
+            confirmMessage = 'deletecompetencyparenthasrule';
+        }
+
+        str.get_strings([
+            { key: 'confirm', component: 'moodle' },
+            { key: confirmMessage, component: 'tool_lp', param: competency.shortname },
+            { key: 'delete', component: 'moodle' },
+            { key: 'cancel', component: 'moodle' }
+        ]).done(function (strings) {
+            notification.confirm(
+                strings[0], // Confirm.
+                strings[1], // Delete competency X?
+                strings[2], // Delete.
+                strings[3], // Cancel.
+                doDelete
+            );
+        }).fail(notification.exception);
+    };
+
+    /**
+     * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
+     * @method dragStart
+     */
+    var dragStart = function(e) {
+        e.originalEvent.dataTransfer.setData('text', $(e.target).parent().data('id'));
+    };
+
+    /**
+     * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
+     * @method allowDrop
+     */
+    var allowDrop = function(e) {
+        e.originalEvent.dataTransfer.dropEffect = 'move';
+        e.preventDefault();
+    };
+
+    /**
+     * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
+     * @method dragEnter
+     */
+    var dragEnter = function(e) {
+        e.preventDefault();
+        $(this).addClass('currentdragtarget');
+    };
+
+    /**
+     * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
+     * @method dragLeave
+     */
+    var dragLeave = function(e) {
+        e.preventDefault();
+        $(this).removeClass('currentdragtarget');
+    };
+
+    /**
+     * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
+     * @method dropOver
+     */
+    var dropOver = function(e) {
+        e.preventDefault();
+        moveSource = e.originalEvent.dataTransfer.getData('text');
+        moveTarget = $(e.target).parent().data('id');
+        $(this).removeClass('currentdragtarget');
+
+        confirmMove();
+    };
+
+    /**
+     * Deletes a related competency without confirmation.
+     *
+     * @param {Event} e The event that triggered the action.
+     * @method deleteRelatedHandler
+     */
+    var deleteRelatedHandler = function(e) {
+        e.preventDefault();
+
+        var relatedid = this.id.substr(11);
+        var competency = $('[data-region="competencyactions"]').data('competency');
+        var removeRelated = ajax.call([
+            { methodname: 'core_competency_remove_related_competency',
+              args: { relatedcompetencyid: relatedid, competencyid: competency.id } },
+            { methodname: 'tool_lp_data_for_related_competencies_section',
+              args: { competencyid: competency.id } }
+        ]);
+
+        removeRelated[1].done(function(context) {
+            templates.render('tool_lp/related_competencies', context).done(function(html) {
+                $('[data-region="relatedcompetencies"]').replaceWith(html);
+                updatedRelatedCompetencies();
+            }.bind(this)).fail(notification.exception);
+        }.bind(this)).fail(notification.exception);
+    };
+
+    /**
+     * Updates the competencies list (with relations) and add listeners.
+     *
+     * @method updatedRelatedCompetencies
+     */
+    var updatedRelatedCompetencies = function() {
+
+        // Listeners to newly loaded related competencies.
+        $('[data-action="deleterelation"]').on('click', deleteRelatedHandler);
+
+    };
+
+    /**
+     * Log the competency viewed event.
+     *
+     * @param  {Object} The competency.
+     * @method triggerCompetencyViewedEvent
+     */
+    var triggerCompetencyViewedEvent = function(competency) {
+        if (competency.id !== selectedCompetencyId) {
+            // Set the selected competency id.
+            selectedCompetencyId = competency.id;
+            ajax.call([{
+                    methodname: 'core_competency_competency_viewed',
+                    args: { id: competency.id }
+            }]);
+        }
+    };
+
+    /**
+     * Return if the level has a sub level.
+     *
+     * @param  {Number} level The level.
+     * @return {Boolean}
+     * @function hasSubLevel
+     */
+    var hasSubLevel = function(level) {
+        return typeof taxonomiesConstants[level + 1] !== 'undefined';
+    };
+
+    /**
+     * Return the taxonomy constant for a level.
+     *
+     * @param  {Number} level The level.
+     * @return {String}
+     * @function getTaxonomyAtLevel
+     */
+    var getTaxonomyAtLevel = function(level) {
+        var constant = taxonomiesConstants[level];
+        if (!constant) {
+            constant = 'competency';
+        }
+        return constant;
+    };
+
+    /**
+     * Render the competency summary.
+     *
+     * @param  {Object} competency The competency.
+     */
+    var renderCompetencySummary = function(competency) {
+        var promise = $.Deferred().resolve().promise(),
+            context = {};
+
+        context.competency = competency;
+        context.showdeleterelatedaction = true;
+        context.showrelatedcompetencies = true;
+        context.showrule = false;
+
+        if (competency.ruleoutcome != Outcomes.NONE) {
+            // Get the outcome and rule name.
+            promise = Outcomes.getString(competency.ruleoutcome).then(function(str) {
+                var name;
+                $.each(rulesModules, function(index, modInfo) {
+                    if (modInfo.type == competency.ruletype) {
+                        name = modInfo.name;
+                    }
+                });
+                return [str, name];
+            });
+        }
+
+        promise.then(function(strs) {
+            if (typeof strs !== 'undefined') {
+                context.showrule = true;
+                context.rule = {
+                    outcome: strs[0],
+                    type: strs[1]
+                };
+            }
+        }).then(function() {
+            return templates.render('tool_lp/competency_summary', context).then(function(html) {
+                $('[data-region="competencyinfo"]').html(html);
+                $('[data-action="deleterelation"]').on('click', deleteRelatedHandler);
+            });
+        }).then(function() {
+            return templates.render('tool_lp/loading', {});
+        }).then(function(html, js) {
+            templates.replaceNodeContents('[data-region="relatedcompetencies"]', html, js);
+        }).done(function() {
+            ajax.call([{
+                methodname: 'tool_lp_data_for_related_competencies_section',
+                args: { competencyid: competency.id },
+                done: function(context) {
+                    return templates.render('tool_lp/related_competencies', context).done(function(html, js) {
+                        $('[data-region="relatedcompetencies"]').replaceWith(html);
+                        templates.runTemplateJS(js);
+                        updatedRelatedCompetencies();
+                    });
+                }
+            }]);
+        }).fail(notification.exception);
+    };
+
+    /**
+     * Return the string "Add <taxonomy>".
+     *
+     * @param  {Number} level The level.
+     * @return {String}
+     * @function strAddTaxonomy
+     */
+    var strAddTaxonomy = function(level) {
+        return str.get_string('taxonomy_add_' + getTaxonomyAtLevel(level), 'tool_lp');
+    };
+
+    /**
+     * Return the string "Selected <taxonomy>".
+     *
+     * @param  {Number} level The level.
+     * @return {String}
+     * @function strSelectedTaxonomy
+     */
+    var strSelectedTaxonomy = function(level) {
+        return str.get_string('taxonomy_selected_' + getTaxonomyAtLevel(level), 'tool_lp');
+    };
+
+    /**
+     * Handler when a node in the aria tree is selected.
+     * @method selectionChanged
+     * @param {Event} evt The event that triggered the selection change.
+     * @param {Object} params The parameters for the event. Contains a list of selected nodes.
+     */
+    var selectionChanged = function(evt, params) {
+        var node = params.selected,
+            id = $(node).data('id'),
+            btn = $('[data-region="competencyactions"] [data-action="add"]'),
+            actionMenu = $('[data-region="competencyactionsmenu"]'),
+            selectedTitle = $('[data-region="selected-competency"]'),
+            level = 0,
+            sublevel = 1;
+
+        menubar.closeAll();
+
+        if (typeof id === "undefined") {
+            // Assume this is the root of the tree.
+            // Here we are only getting the text from the top of the tree, to do it we clone the tree,
+            // remove all children and then call text on the result.
+            $('[data-region="competencyinfo"]').html(node.clone().children().remove().end().text());
+            $('[data-region="competencyactions"]').data('competency', null);
+            actionMenu.hide();
+
+        } else {
+            var competency = treeModel.getCompetency(id);
+
+            level = treeModel.getCompetencyLevel(id);
+            if (!hasSubLevel(level)) {
+                sublevel = false;
+            } else {
+                sublevel = level + 1;
+            }
+
+            actionMenu.show();
+            $('[data-region="competencyactions"]').data('competency', competency);
+            renderCompetencySummary(competency);
+            // Log Competency viewed event.
+            triggerCompetencyViewedEvent(competency);
+        }
+
+        strSelectedTaxonomy(level).then(function(str) {
+            selectedTitle.text(str);
+        });
+
+        if (!sublevel) {
+            btn.hide();
+        } else {
+            strAddTaxonomy(sublevel).then(function(str) {
+                btn.show()
+                    .find('[data-region="term"]')
+                    .text(str);
+            });
+        }
+        // We handled this event so consume it.
+        evt.preventDefault();
+        return false;
+    };
+
+    /**
+     * Return the string "Selected <taxonomy>".
+     *
+     * @function parseTaxonomies
+     * @param  {String} Comma separated list of taxonomies.
+     * @return {Array} of level => taxonomystr
+     */
+    var parseTaxonomies = function(taxonomiesstr) {
+        var all = taxonomiesstr.split(',');
+        all.unshift("");
+        delete all[0];
+
+        // Note we don't need to fill holes, because other functions check for empty anyway.
+        return all;
+    };
+
+    return {
+        /**
+         * Initialise this page (attach event handlers etc).
+         *
+         * @method init
+         * @param {Object} model The tree model provides some useful functions for loading and searching competencies.
+         * @param {Number} pagectxid The page context ID.
+         * @param {Object} taxonomies Constants indexed by level.
+         * @param {Object} rulesMods The modules of the rules.
+         */
+        init: function(model, pagectxid, taxonomies, rulesMods) {
+            treeModel = model;
+            pageContextId = pagectxid;
+            taxonomiesConstants = parseTaxonomies(taxonomies);
+            rulesModules = rulesMods;
+
+            $('[data-region="competencyactions"] [data-action="add"]').on('click', addHandler);
+
+            menubar.enhance('.competencyactionsmenu', {
+                '[data-action="edit"]': editHandler,
+                '[data-action="delete"]': deleteCompetencyHandler,
+                '[data-action="move"]': moveHandler,
+                '[data-action="moveup"]': moveUpHandler,
+                '[data-action="movedown"]': moveDownHandler,
+                '[data-action="linkedcourses"]': seeCoursesHandler,
+                '[data-action="relatedcompetencies"]': relateCompetenciesHandler.bind(this),
+                '[data-action="competencyrules"]': ruleConfigHandler.bind(this)
+            });
+            $('[data-region="competencyactionsmenu"]').hide();
+            $('[data-region="competencyactions"] [data-action="add"]').hide();
+
+            $('[data-region="filtercompetencies"]').on('submit', updateSearchHandler);
+            // Simple html5 drag drop because we already added an accessible alternative.
+            var top = $('[data-region="managecompetencies"] [data-enhance="tree"]');
+            top.on('dragstart', 'li>span', dragStart)
+                .on('dragover', 'li>span', allowDrop)
+                .on('dragenter', 'li>span', dragEnter)
+                .on('dragleave', 'li>span', dragLeave)
+                .on('drop', 'li>span', dropOver);
+
+            model.on('selectionchanged', selectionChanged);
+
+            // Prepare the configuration tool.
+            ruleConfigInstance = new RuleConfig(treeModel, rulesModules);
+            ruleConfigInstance.on('save', ruleConfigSaveHandler.bind(this));
+        }
+    };
+});
diff --git a/admin/tool/lp/amd/src/competencydialogue.js b/admin/tool/lp/amd/src/competencydialogue.js
new file mode 100644 (file)
index 0000000..747b9b0
--- /dev/null
@@ -0,0 +1,175 @@
+// 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/>.
+
+/**
+ * Display Competency in dialogue box.
+ *
+ * @module     tool_lp/Competencydialogue
+ * @package    tool_lp
+ * @copyright  2015 Issam Taboubi <issam.taboubi@umontreal.ca>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery',
+        'core/notification',
+        'core/ajax',
+        'core/templates',
+        'core/str',
+        'tool_lp/dialogue'],
+       function($, notification, ajax, templates, str, Dialogue) {
+
+    /**
+     * Constructor for CompetencyDialogue.
+     *
+     * @param {Object} options
+     *
+     */
+    var Competencydialogue = function(options) {
+        this.options = {
+            includerelated: false,
+            includecourses: false
+        };
+        $.extend(this.options, options);
+    };
+
+    /**
+     * Log the competency viewed event.
+     *
+     * @param  {Number} The competency ID.
+     * @method triggerCompetencyViewedEvent
+     */
+    Competencydialogue.prototype.triggerCompetencyViewedEvent = function(competencyId) {
+        ajax.call([{
+                methodname: 'core_competency_competency_viewed',
+                args: { id: competencyId }
+        }]);
+    };
+
+    /**
+     * Callback on dialogue display, it apply enhance on competencies dialogue.
+     *
+     * @param {Dialogue} dialogue
+     * @method enhanceDialogue
+     */
+    Competencydialogue.prototype.enhanceDialogue = function(dialogue) {
+        //Apply watch on the related competencies and competencies in the dialogue.
+        var comprelated = new Competencydialogue({includerelated : false});
+        comprelated.watch(dialogue.getContent());
+    };
+
+    /**
+     * Display a dialogue box by competencyid.
+     *
+     * @param {Number} the competency id
+     * @param {Object} Options for tool_lp_data_for_competency_summary service
+     * @param {Object} dataSource data to be used to display dialogue box
+     * @method showDialogue
+     */
+    Competencydialogue.prototype.showDialogue = function(competencyid) {
+
+        var datapromise = this.getCompetencyDataPromise(competencyid);
+        var localthis = this;
+        datapromise.done(function(data) {
+            // Inner Html in the dialogue content.
+            templates.render('tool_lp/competency_summary', data)
+                .done(function(html) {
+                    // Log competency viewed event.
+                    localthis.triggerCompetencyViewedEvent(competencyid);
+
+                    // Show the dialogue.
+                    new Dialogue(
+                        data.competency.shortname,
+                        html,
+                        localthis.enhanceDialogue
+                    );
+                }).fail(notification.exception);
+        }).fail(notification.exception);
+    };
+
+    /**
+     * Display a dialogue box from data.
+     *
+     * @param {Object} dataSource data to be used to display dialogue box
+     * @method showDialogueFromData
+     */
+    Competencydialogue.prototype.showDialogueFromData = function(dataSource) {
+
+        var localthis = this;
+        // Inner Html in the dialogue content.
+        templates.render('tool_lp/competency_summary', dataSource)
+            .done(function(html) {
+                // Log competency viewed event.
+                localthis.triggerCompetencyViewedEvent(dataSource.id);
+
+                // Show the dialogue.
+                new Dialogue(
+                    dataSource.shortname,
+                    html,
+                    localthis.enhanceDialogue
+                );
+            }).fail(notification.exception);
+    };
+
+    /**
+     * The action on the click event.
+     *
+     * @param {Event} event click
+     * @method clickEventHandler
+     */
+    Competencydialogue.prototype.clickEventHandler = function(e) {
+
+        var compdialogue = e.data.compdialogue;
+        var competencyid = $(e.currentTarget).data('id');
+
+        // Show the dialogue box.
+        compdialogue.showDialogue(competencyid);
+        e.preventDefault();
+    };
+
+    /**
+     * Get a promise on data competency.
+     *
+     * @param {Number} competencyid
+     * @return {Promise} return promise on data request
+     * @method getCompetencyDataPromise
+     */
+    Competencydialogue.prototype.getCompetencyDataPromise = function(competencyid) {
+
+        var requests = ajax.call([
+            { methodname: 'tool_lp_data_for_competency_summary',
+              args: { competencyid: competencyid,
+                      includerelated: this.options.includerelated,
+                      includecourses: this.options.includecourses
+                    }
+            }
+        ]);
+
+        return requests[0].then(function(context) {
+           return context;
+        }).fail(notification.exception);
+    };
+
+    /**
+     * Watch the competencies links in container.
+     *
+     * @param {String} container selector of node containing competencies links
+     * @method watch
+     */
+    Competencydialogue.prototype.watch = function(containerSelector) {
+        $(containerSelector).off('click', '[data-action="competency-dialogue"]', this.clickEventHandler);
+        $(containerSelector).on('click', '[data-action="competency-dialogue"]', { compdialogue: this }, this.clickEventHandler);
+    };
+
+    return Competencydialogue;
+});
diff --git a/admin/tool/lp/amd/src/competencypicker.js b/admin/tool/lp/amd/src/competencypicker.js
new file mode 100644 (file)
index 0000000..d6247fe
--- /dev/null
@@ -0,0 +1,460 @@
+// 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/>.
+
+/**
+ * Competency picker.
+ *
+ * To handle 'save' events use: picker.on('save')
+ * This will receive a object with either a single 'competencyId', or an array in 'competencyIds'
+ * depending on the value of multiSelect.
+ *
+ * @package    tool_lp
+ * @copyright  2015 Frédéric Massart - FMCorz.net
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery',
+        'core/notification',
+        'core/ajax',
+        'core/templates',
+        'tool_lp/dialogue',
+        'core/str',
+        'tool_lp/tree'],
+        function($, Notification, Ajax, Templates, Dialogue, Str, Tree) {
+
+    /**
+     * Competency picker class.
+     * @param {Number} pageContextId The page context ID.
+     * @param {Number|false} singleFramework The ID of the framework when limited to one.
+     * @param {String} pageContextIncludes One of 'children', 'parents', 'self'.
+     * @param {Boolean} multiSelect Support multi-select in the tree.
+     */
+    var Picker = function(pageContextId, singleFramework, pageContextIncludes, multiSelect) {
+        var self = this;
+        self._eventNode = $('<div></div>');
+        self._frameworks = [];
+        self._reset();
+
+        self._pageContextId = pageContextId;
+        self._pageContextIncludes = pageContextIncludes || 'children';
+        self._multiSelect = (typeof multiSelect === 'undefined' || multiSelect === true);
+        if (singleFramework) {
+            self._frameworkId = singleFramework;
+            self._singleFramework = true;
+        }
+    };
+
+    /** @type {Array} The competencies fetched. */
+    Picker.prototype._competencies = null;
+    /** @type {Array} The competencies that cannot be picked. */
+    Picker.prototype._disallowedCompetencyIDs = null;
+    /** @type {Node} The node we attach the events to. */
+    Picker.prototype._eventNode = null;
+    /** @type {Array} The list of frameworks fetched. */
+    Picker.prototype._frameworks = null;
+    /** @type {Number} The current framework ID. */
+    Picker.prototype._frameworkId = null;
+    /** @type {Number} The page context ID. */
+    Picker.prototype._pageContextId = null;
+    /** @type {Number} Relevant contexts inclusion. */
+    Picker.prototype._pageContextIncludes = null;
+    /** @type {Dialogue} The reference to the dialogue. */
+    Picker.prototype._popup = null;
+    /** @type {String} The string we filter the competencies with. */
+    Picker.prototype._searchText = '';
+    /** @type {Object} The competency that was selected. */
+    Picker.prototype._selectedCompetencies = null;
+    /** @type {Boolean} Whether we can browse frameworks or not. */
+    Picker.prototype._singleFramework = false;
+    /** @type {Boolean} Do we allow multi select? */
+    Picker.prototype._multiSelect = true;
+    /** @type {Boolean} Do we allow to display hidden framework? */
+    Picker.prototype._onlyVisible = true;
+
+    /**
+     * Hook to executed after the view is rendered.
+     *
+     * @method _afterRender
+     */
+    Picker.prototype._afterRender = function() {
+        var self = this;
+
+        // Initialise the tree.
+        var tree = new Tree(self._find('[data-enhance=linktree]'), self._multiSelect);
+
+        // To prevent jiggling we only show the tree after it is enhanced.
+        self._find('[data-enhance=linktree]').show();
+
+        tree.on('selectionchanged', function(evt, params) {
+            var selected = params.selected;
+            evt.preventDefault();
+            var validIds = [];
+            $.each(selected, function(index, item) {
+                var compId = $(item).data('id'),
+                    valid = true;
+
+                if (typeof compId === 'undefined') {
+                    // Do not allow picking nodes with no id.
+                    valid = false;
+                } else {
+                    $.each(self._disallowedCompetencyIDs, function(i, id) {
+                        if (id == compId) {
+                            valid = false;
+                        }
+                    });
+                }
+                if (valid) {
+                    validIds.push(compId);
+                }
+            }.bind(self));
+
+            self._selectedCompetencies = validIds;
+
+            // TODO Implement disabling of nodes in the tree module somehow.
+            if (!self._selectedCompetencies.length) {
+                self._find('[data-region="competencylinktree"] [data-action="add"]').attr('disabled', 'disabled');
+            } else {
+                self._find('[data-region="competencylinktree"] [data-action="add"]').removeAttr('disabled');
+            }
+        }.bind(self));
+
+        // Add listener for framework change.
+        if (!self._singleFramework) {
+            self._find('[data-action="chooseframework"]').change(function(e) {
+                self._frameworkId = $(e.target).val();
+                self._loadCompetencies().then(self._refresh.bind(self));
+            }.bind(self));
+        }
+
+        // Add listener for search.
+        self._find('[data-region="filtercompetencies"] button').click(function(e) {
+            e.preventDefault();
+            $(e.target).attr('disabled', 'disabled');
+            self._searchText = self._find('[data-region="filtercompetencies"] input').val() || '';
+            return self._refresh().always(function() {
+                $(e.target).removeAttr('disabled');
+            });
+        }.bind(self));
+
+        // Add listener for cancel.
+        self._find('[data-region="competencylinktree"] [data-action="cancel"]').click(function(e) {
+            e.preventDefault();
+            self.close();
+        }.bind(self));
+
+        // Add listener for add.
+        self._find('[data-region="competencylinktree"] [data-action="add"]').click(function(e) {
+            e.preventDefault();
+            if (!self._selectedCompetencies.length) {
+                return;
+            }
+
+            if (self._multiSelect) {
+                self._trigger('save', { competencyIds: self._selectedCompetencies });
+            } else {
+                // We checked above that the array has at least one value.
+                self._trigger('save', { competencyId: self._selectedCompetencies[0] });
+            }
+
+            self.close();
+        }.bind(self));
+
+        // The list of selected competencies will be modified while looping (because of the listeners above).
+        var currentItems = self._selectedCompetencies.slice(0);
+
+        $.each(currentItems, function(index, id) {
+            var node = self._find('[data-id=' + id + ']');
+            if (node.length) {
+                tree.toggleItem(node);
+                tree.updateFocus(node);
+            }
+        }.bind(self));
+
+    };
+
+    /**
+     * Close the dialogue.
+     *
+     * @method close
+     */
+    Picker.prototype.close = function() {
+        var self = this;
+        self._popup.close();
+        self._reset();
+    };
+
+    /**
+     * Opens the picker.
+     *
+     * @method display
+     * @return {Promise}
+     */
+    Picker.prototype.display = function() {
+        var self = this;
+        return self._render().then(function(html) {
+            return Str.get_string('competencypicker', 'tool_lp').then(function(title) {
+                self._popup = new Dialogue(
+                    title,
+                    html,
+                    self._afterRender.bind(self)
+                );
+            }.bind(self));
+        }.bind(self)).fail(Notification.exception);
+    };
+
+    /**
+     * Fetch the competencies.
+     *
+     * @param {Number} frameworkId The frameworkId.
+     * @param {String} searchText Limit the competencies to those matching the text.
+     * @method _fetchCompetencies
+     * @return {Promise}
+     */
+    Picker.prototype._fetchCompetencies = function(frameworkId, searchText) {
+        var self = this;
+
+        return Ajax.call([
+            { methodname: 'core_competency_search_competencies', args: {
+                searchtext: searchText,
+                competencyframeworkid: frameworkId
+            }}
+        ])[0].done(function(competencies) {
+
+            function addCompetencyChildren(parent, competencies) {
+                for (var i = 0; i < competencies.length; i++) {
+                    if (competencies[i].parentid == parent.id) {
+                        parent.haschildren = true;
+                        competencies[i].children = [];
+                        competencies[i].haschildren = false;
+                        parent.children[parent.children.length] = competencies[i];
+                        addCompetencyChildren(competencies[i], competencies);
+                    }
+                }
+            }
+
+            // Expand the list of competencies into a tree.
+            var i, tree = [], comp;
+            for (i = 0; i < competencies.length; i++) {
+                comp = competencies[i];
+                if (comp.parentid == "0") { // Loose check for now, because WS returns a string.
+                    comp.children = [];
+                    comp.haschildren = 0;
+                    tree[tree.length] = comp;
+                    addCompetencyChildren(comp, competencies);
+                }
+            }
+
+            self._competencies = tree;
+
+        }.bind(self)).fail(Notification.exception);
+    };
+
+    /**
+     * Find a node in the dialogue.
+     *
+     * @param {String} selector
+     * @method _find
+     */
+    Picker.prototype._find = function(selector) {
+        return $(this._popup.getContent()).find(selector);
+    };
+
+    /**
+     * Convenience method to get a framework object.
+     *
+     * @param {Number} fid The framework ID.
+     * @method _getFramework
+     */
+    Picker.prototype._getFramework = function(fid) {
+        var frm;
+        $.each(this._frameworks, function(i, f) {
+            if (f.id == fid) {
+                frm = f;
+                return false;
+            }
+        });
+        return frm;
+    };
+
+    /**
+     * Load the competencies.
+     *
+     * @method _loadCompetencies
+     * @return {Promise}
+     */
+    Picker.prototype._loadCompetencies = function() {
+        return this._fetchCompetencies(this._frameworkId, this._searchText);
+    };
+
+    /**
+     * Load the frameworks.
+     *
+     * @method _loadFrameworks
+     * @return {Promise}
+     */
+    Picker.prototype._loadFrameworks = function() {
+        var promise,
+            self = this;
+
+        // Quit early because we already have the data.
+        if (self._frameworks.length > 0) {
+            return $.when();
+        }
+
+        if (self._singleFramework) {
+            promise = Ajax.call([
+                { methodname: 'core_competency_read_competency_framework', args: {
+                    id: this._frameworkId
+                }}
+            ])[0].then(function(framework) {
+                return [framework];
+            });
+        } else {
+            promise = Ajax.call([
+                { methodname: 'core_competency_list_competency_frameworks', args: {
+                    sort: 'shortname',
+                    context: { contextid: self._pageContextId },
+                    includes: self._pageContextIncludes,
+                    onlyvisible: self._onlyVisible
+                }}
+            ])[0];
+        }
+
+        return promise.done(function(frameworks) {
+            self._frameworks = frameworks;
+        }).fail(Notification.exception);
+    };
+
+    /**
+     * Register an event listener.
+     *
+     * @param {String} type The event type.
+     * @param {Function} handler The event listener.
+     * @method on
+     */
+    Picker.prototype.on = function(type, handler) {
+        this._eventNode.on(type, handler);
+    };
+
+    /**
+     * Hook to executed before render.
+     *
+     * @method _preRender
+     * @return {Promise}
+     */
+    Picker.prototype._preRender = function() {
+        var self = this;
+        return self._loadFrameworks().then(function() {
+            if (!self._frameworkId && self._frameworks.length > 0) {
+                self._frameworkId = self._frameworks[0].id;
+            }
+
+            // We could not set a framework ID, that probably means there are no frameworks accessible.
+            if (!self._frameworkId) {
+                self._frameworks = [];
+                return $.when();
+            }
+
+            return self._loadCompetencies();
+        }.bind(self));
+    };
+
+    /**
+     * Refresh the view.
+     *
+     * @method _refresh
+     * @return {Promise}
+     */
+    Picker.prototype._refresh = function() {
+        var self = this;
+        return self._render().then(function(html) {
+            self._find('[data-region="competencylinktree"]').replaceWith(html);
+            self._afterRender();
+        }.bind(self));
+    };
+
+    /**
+     * Render the dialogue.
+     *
+     * @method _render
+     * @return {Promise}
+     */
+    Picker.prototype._render = function() {
+        var self = this;
+        return self._preRender().then(function() {
+
+            if (!self._singleFramework) {
+                $.each(self._frameworks, function(i, framework) {
+                    if (framework.id == self._frameworkId) {
+                        framework.selected = true;
+                    } else {
+                        framework.selected = false;
+                    }
+                });
+            }
+
+            var context = {
+                competencies: self._competencies,
+                framework: self._getFramework(self._frameworkId),
+                frameworks: self._frameworks,
+                search: self._searchText,
+                singleFramework: self._singleFramework,
+            };
+
+            return Templates.render('tool_lp/competency_picker', context);
+        }.bind(self));
+    };
+
+    /**
+     * Reset the dialogue properties.
+     *
+     * This does not reset everything, just enough to reset the UI.
+     *
+     * @method _reset
+     */
+    Picker.prototype._reset = function() {
+        this._competencies = [];
+        this._disallowedCompetencyIDs = [];
+        this._popup = null;
+        this._searchText = '';
+        this._selectedCompetencies = [];
+    };
+
+    /**
+     * Set what competencies cannot be picked.
+     *
+     * This needs to be set after reset/close.
+     *
+     * @params {Number[]} The IDs.
+     * @method _setDisallowedCompetencyIDs
+     */
+    Picker.prototype.setDisallowedCompetencyIDs = function(ids) {
+        this._disallowedCompetencyIDs = ids;
+    };
+
+    /**
+     * Trigger an event.
+     *
+     * @param {String} type The type of event.
+     * @param {Object} The data to pass to the listeners.
+     * @method _reset
+     */
+    Picker.prototype._trigger = function(type, data) {
+        this._eventNode.trigger(type, [data]);
+    };
+
+    return /** @alias module:tool_lp/competencypicker */ Picker;
+
+});
diff --git a/admin/tool/lp/amd/src/competencypicker_user_plans.js b/admin/tool/lp/amd/src/competencypicker_user_plans.js
new file mode 100644 (file)
index 0000000..7291829
--- /dev/null
@@ -0,0 +1,240 @@
+// 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/>.
+
+/**
+ * Competency picker from user plans.
+ *
+ * To handle 'save' events use: picker.on('save').
+ *
+ * This will receive a object with either a single 'competencyId', or an array in 'competencyIds'
+ * depending on the value of multiSelect.
+ *
+ * @package    tool_lp
+ * @copyright  2015 Frédéric Massart - FMCorz.net
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery',
+        'core/notification',
+        'core/ajax',
+        'core/templates',
+        'core/str',
+        'tool_lp/tree',
+        'tool_lp/competencypicker'
+        ],
+        function($, Notification, Ajax, Templates, Str, Tree, PickerBase) {
+
+    /**
+     * Competency picker in plan class.
+     *
+     * @param {Number|false} singlePlan The ID of the plan when limited to one.
+     * @param {String} pageContextIncludes One of 'children', 'parents', 'self'.
+     * @param {Boolean} multiSelect Support multi-select in the tree.
+     */
+    var Picker = function(userId, singlePlan, multiSelect) {
+        PickerBase.prototype.constructor.apply(this, [1, false, 'self', multiSelect]);
+        this._userId = userId;
+        this._plans = [];
+
+        if (singlePlan) {
+            this._planId = singlePlan;
+            this._singlePlan = true;
+        }
+    };
+    Picker.prototype = Object.create(PickerBase.prototype);
+
+    /** @type {Array} The list of plans fetched. */
+    Picker.prototype._plans = null;
+    /** @type {Number} The current plan ID. */
+    Picker.prototype._planId = null;
+    /** @type {Boolean} Whether we can browse plans or not. */
+    Picker.prototype._singlePlan = false;
+    /** @type {Number} The user the plans belongs to. */
+    Picker.prototype._userId = null;
+
+    /**
+     * Hook to executed after the view is rendered.
+     *
+     * @method _afterRender
+     */
+    Picker.prototype._afterRender = function() {
+        var self = this;
+        PickerBase.prototype._afterRender.apply(self, arguments);
+
+        // Add listener for framework change.
+        if (!self._singlePlan) {
+            self._find('[data-action="chooseplan"]').change(function(e) {
+                self._planId = $(e.target).val();
+                self._loadCompetencies().then(self._refresh.bind(self));
+            }.bind(self));
+        }
+    };
+
+    /**
+     * Fetch the competencies.
+     *
+     * @param {Number} planId The planId.
+     * @param {String} searchText Limit the competencies to those matching the text.
+     * @method _fetchCompetencies
+     * @return {Promise}
+     */
+    Picker.prototype._fetchCompetencies = function(planId, searchText) {
+        var self = this;
+
+        return Ajax.call([
+            { methodname: 'core_competency_list_plan_competencies', args: {
+                id: planId
+            }}
+        ])[0].done(function(competencies) {
+
+            // Expand the list of competencies into a fake tree.
+            var i, tree = [], comp;
+            for (i = 0; i < competencies.length; i++) {
+                comp = competencies[i].competency;
+                if (comp.shortname.toLowerCase().indexOf(searchText.toLowerCase()) < 0) {
+                    continue;
+                }
+                comp.children = [];
+                comp.haschildren = 0;
+                tree.push(comp);
+            }
+
+            self._competencies = tree;
+
+        }).fail(Notification.exception);
+    };
+
+    /**
+     * Convenience method to get a plan object.
+     *
+     * @param {Number} id The plan ID.
+     * @return {Object|undefined} The plan.
+     * @method _getPlan
+     */
+    Picker.prototype._getPlan = function(id) {
+        var plan;
+        $.each(this._plans, function(i, f) {
+            if (f.id == id) {
+                plan = f;
+                return false;
+            }
+        });
+        return plan;
+    };
+
+    /**
+     * Load the competencies.
+     *
+     * @method _loadCompetencies
+     * @return {Promise}
+     */
+    Picker.prototype._loadCompetencies = function() {
+        return this._fetchCompetencies(this._planId, this._searchText);
+    };
+
+    /**
+     * Load the plans.
+     *
+     * @method _loadPlans
+     * @return {Promise}
+     */
+    Picker.prototype._loadPlans = function() {
+        var promise,
+            self = this;
+
+        // Quit early because we already have the data.
+        if (self._plans.length > 0) {
+            return $.when();
+        }
+
+        if (self._singlePlan) {
+            promise = Ajax.call([
+                { methodname: 'core_competency_read_plan', args: {
+                    id: this._planId
+                }}
+            ])[0].then(function(plan) {
+                return [plan];
+            });
+        } else {
+            promise = Ajax.call([
+                { methodname: 'core_competency_list_user_plans', args: {
+                    userid: self._userId
+                }}
+            ])[0];
+        }
+
+        return promise.done(function(plans) {
+            self._plans = plans;
+        }).fail(Notification.exception);
+    };
+
+    /**
+     * Hook to executed before render.
+     *
+     * @method _preRender
+     * @return {Promise}
+     */
+    Picker.prototype._preRender = function() {
+        var self = this;
+        return self._loadPlans().then(function() {
+            if (!self._planId && self._plans.length > 0) {
+                self._planId = self._plans[0].id;
+            }
+
+            // We could not set a framework ID, that probably means there are no frameworks accessible.
+            if (!self._planId) {
+                self._plans = [];
+                return $.when();
+            }
+
+            return self._loadCompetencies();
+        }.bind(self));
+    };
+
+    /**
+     * Render the dialogue.
+     *
+     * @method _render
+     * @return {Promise}
+     */
+    Picker.prototype._render = function() {
+        var self = this;
+        return self._preRender().then(function() {
+
+            if (!self._singlePlan) {
+                $.each(self._plans, function(i, plan) {
+                    if (plan.id == self._planId) {
+                        plan.selected = true;
+                    } else {
+                        plan.selected = false;
+                    }
+                });
+            }
+
+            var context = {
+                competencies: self._competencies,
+                plan: self._getPlan(self._planId),
+                plans: self._plans,
+                search: self._searchText,
+                singlePlan: self._singlePlan,
+            };
+
+            return Templates.render('tool_lp/competency_picker_user_plans', context);
+        }.bind(self));
+    };
+
+    return /** @alias module:tool_lp/competencypicker_user_plans */ Picker;
+
+});
diff --git a/admin/tool/lp/amd/src/competencyruleconfig.js b/admin/tool/lp/amd/src/competencyruleconfig.js
new file mode 100644 (file)
index 0000000..4894d36
--- /dev/null
@@ -0,0 +1,548 @@
+// 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/>.
+
+/**
+ * Competency rule config.
+ *
+ * @package    tool_lp
+ * @copyright  2015 Frédéric Massart - FMCorz.net
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery',
+        'core/notification',
+        'core/templates',
+        'tool_lp/dialogue',
+        'tool_lp/competency_outcomes',
+        'core/str'],
+        function($, Notification, Templates, Dialogue, Outcomes, Str) {
+
+    /**
+     * Competency rule class.
+     *
+     * When implementing this you should attach a listener to the event 'save'
+     * on the instance. E.g.
+     *
+     * var config = new RuleConfig(tree, modules);
+     * config.on('save', function(e, config) { ... });
+     *
+     * @param {competencytree} tree The competency tree.
+     * @param {Array} rulesModules The modules containing the rules: [{ typeName: { amd: amdModule, name: ruleName }}].
+     */
+    var RuleConfig = function(tree, rulesModules) {
+        this._eventNode = $('<div></div>');
+        this._tree = tree;
+        this._rulesModules = rulesModules;
+        this._setUp();
+    };
+
+    /** @type {Object} The current competency. */
+    RuleConfig.prototype._competency = null;
+    /** @type {Node} The node we attach the events to. */
+    RuleConfig.prototype._eventNode = null;
+    /** @type {Array} Outcomes options. */
+    RuleConfig.prototype._outcomesOption = null;
+    /** @type {Dialogue} The dialogue. */
+    RuleConfig.prototype._popup = null;
+    /** @type {Promise} Resolved when the module is ready. */
+    RuleConfig.prototype._ready = null;
+    /** @type {Array} The rules. */
+    RuleConfig.prototype._rules = null;
+    /** @type {Array} The rules modules. */
+    RuleConfig.prototype._rulesModules = null;
+    /** @type {competencytree} The competency tree. */
+    RuleConfig.prototype._tree = null;
+
+    /**
+     * After change.
+     *
+     * Triggered when a change occured.
+     *
+     * @return {Void}
+     * @method _afterChange
+     * @protected
+     */
+    RuleConfig.prototype._afterChange = function() {
+        if (!this._isValid()) {
+            this._find('[data-action="save"]').prop('disabled', true);
+        } else {
+            this._find('[data-action="save"]').prop('disabled', false);
+        }
+    };
+
+    /**
+     * After change in rule's config.
+     *
+     * Triggered when a change occured in a specific rule config.
+     *
+     * @return {Void}
+     * @method _afterRuleConfigChange
+     * @protected
+     */
+    RuleConfig.prototype._afterRuleConfigChange = function(e, rule) {
+        if (rule != this._getRule()) {
+            // This rule is not the current one any more, we can ignore.
+            return;
+        }
+        this._afterChange();
+    };
+
+    /**
+     * After render hook.
+     *
+     * @return {Promise}
+     * @method _afterRender
+     * @protected
+     */
+    RuleConfig.prototype._afterRender = function() {
+        var self = this;
+
+        self._find('[name="outcome"]').on('change', function() {
+            self._switchedOutcome();
+        }).trigger('change');
+
+        self._find('[name="rule"]').on('change', function() {
+            self._switchedRule();
+        }).trigger('change');
+
+        self._find('[data-action="save"]').on('click', function() {
+            self._trigger('save', self._getConfig());
+            self.close();
+        });
+
+        self._find('[data-action="cancel"]').on('click', function() {
+            self.close();
+        });
+    };
+
+    /**
+     * Whether the current competency can be configured.
+     *
+     * @return {Boolean}
+     * @method canBeConfigured
+     */
+    RuleConfig.prototype.canBeConfigured = function() {
+        var can = false;
+        $.each(this._rules, function(index, rule) {
+            if (rule.canConfig()) {
+                can = true;
+                return false;
+            }
+        });
+        return can;
+    };
+
+    /**
+     * Close the dialogue.
+     *
+     * @method close
+     */
+    RuleConfig.prototype.close = function() {
+        this._popup.close();
+        this._popup = null;
+    };
+
+    /**
+     * Opens the picker.
+     *
+     * @param {Number} competencyId The competency ID of the competency to work on.
+     * @method display
+     * @return {Promise}
+     */
+    RuleConfig.prototype.display = function() {
+        var self = this;
+        if (!self._competency) {
+            return;
+        }
+        return self._render().then(function(html) {
+            return Str.get_string('competencyrule', 'tool_lp').then(function(title) {
+                self._popup = new Dialogue(
+                    title,
+                    html,
+                    self._afterRender.bind(self)
+                );
+            });
+        }).fail(Notification.exception);
+    };
+
+    /**
+     * Find a node in the dialogue.
+     *
+     * @param {String} selector
+     * @method _find
+     * @protected
+     */
+    RuleConfig.prototype._find = function(selector) {
+        return $(this._popup.getContent()).find(selector);
+    };
+
+    /**
+     * Get the applicable outcome options.
+     *
+     * @return {Array}
+     * @method _getApplicableOutcomesOptions
+     * @protected
+     */
+    RuleConfig.prototype._getApplicableOutcomesOptions = function() {
+        var self = this,
+            options = [];
+
+        $.each(self._outcomesOption, function(index, outcome) {
+            options.push({
+                code: outcome.code,
+                name: outcome.name,
+                selected: (outcome.code == self._competency.ruleoutcome) ? true : false,
+            });
+        });
+
+    &