Merge branch 'MDL-47499-master' of git://github.com/damyon/moodle
authorDan Poltawski <dan@moodle.com>
Thu, 23 Oct 2014 09:19:53 +0000 (10:19 +0100)
committerDan Poltawski <dan@moodle.com>
Thu, 23 Oct 2014 09:19:53 +0000 (10:19 +0100)
310 files changed:
admin/cli/mysql_compressed_rows.php
admin/settings/grades.php
admin/tool/behat/tests/behat/data_generators.feature
admin/tool/monitor/classes/event/rule_created.php [new file with mode: 0644]
admin/tool/monitor/classes/event/rule_deleted.php [new file with mode: 0644]
admin/tool/monitor/classes/event/rule_updated.php [new file with mode: 0644]
admin/tool/monitor/classes/event/subscription_created.php [new file with mode: 0644]
admin/tool/monitor/classes/event/subscription_criteria_met.php [new file with mode: 0644]
admin/tool/monitor/classes/event/subscription_deleted.php [new file with mode: 0644]
admin/tool/monitor/classes/eventlist.php [new file with mode: 0644]
admin/tool/monitor/classes/eventobservers.php [new file with mode: 0644]
admin/tool/monitor/classes/notification_task.php [new file with mode: 0644]
admin/tool/monitor/classes/output/helpicon/renderable.php [new file with mode: 0644]
admin/tool/monitor/classes/output/helpicon/renderer.php [new file with mode: 0644]
admin/tool/monitor/classes/output/managerules/renderable.php [new file with mode: 0644]
admin/tool/monitor/classes/output/managerules/renderer.php [new file with mode: 0644]
admin/tool/monitor/classes/output/managesubs/renderer.php [new file with mode: 0644]
admin/tool/monitor/classes/output/managesubs/rules.php [new file with mode: 0644]
admin/tool/monitor/classes/output/managesubs/subs.php [new file with mode: 0644]
admin/tool/monitor/classes/rule.php [new file with mode: 0644]
admin/tool/monitor/classes/rule_form.php [new file with mode: 0644]
admin/tool/monitor/classes/rule_manager.php [new file with mode: 0644]
admin/tool/monitor/classes/subscription.php [new file with mode: 0644]
admin/tool/monitor/classes/subscription_manager.php [new file with mode: 0644]
admin/tool/monitor/classes/task/clean_events.php [new file with mode: 0644]
admin/tool/monitor/db/access.php [new file with mode: 0644]
admin/tool/monitor/db/events.php [new file with mode: 0644]
admin/tool/monitor/db/install.xml [new file with mode: 0644]
admin/tool/monitor/db/messages.php [new file with mode: 0644]
admin/tool/monitor/db/tasks.php [new file with mode: 0644]
admin/tool/monitor/edit.php [new file with mode: 0644]
admin/tool/monitor/help.php [new file with mode: 0644]
admin/tool/monitor/help_ajax.php [new file with mode: 0644]
admin/tool/monitor/index.php [new file with mode: 0644]
admin/tool/monitor/lang/en/tool_monitor.php [new file with mode: 0644]
admin/tool/monitor/lib.php [new file with mode: 0644]
admin/tool/monitor/managerules.php [new file with mode: 0644]
admin/tool/monitor/settings.php [new file with mode: 0644]
admin/tool/monitor/tests/behat/rule.feature [new file with mode: 0644]
admin/tool/monitor/tests/behat/subscription.feature [new file with mode: 0644]
admin/tool/monitor/tests/eventobservers_test.php [new file with mode: 0644]
admin/tool/monitor/tests/events_test.php [new file with mode: 0644]
admin/tool/monitor/tests/generator/lib.php [new file with mode: 0644]
admin/tool/monitor/tests/generator_test.php [new file with mode: 0644]
admin/tool/monitor/tests/rule_manager_test.php [new file with mode: 0644]
admin/tool/monitor/tests/task_clean_events_test.php [new file with mode: 0644]
admin/tool/monitor/version.php [new file with mode: 0644]
admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown-debug.js [new file with mode: 0644]
admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown-min.js [new file with mode: 0644]
admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown.js [new file with mode: 0644]
admin/tool/monitor/yui/src/dropdown/build.json [new file with mode: 0644]
admin/tool/monitor/yui/src/dropdown/js/dropdown.js [new file with mode: 0644]
admin/tool/monitor/yui/src/dropdown/meta/dropdown.json [new file with mode: 0644]
admin/tool/uploadcourse/tests/course_test.php
admin/tool/uploadcourse/tests/processor_test.php
auth/db/tests/db_test.php
availability/classes/tree.php
availability/condition/profile/tests/condition_test.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_stepslib.php
backup/moodle2/tests/moodle2_test.php
blocks/badges/db/upgrade.php
blocks/calendar_month/db/upgrade.php
blocks/calendar_upcoming/db/upgrade.php
cache/stores/memcache/lib.php
cache/stores/memcache/tests/memcache_test.php
cache/tests/fixtures/stores.php
course/tests/courselib_test.php
course/tests/externallib_test.php
enrol/database/tests/sync_test.php
grade/edit/tree/category_form.php
grade/edit/tree/item_form.php
grade/edit/tree/lib.php
grade/edit/tree/outcomeitem.php
grade/edit/tree/outcomeitem_form.php
grade/export/ods/grade_export_ods.php
grade/export/ods/lang/en/gradeexport_ods.php
grade/export/txt/grade_export_txt.php
grade/export/txt/lang/en/gradeexport_txt.php
grade/export/xls/grade_export_xls.php
grade/export/xls/lang/en/gradeexport_xls.php
grade/import/csv/classes/load_data.php
grade/import/csv/index.php
grade/import/csv/tests/load_data_test.php
grade/import/direct/classes/import_form.php
grade/import/direct/classes/mapping_form.php
grade/import/direct/index.php
grade/import/grade_import_form.php
grade/lib.php
grade/querylib.php
grade/report/grader/index.php
grade/report/grader/lib.php
grade/report/history/classes/output/tablelog.php
grade/report/history/index.php
grade/report/lib.php
grade/report/singleview/classes/event/grade_report_viewed.php [new file with mode: 0644]
grade/report/singleview/classes/local/screen/filterable_items.php [new file with mode: 0644]
grade/report/singleview/classes/local/screen/grade.php [new file with mode: 0644]
grade/report/singleview/classes/local/screen/screen.php [new file with mode: 0644]
grade/report/singleview/classes/local/screen/select.php [new file with mode: 0644]
grade/report/singleview/classes/local/screen/selectable_items.php [new file with mode: 0644]
grade/report/singleview/classes/local/screen/tablelike.php [new file with mode: 0644]
grade/report/singleview/classes/local/screen/user.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/attribute_format.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/be_checked.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/be_disabled.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/bulk_insert.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/checkbox_attribute.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/dropdown_attribute.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/element.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/empty_element.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/exclude.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/feedback.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/finalgrade.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/grade_attribute_format.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/override.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/range.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/tabbable.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/text_attribute.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/unique_name.php [new file with mode: 0644]
grade/report/singleview/classes/local/ui/unique_value.php [new file with mode: 0644]
grade/report/singleview/db/access.php [new file with mode: 0644]
grade/report/singleview/index.php [new file with mode: 0644]
grade/report/singleview/js/singleview.js [new file with mode: 0644]
grade/report/singleview/lang/en/gradereport_singleview.php [new file with mode: 0644]
grade/report/singleview/lib.php [new file with mode: 0644]
grade/report/singleview/styles.css [new file with mode: 0644]
grade/report/singleview/tests/behat/singleview.feature [new file with mode: 0644]
grade/report/singleview/version.php [new file with mode: 0644]
grade/report/user/db/upgrade.php [new file with mode: 0644]
grade/report/user/lib.php
grade/report/user/settings.php
grade/report/user/styles.css
grade/report/user/version.php
grade/tests/behat/grade_aggregation.feature
grade/tests/behat/grade_calculated_weights.feature
grade/tests/behat/grade_contribution_with_extra_credit.feature [new file with mode: 0644]
grade/tests/behat/grade_mingrade.feature
grade/tests/behat/grade_scales.feature
grade/tests/behat/grade_single_item_scales.feature [new file with mode: 0644]
grade/tests/report_graderlib_test.php
install/lang/no/error.php
install/lang/no/install.php
install/lang/ru/error.php
install/lang/vi/error.php
lang/en/grades.php
lib/accesslib.php
lib/adminlib.php
lib/blocklib.php
lib/classes/grades_external.php
lib/classes/plugin_manager.php
lib/csvlib.class.php
lib/db/install.xml
lib/db/upgrade.php
lib/editor/atto/lang/en/editor_atto.php
lib/editor/atto/plugins/managefiles/yui/build/moodle-atto_managefiles-usedfiles/moodle-atto_managefiles-usedfiles-debug.js
lib/editor/atto/plugins/managefiles/yui/build/moodle-atto_managefiles-usedfiles/moodle-atto_managefiles-usedfiles-min.js
lib/editor/atto/plugins/managefiles/yui/build/moodle-atto_managefiles-usedfiles/moodle-atto_managefiles-usedfiles.js
lib/editor/atto/plugins/managefiles/yui/src/usedfiles/js/usedfiles.js
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/editor/tinymce/plugins/managefiles/tinymce/editor_plugin.js
lib/filelib.php
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-debug.js
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-min.js
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector.js
lib/form/yui/src/dateselector/js/calendar.js
lib/grade/grade_category.php
lib/grade/grade_grade.php
lib/grade/grade_item.php
lib/grade/tests/fixtures/lib.php
lib/grade/tests/grade_item_test.php
lib/gradelib.php
lib/modinfolib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/testing/classes/util.php
lib/testing/generator/data_generator.php
lib/tests/admintree_test.php
lib/tests/behat/behat_data_generators.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_hooks.php
lib/tests/cronlib_test.php
lib/tests/event_user_graded_test.php
lib/tests/filelib_test.php
lib/tests/questionlib_test.php
lib/upgrade.txt
lib/weblib.php
mod/assign/feedback/editpdf/tests/editpdf_test.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/tests/lib_test.php
mod/assign/upgradelib.php
mod/forum/tests/behat/my_forum_posts.feature [new file with mode: 0644]
mod/forum/tests/mail_test.php
mod/glossary/edit.php
mod/lesson/db/access.php
mod/lesson/essay.php
mod/lesson/lang/en/lesson.php
mod/lesson/lib.php
mod/lesson/renderer.php
mod/lesson/tabs.php
mod/lesson/tests/behat/lesson_informations_at_end.feature [new file with mode: 0644]
mod/lesson/tests/behat/teacher_grade_essays.feature [new file with mode: 0644]
mod/lesson/version.php
mod/lti/OAuthBody.php
mod/lti/TrivialStore.php
mod/lti/ajax.php
mod/lti/backup/moodle1/lib.php
mod/lti/backup/moodle2/backup_lti_activity_task.class.php
mod/lti/backup/moodle2/backup_lti_stepslib.php
mod/lti/backup/moodle2/restore_lti_activity_task.class.php
mod/lti/backup/moodle2/restore_lti_stepslib.php
mod/lti/basiclti.js [deleted file]
mod/lti/classes/local/ltiservice/resource_base.php [new file with mode: 0644]
mod/lti/classes/local/ltiservice/response.php [new file with mode: 0644]
mod/lti/classes/local/ltiservice/service_base.php [new file with mode: 0644]
mod/lti/classes/plugininfo/ltiservice.php [new file with mode: 0644]
mod/lti/db/access.php
mod/lti/db/install.xml
mod/lti/db/log.php
mod/lti/db/subplugins.php
mod/lti/db/upgrade.php
mod/lti/db/upgradelib.php [new file with mode: 0644]
mod/lti/edit_form.php
mod/lti/grade.php
mod/lti/index.php
mod/lti/instructor_edit_tool_type.php
mod/lti/lang/en/lti.php
mod/lti/launch.php
mod/lti/lib.php
mod/lti/localadminlib.php [deleted file]
mod/lti/locallib.php
mod/lti/mod_form.js
mod/lti/mod_form.php
mod/lti/register.php [new file with mode: 0644]
mod/lti/register_form.php [new file with mode: 0644]
mod/lti/registersettings.php [new file with mode: 0644]
mod/lti/registration.php [new file with mode: 0644]
mod/lti/registrationreturn.php [new file with mode: 0644]
mod/lti/request_tool.php
mod/lti/return.php
mod/lti/service.php
mod/lti/service/profile/classes/local/resource/profile.php [new file with mode: 0644]
mod/lti/service/profile/classes/local/service/profile.php [new file with mode: 0644]
mod/lti/service/profile/lang/en/ltiservice_profile.php [new file with mode: 0644]
mod/lti/service/profile/version.php [new file with mode: 0644]
mod/lti/service/readme.txt [new file with mode: 0644]
mod/lti/service/toolproxy/classes/local/resource/toolproxy.php [new file with mode: 0644]
mod/lti/service/toolproxy/classes/local/service/toolproxy.php [new file with mode: 0644]
mod/lti/service/toolproxy/lang/en/ltiservice_toolproxy.php [new file with mode: 0644]
mod/lti/service/toolproxy/version.php [new file with mode: 0644]
mod/lti/service/toolsettings/classes/local/resource/contextsettings.php [new file with mode: 0644]
mod/lti/service/toolsettings/classes/local/resource/linksettings.php [new file with mode: 0644]
mod/lti/service/toolsettings/classes/local/resource/systemsettings.php [new file with mode: 0644]
mod/lti/service/toolsettings/classes/local/service/toolsettings.php [new file with mode: 0644]
mod/lti/service/toolsettings/lang/en/ltiservice_toolsettings.php [new file with mode: 0644]
mod/lti/service/toolsettings/version.php [new file with mode: 0644]
mod/lti/servicelib.php
mod/lti/services.php [new file with mode: 0644]
mod/lti/settings.php
mod/lti/tests/event/unknown_service_api_called_test.php
mod/lti/tests/generator_test.php
mod/lti/tests/locallib_test.php
mod/lti/tests/upgradelib_test.php [new file with mode: 0644]
mod/lti/toolproxies.php [new file with mode: 0644]
mod/lti/toolssettings.php [new file with mode: 0644]
mod/lti/typessettings.php
mod/lti/upgrade.txt
mod/lti/version.php
mod/lti/view.php
mod/quiz/attemptlib.php
mod/quiz/classes/output/edit_renderer.php
mod/quiz/db/events.php
mod/quiz/locallib.php
mod/quiz/report/responses/first_or_all_responses_table.php
mod/quiz/styles.css
mod/quiz/tests/attempt_walkthrough_test.php
mod/quiz/tests/generator/lib.php
mod/quiz/upgrade.txt
mod/quiz/version.php
mod/wiki/pagelib.php
mod/wiki/renderer.php
mod/wiki/search.php
mod/wiki/tests/behat/wiki_search.feature
my/tests/behat/add_blocks.feature
question/engine/datalib.php
question/engine/questionattempt.php
question/engine/questionattemptstep.php
question/engine/tests/datalib_reporting_queries_test.php [moved from question/engine/tests/question_engine_data_mapper_test.php with 92% similarity]
question/engine/tests/datalib_test.php [new file with mode: 0644]
question/format/gift/tests/behat/import_export.feature
question/tests/behat/edit_questions.feature
question/tests/behat/preview_question.feature
question/type/multianswer/module.js
report/participation/index.php
theme/base/style/core.css
theme/base/style/grade.css
theme/bootstrapbase/layout/popup.php
theme/bootstrapbase/less/moodle/grade.less
theme/bootstrapbase/less/moodle/modules.less
theme/bootstrapbase/style/moodle.css
user/editadvanced.php
user/index.php
user/tests/behat/table_sorting.feature
user/tests/profilelib_test.php
version.php

index 92aa9db..95af47d 100644 (file)
@@ -52,7 +52,7 @@ $help =
 
 By default InnoDB storage table is using legacy Antelope file format
 which has major restriction on database row size.
-Use this script to detect and fix database tables with potentail data
+Use this script to detect and fix database tables with potential data
 overflow problems.
 
 Options:
index 84a5296..15a429e 100644 (file)
@@ -112,8 +112,6 @@ if (has_capability('moodle/grade:manage', $systemcontext)
         $defaults = array('value'=>0, 'forced'=>false, 'adv'=>true);
         $temp->add(new admin_setting_gradecat_combo('grade_aggregateoutcomes', new lang_string('aggregateoutcomes', 'grades'),
                     new lang_string('aggregateoutcomes_help', 'grades'), $defaults, $options));
-        $temp->add(new admin_setting_gradecat_combo('grade_aggregatesubcats', new lang_string('aggregatesubcats', 'grades'),
-                    new lang_string('aggregatesubcats_help', 'grades'), $defaults, $options));
 
         $options = array(0 => new lang_string('none'));
         for ($i=1; $i<=20; $i++) {
index c9758f2..2b32cb0 100644 (file)
@@ -185,6 +185,12 @@ Feature: Set up contextual data for tests
       | url        | Test url name          | Test url description          | C1     | url1        |
       | wiki       | Test wiki name         | Test wiki description         | C1     | wiki1       |
       | workshop   | Test workshop name     | Test workshop description     | C1     | workshop1   |
+    And the following "scales" exist:
+      | name | scale |
+      | Test Scale 1 | Disappointing, Good, Very good, Excellent |
+    And the following "activities" exist:
+      | activity   | name                            | intro                         | course | idnumber    | grade |
+      | assign     | Test assignment name with scale | Test assignment description   | C1     | assign1     | Test Scale 1 |
     When I log in as "admin"
     And I follow "Course 1"
     Then I should see "Test assignment name"
@@ -214,6 +220,10 @@ Feature: Set up contextual data for tests
     And I should see "Test workshop name"
     And I follow "Test assignment name"
     And I should see "Test assignment description"
+    And I follow "C1"
+    And I follow "Test assignment name with scale"
+    And I follow "Edit settings"
+    And the field "Type" matches value "Scale"
 
   @javascript
   Scenario: Add relations between users and groups
@@ -297,13 +307,124 @@ Feature: Set up contextual data for tests
       | Course 1 | C1 |
     And the following "grade categories" exist:
       | fullname | course |
-      | Grade category 1 | C1|
+      | Grade category 1 | C1 |
     And the following "grade categories" exist:
       | fullname | course | gradecategory |
-      | Grade sub category 2 | C1 | Grade category 1|
+      | Grade sub category 2 | C1 | Grade category 1 |
     When I log in as "admin"
     And I follow "Courses"
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     Then I should see "Grade category 1"
     And I should see "Grade sub category 2"
+
+  Scenario: Add a bunch of grade items
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1 |
+    And the following "grade categories" exist:
+      | fullname | course |
+      | Grade category 1 | C1 |
+    And the following "grade categories" exist:
+      | fullname | course | gradecategory |
+      | Grade sub category 2 | C1 | Grade category 1 |
+    And the following "grade items" exist:
+      | itemname    | course |
+      | Test Grade Item 1 | C1 |
+    And the following "grade items" exist:
+      | itemname    | course | gradecategory |
+      | Test Grade Item 2 | C1 | Grade category 1 |
+      | Test Grade Item 3 | C1 | Grade sub category 2 |
+    When I log in as "admin"
+    And I follow "Course 1"
+    And I follow "Grades"
+    And I expand "Setup" node
+    And I follow "Categories and items"
+    Then I should see "Test Grade Item 1"
+    And I follow "Edit   Test Grade Item 1"
+    And I expand all fieldsets
+    And I should see "Course 1"
+    And I press "Cancel"
+    And I should see "Grade category 1"
+    And I should see "Test Grade Item 2"
+    And I follow "Edit   Test Grade Item 2"
+    And I expand all fieldsets
+    And I should see "Grade category 1"
+    And I press "Cancel"
+    And I should see "Grade sub category 2"
+    And I should see "Test Grade Item 3"
+    And I follow "Edit   Test Grade Item 3"
+    And I expand all fieldsets
+    And I should see "Grade sub category 2"
+    And I press "Cancel"
+
+  Scenario: Add a bunch of scales
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1 |
+    And the following "scales" exist:
+      | name | scale |
+      | Test Scale 1 | Disappointing, Good, Very good, Excellent |
+    When I log in as "admin"
+    And I follow "Course 1"
+    And I follow "Grades"
+    And I follow "Scales"
+    Then I should see "Test Scale 1"
+    And I should see "Disappointing,  Good,  Very good,  Excellent"
+
+  Scenario: Add a bunch of outcomes
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "scales" exist:
+      | name | scale |
+      | Test Scale 1 | Disappointing, Good, Very good, Excellent |
+    And the following "grade outcomes" exist:
+      | fullname        | shortname | scale        |
+      | Grade outcome 1 | OT1       | Test Scale 1 |
+    And the following "grade outcomes" exist:
+      | fullname        | shortname | course | scale        |
+      | Grade outcome 2 | OT2       | C1     | Test Scale 1 |
+    When I log in as "admin"
+    And I set the following administration settings values:
+      | Enable outcomes | 1 |
+    And I follow "Home"
+    And I follow "Course 1"
+    And I follow "Outcomes"
+    Then I should see "Grade outcome 1" in the "#addoutcomes" "css_element"
+    And I should see "Grade outcome 2" in the "#removeoutcomes" "css_element"
+    And I follow "Edit outcomes"
+    And the following should exist in the "generaltable" table:
+      | Full name       | Short name | Scale        |
+      | Grade outcome 2 | OT2        | Test Scale 1 |
+
+  Scenario: Add a bunch of outcome grade items
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "scales" exist:
+      | name         | scale                                     |
+      | Test Scale 1 | Disappointing, Good, Very good, Excellent |
+    And the following "grade outcomes" exist:
+      | fullname        | shortname | course | scale        |
+      | Grade outcome 1 | OT1       | C1     | Test Scale 1 |
+    And the following "grade categories" exist:
+      | fullname         | course |
+      | Grade category 1 | C1     |
+     And the following "grade items" exist:
+       | itemname                  | course | outcome | gradecategory    |
+       | Test Outcome Grade Item 1 | C1     | OT1     | Grade category 1 |
+    When I log in as "admin"
+    And I set the following administration settings values:
+      | Enable outcomes | 1 |
+    And I follow "Home"
+    And I follow "Course 1"
+    And I follow "Grades"
+    And I expand "Setup" node
+    And I follow "Categories and items"
+    Then I should see "Test Outcome Grade Item 1"
+    And I follow "Edit   Test Outcome Grade Item 1"
+    And the field "Outcome" matches value "Grade outcome 1"
+    And I expand all fieldsets
+    And "//div[contains(@class, 'fitem')]/div[contains(@class, 'fitemtitle')]/div[contains(@class, fstaticlabel) and contains(., 'Grade category')]/../../div[contains(@class, 'felement') and contains(., 'Grade category 1')]" "xpath_element" should exist
+    And I press "Cancel"
diff --git a/admin/tool/monitor/classes/event/rule_created.php b/admin/tool/monitor/classes/event/rule_created.php
new file mode 100644 (file)
index 0000000..a5d6fe1
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The tool_monitor rule created event.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_monitor rule created event class.
+ *
+ * @package    tool_monitor
+ * @since      Moodle 2.8
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class rule_created extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'tool_monitor_rules';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventrulecreated', 'tool_monitor');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' created the event monitor rule with id '$this->objectid'.";
+    }
+
+    /**
+     * Get URL related to the action
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/admin/tool/monitor/edit.php', array('ruleid' => $this->objectid,
+            'courseid' => $this->courseid));
+    }
+}
diff --git a/admin/tool/monitor/classes/event/rule_deleted.php b/admin/tool/monitor/classes/event/rule_deleted.php
new file mode 100644 (file)
index 0000000..988a5b6
--- /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/>.
+
+/**
+ * The tool_monitor rule deleted event.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_monitor rule deleted event class.
+ *
+ * @package    tool_monitor
+ * @since      Moodle 2.8
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class rule_deleted extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'tool_monitor_rules';
+        $this->data['crud'] = 'd';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventruledeleted', 'tool_monitor');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' deleted the event monitor rule with id '$this->objectid'.";
+    }
+
+    /**
+     * Get URL related to the action
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/admin/tool/monitor/managerules.php', array('courseid' => $this->courseid));
+    }
+}
diff --git a/admin/tool/monitor/classes/event/rule_updated.php b/admin/tool/monitor/classes/event/rule_updated.php
new file mode 100644 (file)
index 0000000..c17da2d
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The tool_monitor rule updated event.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_monitor rule updated event class.
+ *
+ * @package    tool_monitor
+ * @since      Moodle 2.8
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class rule_updated extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'tool_monitor_rules';
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventruleupdated', 'tool_monitor');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' updated the event monitor rule with id '$this->objectid'.";
+    }
+
+    /**
+     * Get URL related to the action
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/admin/tool/monitor/edit.php', array('ruleid' => $this->objectid,
+            'courseid' => $this->courseid));
+    }
+}
diff --git a/admin/tool/monitor/classes/event/subscription_created.php b/admin/tool/monitor/classes/event/subscription_created.php
new file mode 100644 (file)
index 0000000..e3d2b81
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The tool_monitor subscription created event.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_monitor subscription created event class.
+ *
+ * @package    tool_monitor
+ * @since      Moodle 2.8
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class subscription_created extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'tool_monitor_subscriptions';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventsubcreated', 'tool_monitor');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' created the event monitor subscription with id '$this->objectid'.";
+    }
+}
diff --git a/admin/tool/monitor/classes/event/subscription_criteria_met.php b/admin/tool/monitor/classes/event/subscription_criteria_met.php
new file mode 100644 (file)
index 0000000..693bcf4
--- /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/>.
+
+/**
+ * The tool_monitor subscription criteria met event.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_monitor subscription criteria met event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - string subscriptionid: id of the subscription.
+ * }
+ *
+ * @package    tool_monitor
+ * @since      Moodle 2.8
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class subscription_criteria_met extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventsubcriteriamet', 'tool_monitor');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The criteria for the subscription with id '{$this->other['subscriptionid']}' was met.";
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['subscriptionid'])) {
+            throw new \coding_exception('The \'subscriptionid\' value must be set in other.');
+        }
+    }
+}
diff --git a/admin/tool/monitor/classes/event/subscription_deleted.php b/admin/tool/monitor/classes/event/subscription_deleted.php
new file mode 100644 (file)
index 0000000..e4b2bc0
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The tool_monitor subscription deleted event.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_monitor subscription deleted event class.
+ *
+ * @package    tool_monitor
+ * @since      Moodle 2.8
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class subscription_deleted extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'tool_monitor_subscriptions';
+        $this->data['crud'] = 'd';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventsubdeleted', 'tool_monitor');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' deleted the event monitor subscription with id '$this->objectid'.";
+    }
+}
diff --git a/admin/tool/monitor/classes/eventlist.php b/admin/tool/monitor/classes/eventlist.php
new file mode 100644 (file)
index 0000000..989bc3c
--- /dev/null
@@ -0,0 +1,221 @@
+<?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/>.
+
+/**
+ * Event documentation
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class for returning event information.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class eventlist {
+    /**
+     * Return all of the core event files.
+     *
+     * @return array Core events.
+     */
+    protected static function get_core_eventlist() {
+        global $CFG;
+
+        // Disable developer debugging as deprecated events will fire warnings.
+        // Setup backup variables to restore the following settings back to what they were when we are finished.
+        $debuglevel          = $CFG->debug;
+        $debugdisplay        = $CFG->debugdisplay;
+        $debugdeveloper      = $CFG->debugdeveloper;
+        $CFG->debug          = 0;
+        $CFG->debugdisplay   = false;
+        $CFG->debugdeveloper = false;
+
+        $eventinformation = array();
+        $directory = $CFG->libdir . '/classes/event';
+        $files = self::get_file_list($directory);
+
+        // Remove exceptional events that will cause problems being displayed.
+        if (isset($files['unknown_logged'])) {
+            unset($files['unknown_logged']);
+        }
+        foreach ($files as $file => $location) {
+            $classname = '\\core\\event\\' . $file;
+            // Check to see if this is actually a valid event.
+            if (method_exists($classname, 'get_static_info')) {
+                $ref = new \ReflectionClass($classname);
+                // Ignore abstracts.
+                if (!$ref->isAbstract() && $file != 'manager') {
+                    $eventinformation[$classname] = $classname::get_name();
+                }
+            }
+        }
+        // Now enable developer debugging as event information has been retrieved.
+        $CFG->debug          = $debuglevel;
+        $CFG->debugdisplay   = $debugdisplay;
+        $CFG->debugdeveloper = $debugdeveloper;
+        return $eventinformation;
+    }
+
+    /**
+     * This function returns an array of all events for the plugins of the system.
+     *
+     * @param bool $withoutcomponent Return an eventlist without associated components.
+     *
+     * @return array A list of events from all plug-ins.
+     */
+    protected static function get_non_core_eventlist($withoutcomponent = false) {
+        global $CFG;
+        // Disable developer debugging as deprecated events will fire warnings.
+        // Setup backup variables to restore the following settings back to what they were when we are finished.
+        $debuglevel          = $CFG->debug;
+        $debugdisplay        = $CFG->debugdisplay;
+        $debugdeveloper      = $CFG->debugdeveloper;
+        $CFG->debug          = 0;
+        $CFG->debugdisplay   = false;
+        $CFG->debugdeveloper = false;
+
+        $noncorepluginlist = array();
+        $plugintypes = \core_component::get_plugin_types();
+        foreach ($plugintypes as $plugintype => $notused) {
+            $pluginlist = \core_component::get_plugin_list($plugintype);
+            foreach ($pluginlist as $plugin => $directory) {
+                $plugindirectory = $directory . '/classes/event';
+                foreach (self::get_file_list($plugindirectory) as $eventname => $notused) {
+                    $plugineventname = '\\' . $plugintype . '_' . $plugin . '\\event\\' . $eventname;
+                    // Check that this is actually an event.
+                    if (method_exists($plugineventname, 'get_static_info')  && $plugin != 'monitor') { // No selfie here.
+                        $ref = new \ReflectionClass($plugineventname);
+                        if (!$ref->isAbstract() && $plugin != 'legacy') {
+                            if ($withoutcomponent) {
+                                $noncorepluginlist[$plugineventname] = $plugineventname::get_name();
+                            } else {
+                                $noncorepluginlist[$plugintype . '_' . $plugin][$plugineventname] = $plugineventname::get_name();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // Now enable developer debugging as event information has been retrieved.
+        $CFG->debug          = $debuglevel;
+        $CFG->debugdisplay   = $debugdisplay;
+        $CFG->debugdeveloper = $debugdeveloper;
+
+        return $noncorepluginlist;
+    }
+
+    /**
+     * Returns a list of files with a full directory path in a specified directory.
+     *
+     * @param string $directory location of files.
+     * @return array full location of files from the specified directory.
+     */
+    protected static function get_file_list($directory) {
+        global $CFG;
+        $directoryroot = $CFG->dirroot;
+        $finalfiles = array();
+        if (is_dir($directory)) {
+            if ($handle = opendir($directory)) {
+                $files = scandir($directory);
+                foreach ($files as $file) {
+                    if ($file != '.' && $file != '..') {
+                        // Ignore the file if it is external to the system.
+                        if (strrpos($directory, $directoryroot) !== false) {
+                            $location = substr($directory, strlen($directoryroot));
+                            $name = substr($file, 0, -4);
+                            $finalfiles[$name] = $location  . '/' . $file;
+                        }
+                    }
+                }
+            }
+        }
+        return $finalfiles;
+    }
+
+    /**
+     * Get a list of events present in the system.
+     *
+     * @param bool $withoutcomponent Return an eventlist without associated components.
+     *
+     * @return array list of events present in the system.
+     */
+    public static function get_all_eventlist($withoutcomponent = false) {
+        if ($withoutcomponent) {
+            $return = array_merge(self::get_core_eventlist(), self::get_non_core_eventlist($withoutcomponent));
+            array_multisort($return, SORT_NATURAL);
+        } else {
+            $return = array_merge(array('core' => self::get_core_eventlist()),
+                    self::get_non_core_eventlist($withoutcomponent = false));
+        }
+        return $return;
+    }
+
+    /**
+     * Return list of plugins that have events.
+     *
+     * @param array $eventlist a list of events present in the system {@link eventlist::get_all_eventlist}.
+     *
+     * @return array list of plugins with human readable name.
+     */
+    public static function get_plugin_list($eventlist = array()) {
+        if (empty($eventlist)) {
+            $eventlist = self::get_all_eventlist();
+        }
+        $plugins = array_keys($eventlist);
+        $return = array();
+        foreach ($plugins as $plugin) {
+            if ($plugin === 'core') {
+                $return[$plugin] = get_string('core', 'tool_monitor');
+            } else if (get_string_manager()->string_exists('pluginname', $plugin)) {
+                $return[$plugin] = get_string('pluginname', $plugin);
+            } else {
+                $return[$plugin] = $plugin;
+            }
+        }
+
+        return $return;
+    }
+
+    /**
+     * validate if the given event belongs to the given plugin.
+     *
+     * @param string $plugin Frankenstyle name of the plugin.
+     * @param string $eventname Full qualified event name.
+     * @param array $eventlist List of events generated by {@link eventlist::get_all_eventlist}
+     *
+     * @return bool Returns true if the selected event belongs to the selected plugin, false otherwise.
+     */
+    public static function validate_event_plugin($plugin, $eventname, $eventlist = array()) {
+        if (empty($eventlist)) {
+            $eventlist = self::get_all_eventlist();
+        }
+        if (isset($eventlist[$plugin][$eventname])) {
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/admin/tool/monitor/classes/eventobservers.php b/admin/tool/monitor/classes/eventobservers.php
new file mode 100644 (file)
index 0000000..2ffc4fd
--- /dev/null
@@ -0,0 +1,238 @@
+<?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/>.
+
+/**
+ * Observer class containing methods monitoring various events.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Observer class containing methods monitoring various events.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class eventobservers {
+
+    /** @var array $buffer buffer of events. */
+    protected $buffer = array();
+
+    /** @var int Number of entries in the buffer. */
+    protected $count = 0;
+
+    /** @var  eventobservers a reference to a self instance. */
+    protected static $instance;
+
+    /**
+     * Course delete event observer.
+     * This observer monitors course delete event, and when a course is deleted it deletes any rules and subscriptions associated
+     * with it, so no orphan data is left behind.
+     *
+     * @param \core\event\course_deleted $event The course deleted event.
+     */
+    public static function course_deleted(\core\event\course_deleted $event) {
+        $rules = rule_manager::get_rules_by_courseid($event->courseid);
+        $context = null;
+        if ($event->contextlevel == CONTEXT_COURSE) {
+            $context = $event->get_context();
+        }
+        foreach ($rules as $rule) {
+            rule_manager::delete_rule($rule->id, $context);
+        }
+    }
+
+    /**
+     * The observer monitoring all the events.
+     *
+     * This observers puts small event objects in buffer for later writing to the database. At the end of the request the buffer
+     * is cleaned up and all data dumped into the tool_monitor_events table.
+     *
+     * @param \core\event\base $event event object
+     */
+    public static function process_event(\core\event\base $event) {
+
+        if (empty(self::$instance)) {
+            self::$instance = new static();
+            // Register shutdown handler - this is useful for buffering, processing events, etc.
+            \core_shutdown_manager::register_function(array(self::$instance, 'process_buffer'));
+        }
+
+        self::$instance->buffer_event($event);
+
+        if (PHPUNIT_TEST) {
+            // Process buffer after every event when unit testing.
+            self::$instance->process_buffer();
+
+        }
+    }
+
+    /**
+     * Api to buffer events to store, to reduce db queries.
+     *
+     * @param \core\event\base $event
+     */
+    protected function buffer_event(\core\event\base $event) {
+
+        $eventdata = $event->get_data();
+        $eventobj = new \stdClass();
+        $eventobj->eventname = $eventdata['eventname'];
+        $eventobj->contextid = $eventdata['contextid'];
+        $eventobj->contextlevel = $eventdata['contextlevel'];
+        $eventobj->contextinstanceid = $eventdata['contextinstanceid'];
+        if ($event->get_url()) {
+            // Get link url if exists.
+            $eventobj->link = $event->get_url()->out();
+        } else {
+            $eventobj->link = '';
+        }
+        $eventobj->courseid = $eventdata['courseid'];
+        $eventobj->timecreated = $eventdata['timecreated'];
+
+        $this->buffer[] = $eventobj;
+        $this->count++;
+    }
+
+    /**
+     * This method process all events stored in the buffer.
+     *
+     * This is a multi purpose api. It does the following:-
+     * 1. Write event data to tool_monitor_events
+     * 2. Find out users that need to be notified about rule completion and schedule a task to send them messages.
+     */
+    public function process_buffer() {
+        global $DB;
+
+        $events = $this->flush(); // Flush data.
+
+        $select = "SELECT COUNT(id) FROM {tool_monitor_events} ";
+        $now = time();
+        $messagestosend = array();
+
+        // Let us now process the events and check for subscriptions.
+        foreach ($events as $eventobj) {
+            $subscriptions = subscription_manager::get_subscriptions_by_event($eventobj);
+            $idstosend = array();
+            foreach ($subscriptions as $subscription) {
+                $starttime = $now - $subscription->timewindow;
+                if ($subscription->courseid == 0) {
+                    // Site level subscription. Count all events.
+                    $where = "eventname = :eventname AND timecreated >  :starttime";
+                    $params = array('eventname' => $eventobj->eventname, 'starttime' => $starttime);
+                } else {
+                    // Course level subscription.
+                    if ($subscription->cmid == 0) {
+                        // All modules.
+                        $where = "eventname = :eventname AND courseid = :courseid AND timecreated > :starttime";
+                        $params = array('eventname' => $eventobj->eventname, 'courseid' => $eventobj->courseid,
+                                'starttime' => $starttime);
+                    } else {
+                        // Specific module.
+                        $where = "eventname = :eventname AND courseid = :courseid AND contextinstanceid = :cmid
+                                AND timecreated > :starttime";
+                        $params = array('eventname' => $eventobj->eventname, 'courseid' => $eventobj->courseid,
+                                'cmid' => $eventobj->contextinstanceid, 'starttime' => $starttime);
+
+                    }
+                }
+                $sql = $select . "WHERE " . $where;
+                $count = $DB->count_records_sql($sql, $params);
+                if (!empty($count) && $count >= $subscription->frequency) {
+                    $idstosend[] = $subscription->id;
+
+                    // Trigger a subscription_criteria_met event.
+                    // It's possible that the course has been deleted since the criteria was met, so in that case use
+                    // the system context. Set it here and change later if needed.
+                    $context = \context_system::instance();
+                    // We can't perform if (!empty($subscription->courseid)) below as it uses the magic method
+                    // __get to return the variable, which will always result in being empty.
+                    $courseid = $subscription->courseid;
+                    if (!empty($courseid)) {
+                        if ($coursecontext = \context_course::instance($courseid, IGNORE_MISSING)) {
+                            $context = $coursecontext;
+                        }
+                    }
+
+                    $params = array(
+                        'userid' => $subscription->userid,
+                        'courseid' => $subscription->courseid,
+                        'context' => $context,
+                        'other' => array(
+                            'subscriptionid' => $subscription->id
+                        )
+                    );
+                    $event = \tool_monitor\event\subscription_criteria_met::create($params);
+                    $event->trigger();
+                }
+            }
+            if (!empty($idstosend)) {
+                $messagestosend[] = array('subscriptionids' => $idstosend, 'event' => $eventobj);
+            }
+        }
+
+        // Schedule a task to send notification.
+        if (!empty($messagestosend)) {
+            $adhocktask = new notification_task();
+            $adhocktask->set_custom_data($messagestosend);
+            $adhocktask->set_component('tool_monitor');
+            \core\task\manager::queue_adhoc_task($adhocktask);
+        }
+    }
+
+    /**
+     * Protected method that flushes the buffer of events and writes them to the database.
+     *
+     * @return array a copy of the events buffer.
+     */
+    protected function flush() {
+        global $DB;
+
+        // Flush the buffer to the db.
+        $events = $this->buffer;
+        $DB->insert_records('tool_monitor_events', $events); // Insert the whole chunk into the database.
+        $this->buffer = array();
+        $this->count = 0;
+        return $events;
+    }
+
+    /**
+     * Observer that monitors user deleted event and delete user subscriptions.
+     *
+     * @param \core\event\user_deleted $event the event object.
+     */
+    public static function user_deleted(\core\event\user_deleted $event) {
+        $userid = $event->objectid;
+        subscription_manager::delete_user_subscriptions($userid);
+    }
+
+    /**
+     * Observer that monitors course module deleted event and delete user subscriptions.
+     *
+     * @param \core\event\course_module_deleted $event the event object.
+     */
+    public static function course_module_deleted(\core\event\course_module_deleted $event) {
+        $cmid = $event->contextinstanceid;
+        subscription_manager::delete_cm_subscriptions($cmid);
+    }
+}
diff --git a/admin/tool/monitor/classes/notification_task.php b/admin/tool/monitor/classes/notification_task.php
new file mode 100644 (file)
index 0000000..ffb1f4a
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file defines an adhoc task to send notifications.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Adhock class, used to send notifications to users.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class notification_task extends \core\task\adhoc_task {
+
+    /**
+     * Send out messages.
+     */
+    public function execute() {
+        foreach ($this->get_custom_data() as $data) {
+            $eventobj = $data->event;
+            $subscriptionids = $data->subscriptionids;
+            foreach ($subscriptionids as $id) {
+                if ($message = $this->generate_message($id, $eventobj)) {
+                    message_send($message);
+                }
+            }
+        }
+    }
+
+    /**
+     * Generates the message object for a give subscription and event.
+     *
+     * @param int $subscriptionid Subscription instance
+     * @param \stdClass $eventobj Event data
+     *
+     * @return false|\stdClass message object
+     */
+    protected function generate_message($subscriptionid, \stdClass $eventobj) {
+
+        try {
+            $subscription = subscription_manager::get_subscription($subscriptionid);
+        } catch (\dml_exception $e) {
+            // Race condition, someone deleted the subscription.
+            return false;
+        }
+        $user = \core_user::get_user($subscription->userid);
+        $context = \context_user::instance($user->id, IGNORE_MISSING);
+        if ($context === false) {
+            // User context doesn't exist. Should never happen, nothing to do return.
+            return false;
+        }
+
+        $template = $subscription->template;
+        $template = $this->replace_placeholders($template, $subscription, $eventobj, $context);
+        $msgdata = new \stdClass();
+        $msgdata->component         = 'tool_monitor'; // Your component name.
+        $msgdata->name              = 'notification'; // This is the message name from messages.php.
+        $msgdata->userfrom          = \core_user::get_noreply_user();
+        $msgdata->userto            = $user;
+        $msgdata->subject           = $subscription->get_name($context);
+        $msgdata->fullmessage       = format_text($template, $subscription->templateformat, array('context' => $context));
+        $msgdata->fullmessageformat = $subscription->templateformat;
+        $msgdata->fullmessagehtml   = format_text($template, $subscription->templateformat, array('context' => $context));
+        $msgdata->smallmessage      = '';
+        $msgdata->notification      = 1; // This is only set to 0 for personal messages between users.
+
+        return $msgdata;
+    }
+
+    /**
+     * Replace place holders in the template with respective content.
+     *
+     * @param string $template Message template.
+     * @param subscription $subscription subscription instance
+     * @param \stdclass $eventobj Event data
+     * @param \context $context context object
+     *
+     * @return mixed final template string.
+     */
+    protected function replace_placeholders($template, subscription $subscription, $eventobj, $context) {
+        $template = str_replace('{link}', $eventobj->link, $template);
+        if ($eventobj->contextlevel == CONTEXT_MODULE && !empty($eventobj->contextinstanceid)
+            && (strpos($template, '{modulelink}') !== false)) {
+            $cm = get_fast_modinfo($eventobj->courseid)->get_cm($eventobj->contextinstanceid);
+            $modulelink = $cm->url;
+            $template = str_replace('{modulelink}', $modulelink, $template);
+        }
+        $template = str_replace('{rulename}', $subscription->get_name($context), $template);
+        $template = str_replace('{description}', $subscription->get_description($context), $template);
+        $template = str_replace('{eventname}', $subscription->get_event_name(), $template);
+
+        return $template;
+    }
+}
diff --git a/admin/tool/monitor/classes/output/helpicon/renderable.php b/admin/tool/monitor/classes/output/helpicon/renderable.php
new file mode 100644 (file)
index 0000000..3cb0395
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The file for the renderable class for the tool_monitor help icon.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\output\helpicon;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Renderable class for the tool_monitor help icon.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class renderable implements \renderable {
+
+    /**
+     * @var string $type the type we are displaying the help icon for (either rule or subscription).
+     */
+    public $type;
+
+    /**
+     * @var int $id the id of the type.
+     */
+    public $id;
+
+    /**
+     * The constructor.
+     *
+     * @param string $type the type we are displaying the help icon for (either rule or subscription).
+     * @param int $id the id of the type.
+     */
+    public function __construct($type, $id) {
+        $this->type = $type;
+        $this->id = $id;
+    }
+
+    /**
+     * Returns the string to display for the help icon.
+     *
+     * @param string $type the type we are displaying the help icon for (either rule or subscription).
+     * @param int $id the id of the type.
+     * @param boolean $ajax Whether this help is called from an AJAX script.
+     *      This is used to influence text formatting and determines which format to output the doclink in.
+     * @return string|object|array $a An object, string or number that can be used within translation strings
+     */
+    public static function get_help_string_parameters($type, $id, $ajax = false) {
+        if ($type == 'rule') {
+            $rule = \tool_monitor\rule_manager::get_rule($id);
+
+            $langstring = new \stdClass();
+            $langstring->eventname = $rule->get_event_name();
+            $langstring->eventcomponent = $rule->get_plugin_name();
+            $langstring->frequency = $rule->frequency;
+            $langstring->minutes = $rule->timewindow / MINSECS;
+
+            return get_formatted_help_string('rulehelp', 'tool_monitor', $ajax, $langstring);
+        }
+
+        // Must be a subscription.
+        $sub = \tool_monitor\subscription_manager::get_subscription($id);
+
+        $langstring = new \stdClass();
+        $langstring->eventname = $sub->get_event_name();
+        $langstring->moduleinstance = $sub->get_instance_name();
+        $langstring->frequency = $sub->frequency;
+        $langstring->minutes = $sub->timewindow / MINSECS;
+
+        return get_formatted_help_string('subhelp', 'tool_monitor', $ajax, $langstring);
+    }
+}
diff --git a/admin/tool/monitor/classes/output/helpicon/renderer.php b/admin/tool/monitor/classes/output/helpicon/renderer.php
new file mode 100644 (file)
index 0000000..99025f2
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The file for the renderer class for the tool_monitor help icon.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\output\helpicon;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Renderer class for tool_monitor help icons.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class renderer extends \plugin_renderer_base {
+
+    /**
+     * Get the HTML for the help icon.
+     *
+     * @param renderable $renderable renderable widget
+     *
+     * @return string the HTML of the help icon to display.
+     */
+    protected function render_renderable(renderable $renderable) {
+        global $CFG;
+
+        // First get the help image icon.
+        $src = $this->pix_url('help');
+
+        if ($renderable->type == 'rule') {
+            $title = get_string('rulehelp', 'tool_monitor');
+        } else { // Must be a subscription.
+            $title = get_string('subhelp', 'tool_monitor');
+        }
+
+        $alt = get_string('helpwiththis');
+
+        $attributes = array('src' => $src, 'alt' => $alt, 'class' => 'iconhelp');
+        $output = \html_writer::empty_tag('img', $attributes);
+
+        // Now create the link around it - we need https on loginhttps pages.
+        $urlparams = array();
+        $urlparams['type'] = $renderable->type;
+        $urlparams['id'] = $renderable->id;
+        $urlparams['lang'] = current_language();
+        $url = new \moodle_url($CFG->httpswwwroot . '/admin/tool/monitor/help.php', $urlparams);
+
+        // Note: this title is displayed only if JS is disabled, otherwise the link will have the new ajax tooltip.
+        $title = get_string('helpprefix2', '', trim($title, ". \t"));
+
+        $attributes = array('href' => $url, 'title' => $title, 'aria-haspopup' => 'true', 'target' => '_blank');
+        $output = \html_writer::tag('a', $output, $attributes);
+
+        // Now, finally the span.
+        return \html_writer::tag('span', $output, array('class' => 'helptooltip'));
+    }
+}
diff --git a/admin/tool/monitor/classes/output/managerules/renderable.php b/admin/tool/monitor/classes/output/managerules/renderable.php
new file mode 100644 (file)
index 0000000..0819aae
--- /dev/null
@@ -0,0 +1,201 @@
+<?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/>.
+
+/**
+ * Renderable class for manage rules page.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\output\managerules;
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->libdir . '/tablelib.php');
+
+/**
+ * Renderable class for manage rules page.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class renderable extends \table_sql implements \renderable {
+
+    /**
+     * @var int course id.
+     */
+    public $courseid;
+
+    /**
+     * @var \context_course|\context_system context of the page to be rendered.
+     */
+    protected $context;
+
+    /**
+     * @var bool Does the user have capability to manage rules at site context.
+     */
+    protected $hassystemcap;
+
+    /**
+     * Sets up the table_log parameters.
+     *
+     * @param string $uniqueid unique id of form.
+     * @param \moodle_url $url url where this table is displayed.
+     * @param int $courseid course id.
+     * @param int $perpage Number of rules to display per page.
+     */
+    public function __construct($uniqueid, \moodle_url $url, $courseid = 0, $perpage = 100) {
+        parent::__construct($uniqueid);
+
+        $this->set_attribute('class', 'toolmonitor managerules generaltable generalbox');
+        $this->define_columns(array('name', 'description', 'plugin', 'eventname', 'filters', 'manage'));
+        $this->define_headers(array(
+                get_string('name'),
+                get_string('description'),
+                get_string('plugin'),
+                get_string('eventname'),
+                get_string('frequency', 'tool_monitor'),
+                get_string('manage', 'tool_monitor'),
+            )
+        );
+        $this->courseid = $courseid;
+        $this->pagesize = $perpage;
+        $systemcontext = \context_system::instance();
+        $this->context = empty($courseid) ? $systemcontext : \context_course::instance($courseid);
+        $this->hassystemcap = has_capability('tool/monitor:managerules', $systemcontext);
+        $this->collapsible(false);
+        $this->sortable(false);
+        $this->pageable(true);
+        $this->is_downloadable(false);
+        $this->define_baseurl($url);
+    }
+
+    /**
+     * Generate content for name column.
+     *
+     * @param \tool_monitor\rule $rule rule object
+     *
+     * @return string html used to display the column field.
+     */
+    public function col_name(\tool_monitor\rule $rule) {
+        return $rule->get_name($this->context);
+    }
+
+    /**
+     * Generate content for description column.
+     *
+     * @param \tool_monitor\rule $rule rule object
+     *
+     * @return string html used to display the column field.
+     */
+    public function col_description(\tool_monitor\rule $rule) {
+        return $rule->get_description($this->context);
+    }
+
+    /**
+     * Generate content for plugin column.
+     *
+     * @param \tool_monitor\rule $rule rule object
+     *
+     * @return string html used to display the column field.
+     */
+    public function col_plugin(\tool_monitor\rule $rule) {
+        return $rule->get_plugin_name();
+    }
+
+    /**
+     * Generate content for eventname column.
+     *
+     * @param \tool_monitor\rule $rule rule object
+     *
+     * @return string html used to display the column field.
+     */
+    public function col_eventname(\tool_monitor\rule $rule) {
+        return $rule->get_event_name();
+    }
+
+    /**
+     * Generate content for filters column.
+     *
+     * @param \tool_monitor\rule $rule rule object
+     *
+     * @return string html used to display the filters column field.
+     */
+    public function col_filters(\tool_monitor\rule $rule) {
+        return $rule->get_filters_description();
+    }
+
+    /**
+     * Generate content for manage column.
+     *
+     * @param \tool_monitor\rule $rule rule object
+     *
+     * @return string html used to display the manage column field.
+     */
+    public function col_manage(\tool_monitor\rule $rule) {
+        global $OUTPUT, $CFG;
+        $manage = '';
+        // We don't need to check for capability at course level since, user is never shown this page,
+        // if he doesn't have the capability.
+        if ($this->hassystemcap || ($rule->courseid != 0)) {
+            // There might be site rules which the user can not manage.
+            $editurl = new \moodle_url($CFG->wwwroot. '/admin/tool/monitor/edit.php', array('ruleid' => $rule->id,
+                    'courseid' => $rule->courseid, 'sesskey' => sesskey()));
+            $copyurl = new \moodle_url($CFG->wwwroot. '/admin/tool/monitor/managerules.php',
+                    array('ruleid' => $rule->id, 'action' => 'copy', 'courseid' => $this->courseid, 'sesskey' => sesskey()));
+            $deleteurl = new \moodle_url($CFG->wwwroot. '/admin/tool/monitor/managerules.php', array('ruleid' => $rule->id,
+                    'action' => 'delete', 'courseid' => $rule->courseid, 'sesskey' => sesskey()));
+
+            $icon = $OUTPUT->render(new \pix_icon('t/edit', get_string('editrule', 'tool_monitor')));
+            $manage .= \html_writer::link($editurl, $icon, array('class' => 'action-icon'));
+
+            $icon = $OUTPUT->render(new \pix_icon('t/copy', get_string('duplicaterule', 'tool_monitor')));
+            $manage .= \html_writer::link($copyurl, $icon, array('class' => 'action-icon'));
+
+            $a = $rule->get_name($this->context);
+            $action = new \component_action('click', 'M.util.show_confirm_dialog', array('message' => get_string('ruleareyousure',
+                    'tool_monitor', $a)));
+            $icon = $OUTPUT->action_link($deleteurl, new \pix_icon('t/delete', get_string('deleterule', 'tool_monitor')), $action);
+            $manage .= $icon;
+        } else {
+            $manage = get_string('nopermission', 'tool_monitor');
+        }
+        return $manage;
+    }
+
+    /**
+     * Query the reader. Store results in the object for use by build_table.
+     *
+     * @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) {
+
+        $total = \tool_monitor\rule_manager::count_rules_by_courseid($this->courseid);
+        $this->pagesize($pagesize, $total);
+        $rules = \tool_monitor\rule_manager::get_rules_by_courseid($this->courseid, $this->get_page_start(),
+                $this->get_page_size());
+        $this->rawdata = $rules;
+        // Set initial bars.
+        if ($useinitialsbar) {
+            $this->initialbars($total > $pagesize);
+        }
+    }
+}
diff --git a/admin/tool/monitor/classes/output/managerules/renderer.php b/admin/tool/monitor/classes/output/managerules/renderer.php
new file mode 100644 (file)
index 0000000..889817d
--- /dev/null
@@ -0,0 +1,84 @@
+<?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 manage rules page.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\output\managerules;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Renderer class for manage rules page.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class renderer extends \plugin_renderer_base {
+
+    /**
+     * Get html to display on the page.
+     *
+     * @param renderable $renderable renderable widget
+     *
+     * @return string to display on the mangerules page.
+     */
+    protected function render_renderable(renderable $renderable) {
+        $o = $this->render_table($renderable);
+        $o .= $this->render_add_button($renderable->courseid);
+
+        return $o;
+    }
+
+    /**
+     * Get html to display on the page.
+     *
+     * @param renderable $renderable renderable widget
+     *
+     * @return string to display on the mangerules page.
+     */
+    protected function render_table(renderable $renderable) {
+        $o = '';
+        ob_start();
+        $renderable->out($renderable->pagesize, true);
+        $o = ob_get_contents();
+        ob_end_clean();
+
+        return $o;
+    }
+
+    /**
+     * Html to add a button for adding a new rul.
+     *
+     * @param int $courseid course id.
+     *
+     * @return string html for the button.
+     */
+    protected function render_add_button($courseid) {
+        global $CFG;
+
+        $button = \html_writer::tag('button', get_string('addrule', 'tool_monitor'));
+        $addurl = new \moodle_url($CFG->wwwroot. '/admin/tool/monitor/edit.php', array('courseid' => $courseid));
+        return \html_writer::link($addurl, $button);
+    }
+}
diff --git a/admin/tool/monitor/classes/output/managesubs/renderer.php b/admin/tool/monitor/classes/output/managesubs/renderer.php
new file mode 100644 (file)
index 0000000..fe3f8e0
--- /dev/null
@@ -0,0 +1,94 @@
+<?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 manage subscriptions page.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\output\managesubs;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Renderer class for manage subscriptions page.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class renderer extends \plugin_renderer_base {
+
+    /**
+     * Get html to display on the page.
+     *
+     * @param subs $renderable renderable widget
+     *
+     * @return string to display on the mangesubs page.
+     */
+    protected function render_subs(subs $renderable) {
+        $o = $this->render_table($renderable);
+        return $o;
+    }
+
+    /**
+     * Get html to display on the page.
+     *
+     * @param rules $renderable renderable widget
+     *
+     * @return string to display on the mangesubs page.
+     */
+    protected function render_rules(rules $renderable) {
+        $o = $this->render_course_select($renderable);
+        if (!empty($renderable->totalcount)) {
+            $o .= $this->render_table($renderable);
+        }
+        return $o;
+    }
+
+    /**
+     * Get html to display on the page for select dropdown..
+     *
+     * @param rules $renderable renderable widget
+     *
+     * @return string to display on the mangesubs page.
+     */
+    protected function render_course_select(rules $renderable) {
+        $select = $renderable->get_user_courses_select();
+        return $this->render($select);;
+    }
+
+    /**
+     * Get html to display on the page.
+     *
+     * @param rules|subs $renderable renderable widget
+     *
+     * @return string to display on the mangesubs page.
+     */
+    protected function render_table($renderable) {
+        $o = '';
+        ob_start();
+        $renderable->out($renderable->pagesize, true);
+        $o = ob_get_contents();
+        ob_end_clean();
+
+        return $o;
+    }
+}
diff --git a/admin/tool/monitor/classes/output/managesubs/rules.php b/admin/tool/monitor/classes/output/managesubs/rules.php
new file mode 100644 (file)
index 0000000..f891e90
--- /dev/null
@@ -0,0 +1,171 @@
+<?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/>.
+
+/**
+ * Renderable class to display a set of rules in the manage subscriptions page.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\output\managesubs;
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->libdir . '/tablelib.php');
+
+/**
+ * Renderable class to display a set of rules in the manage subscriptions page.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class rules extends \table_sql implements \renderable {
+
+    /**
+     * @var int course id.
+     */
+    public $courseid;
+
+    /**
+     * @var int total rules present.
+     */
+    public $totalcount = 0;
+
+    /**
+     * @var \context_course|\context_system context of the page to be rendered.
+     */
+    protected $context;
+
+    /**
+     * @var \tool_monitor\output\helpicon\renderer the help icon renderer.
+     */
+    protected $helpiconrenderer;
+
+    /**
+     * Sets up the table_log parameters.
+     *
+     * @param string $uniqueid unique id of form.
+     * @param \moodle_url $url url where this table is displayed.
+     * @param int $courseid course id.
+     * @param int $perpage Number of rules to display per page.
+     */
+    public function __construct($uniqueid, \moodle_url $url, $courseid = 0, $perpage = 100) {
+        global $PAGE;
+
+        parent::__construct($uniqueid);
+
+        $this->set_attribute('class', 'toolmonitor subscriberules generaltable generalbox');
+        $this->define_columns(array('name', 'description', 'select'));
+        $this->define_headers(array(
+                get_string('name'),
+                get_string('description'),
+                get_string('select')
+            )
+        );
+        $this->courseid = $courseid;
+        $this->pagesize = $perpage;
+        $systemcontext = \context_system::instance();
+        $this->context = empty($courseid) ? $systemcontext : \context_course::instance($courseid);
+        $this->collapsible(false);
+        $this->sortable(false);
+        $this->pageable(true);
+        $this->is_downloadable(false);
+        $this->define_baseurl($url);
+        $this->helpiconrenderer = $PAGE->get_renderer('tool_monitor', 'helpicon');
+        $total = \tool_monitor\rule_manager::count_rules_by_courseid($this->courseid);
+        $this->totalcount = $total;
+    }
+
+    /**
+     * Generate content for name column.
+     *
+     * @param \tool_monitor\rule $rule rule object
+     *
+     * @return string html used to display the column field.
+     */
+    public function col_name(\tool_monitor\rule $rule) {
+        $name = $rule->get_name($this->context);
+        $helpicon = new \tool_monitor\output\helpicon\renderable('rule', $rule->id);
+        $helpicon = $this->helpiconrenderer->render($helpicon);
+
+        return $name . $helpicon;
+    }
+
+    /**
+     * Generate content for description column.
+     *
+     * @param \tool_monitor\rule $rule rule object
+     *
+     * @return string html used to display the column field.
+     */
+    public function col_description(\tool_monitor\rule $rule) {
+        return $rule->get_description($this->context);
+    }
+
+    /**
+     * Generate content for plugin column.
+     *
+     * @param \tool_monitor\rule $rule rule object
+     *
+     * @return string html used to display the column field.
+     */
+    public function col_select(\tool_monitor\rule $rule) {
+        global $OUTPUT;
+        $select = $rule->get_module_select($this->courseid);
+        return is_object($select) ? $OUTPUT->render($select) : $select;
+    }
+
+    /**
+     * Query the reader. Store results in the object for use by build_table.
+     *
+     * @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) {
+
+        $total = \tool_monitor\rule_manager::count_rules_by_courseid($this->courseid);
+        $this->pagesize($pagesize, $total);
+        $rules = \tool_monitor\rule_manager::get_rules_by_courseid($this->courseid, $this->get_page_start(),
+                $this->get_page_size());
+        $this->rawdata = $rules;
+        // Set initial bars.
+        if ($useinitialsbar) {
+            $this->initialbars($total > $pagesize);
+        }
+    }
+
+    /**
+     * Gets a list of courses where the current user can subscribe to rules as a dropdown.
+     *
+     * @return \single_select list of courses.
+     */
+    public function get_user_courses_select() {
+        $courses = get_user_capability_course('tool/monitor:subscribe', null, true, 'fullname');
+        $options = array(0 => get_string('site'));
+        $systemcontext = \context_system::instance();
+        foreach ($courses as $course) {
+            $options[$course->id] = format_text($course->fullname, array('context' => $systemcontext));
+        }
+        $url = new \moodle_url('/admin/tool/monitor/index.php');
+        $select = new \single_select($url, 'courseid', $options, $this->courseid);
+        $select->set_label(get_string('selectacourse', 'tool_monitor'));
+        return $select;
+    }
+}
diff --git a/admin/tool/monitor/classes/output/managesubs/subs.php b/admin/tool/monitor/classes/output/managesubs/subs.php
new file mode 100644 (file)
index 0000000..8ba616d
--- /dev/null
@@ -0,0 +1,182 @@
+<?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/>.
+
+/**
+ * Renderable class to display a set of subscriptions in the manage subscriptions page.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\output\managesubs;
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->libdir . '/tablelib.php');
+
+/**
+ * Renderable class to display a set of subscriptions in the manage subscriptions page.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class subs extends \table_sql implements \renderable {
+
+    /**
+     * @var int course id.
+     */
+    public $courseid;
+
+    /**
+     * @var \context_course|\context_system context of the page to be rendered.
+     */
+    protected $context;
+
+    /**
+     * @var \tool_monitor\output\helpicon\renderer the help icon renderer.
+     */
+    protected $helpiconrenderer;
+
+    /**
+     * Sets up the table_log parameters.
+     *
+     * @param string $uniqueid unique id of form.
+     * @param \moodle_url $url url where this table is displayed.
+     * @param int $courseid course id.
+     * @param int $perpage Number of rules to display per page.
+     */
+    public function __construct($uniqueid, \moodle_url $url, $courseid = 0, $perpage = 100) {
+        global $PAGE;
+
+        parent::__construct($uniqueid);
+
+        $this->set_attribute('class', 'toolmonitor subscriptions generaltable generalbox');
+        $this->define_columns(array('name', 'course', 'instance', 'unsubscribe', 'editrule'));
+        $this->define_headers(array(
+                get_string('name'),
+                get_string('course'),
+                get_string('moduleinstance', 'tool_monitor'),
+                get_string('unsubscribe', 'tool_monitor'),
+                get_string('editrule', 'tool_monitor')
+            )
+        );
+        $this->courseid = $courseid;
+        $this->pagesize = $perpage;
+        $systemcontext = \context_system::instance();
+        $this->context = empty($courseid) ? $systemcontext : \context_course::instance($courseid);
+        $this->collapsible(false);
+        $this->sortable(false);
+        $this->pageable(true);
+        $this->is_downloadable(false);
+        $this->define_baseurl($url);
+        $this->helpiconrenderer = $PAGE->get_renderer('tool_monitor', 'helpicon');
+    }
+
+    /**
+     * Generate content for name column.
+     *
+     * @param \tool_monitor\subscription $sub subscription object
+     *
+     * @return string html used to display the column field.
+     */
+    public function col_name(\tool_monitor\subscription $sub) {
+        $name = $sub->get_name($this->context);
+        $helpicon = new \tool_monitor\output\helpicon\renderable('subscription', $sub->id);
+        $helpicon = $this->helpiconrenderer->render($helpicon);
+
+        return $name . $helpicon;
+    }
+
+    /**
+     * Generate content for course column.
+     *
+     * @param \tool_monitor\subscription $sub subscription object
+     *
+     * @return string html used to display the column field.
+     */
+    public function col_course(\tool_monitor\subscription $sub) {
+       return $sub->get_course_name($this->context);
+    }
+
+    /**
+     * Generate content for description column.
+     *
+     * @param \tool_monitor\subscription $sub subscription object
+     *
+     * @return string html used to display the column field.
+     */
+    public function col_instance(\tool_monitor\subscription $sub) {
+        return $sub->get_instance_name();
+    }
+
+    /**
+     * Generate content for manage column.
+     *
+     * @param \tool_monitor\subscription $sub subscription object
+     *
+     * @return string html used to display the column field.
+     */
+    public function col_unsubscribe(\tool_monitor\subscription $sub) {
+        global $OUTPUT, $CFG;
+
+        $a = $sub->get_name($this->context);
+        $deleteurl = new \moodle_url($CFG->wwwroot. '/admin/tool/monitor/index.php', array('subscriptionid' => $sub->id,
+                'action' => 'unsubscribe', 'courseid' => $this->courseid, 'sesskey' => sesskey()));
+        $action = new \component_action('click', 'M.util.show_confirm_dialog', array('message' => get_string('subareyousure',
+            'tool_monitor', $a)));
+        $icon = $OUTPUT->action_link($deleteurl,
+                new \pix_icon('t/delete', get_string('deletesubscription', 'tool_monitor')), $action);
+        return $icon;
+    }
+
+    /**
+     * Generate content for edit rule column.
+     *
+     * @param \tool_monitor\subscription $sub subscription object
+     *
+     * @return string html used to display the column field.
+     */
+    public function col_editrule(\tool_monitor\subscription $sub) {
+        if ($sub->can_manage_rule()) {
+            // User can manage rule.
+            $editurl = new \moodle_url('/admin/tool/monitor/edit.php', array('ruleid' => $sub->ruleid,
+                    'courseid' => $sub->rulecourseid));
+            return \html_writer::link($editurl, get_string('editrule', 'tool_monitor'));
+        }
+        return '-';
+    }
+
+    /**
+     * Query the reader. Store results in the object for use by build_table.
+     *
+     * @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) {
+
+        $total = \tool_monitor\subscription_manager::count_user_subscriptions();
+        $this->pagesize($pagesize, $total);
+        $subs = \tool_monitor\subscription_manager::get_user_subscriptions($this->get_page_start(), $this->get_page_size());
+        $this->rawdata = $subs;
+        // Set initial bars.
+        if ($useinitialsbar) {
+            $this->initialbars($total > $pagesize);
+        }
+    }
+}
diff --git a/admin/tool/monitor/classes/rule.php b/admin/tool/monitor/classes/rule.php
new file mode 100644 (file)
index 0000000..81b3aef
--- /dev/null
@@ -0,0 +1,247 @@
+<?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 represents a single rule.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class represents a single rule.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class rule {
+
+    /**
+     * @var \stdClass The rule object form database.
+     */
+    protected $rule;
+
+    /**
+     * Constructor.
+     *
+     * @param \stdClass $rule A rule object from database.
+     */
+    public function __construct($rule) {
+        $this->rule = $rule;
+    }
+
+    /**
+     * Can the current user manage this rule?
+     *
+     * @return bool true if the current user can manage this rule, else false.
+     */
+    public function can_manage_rule() {
+        $courseid = $this->courseid;
+        $context = empty($courseid) ? \context_system::instance() : \context_course::instance($this->courseid);
+        return has_capability('tool/monitor:managerules', $context);
+    }
+
+    /**
+     * Api to duplicate a rule in a given courseid.
+     *
+     * @param int $finalcourseid Final course id.
+     */
+    public function duplicate_rule($finalcourseid) {
+        $rule = fullclone($this->rule);
+        unset($rule->id);
+        $rule->courseid = $finalcourseid;
+        $time = time();
+        $rule->timecreated = $time;
+        $rule->timemodified = $time;
+        rule_manager::add_rule($rule);
+    }
+
+    /**
+     * Delete this rule.
+     *
+     * Note: It also removes all associated subscriptions.
+     */
+    public function delete_rule() {
+        rule_manager::delete_rule($this->id);
+    }
+
+    /**
+     * Generate a select drop down with list of possible modules for a given course and rule.
+     *
+     * @param int $courseid course id
+     *
+     * @return \single_select a single select object
+     * @throws \coding_exception
+     */
+    public function get_module_select($courseid) {
+        global $CFG;
+        $options = array();
+        if (strpos($this->plugin, 'mod_') === 0) {
+            $options[0] = get_string('allmodules', 'tool_monitor');
+        } else {
+            $options[0] = get_string('allevents', 'tool_monitor');
+        }
+        if (strpos($this->plugin, 'mod_') === 0) {
+            if ($courseid == 0) {
+                // They need to be in a course to select module instance.
+                return get_string('selectcourse', 'tool_monitor');
+            }
+            // Let them select an instance.
+            $cms = get_fast_modinfo($courseid);
+            $instances = $cms->get_instances_of(str_replace('mod_', '',  $this->plugin));
+            foreach ($instances as $cminfo) {
+                // Don't list instances that are not visible or available to the user.
+                if ($cminfo->uservisible && $cminfo->available) {
+                    $options[$cminfo->id] = $cminfo->get_formatted_name();
+                }
+            }
+        }
+        $url = new \moodle_url($CFG->wwwroot. '/admin/tool/monitor/index.php', array('courseid' => $courseid, 'ruleid' => $this->id,
+                'action' => 'subscribe', 'sesskey' => sesskey()));
+        return new \single_select($url, 'cmid', $options, '', $nothing = array('' => 'choosedots'));
+    }
+
+    /**
+     * Subscribe an user to this rule.
+     *
+     * @param int $courseid Course id.
+     * @param int $cmid Course module id.
+     * @param int $userid User id.
+     *
+     * @throws \coding_exception
+     */
+    public function subscribe_user($courseid, $cmid, $userid = 0) {
+        global $USER;
+
+        if ($this->courseid != $courseid && $this->courseid != 0) {
+            // Trying to subscribe to a rule that belongs to a different course. Should never happen.
+            throw new \coding_exception('Can not subscribe to rules from a different course');
+        }
+        if ($cmid !== 0) {
+            $cms = get_fast_modinfo($courseid);
+            $cminfo = $cms->get_cm($cmid);
+            if (!$cminfo->uservisible || !$cminfo->available) {
+                // Trying to subscribe to a hidden or restricted cm. Should never happen.
+                throw new \coding_exception('You cannot do that');
+            }
+        }
+        $userid = empty($userid) ? $USER->id : $userid;
+
+        subscription_manager::create_subscription($this->id, $courseid, $cmid, $userid);
+    }
+
+    /**
+     * Magic get method.
+     *
+     * @param string $prop property to get.
+     *
+     * @return mixed
+     * @throws \coding_exception
+     */
+    public function __get($prop) {
+        if (property_exists($this->rule, $prop)) {
+            return $this->rule->$prop;
+        }
+        throw new \coding_exception('Property "' . $prop . '" doesn\'t exist');
+    }
+
+    /**
+     * Return the rule data to be used while setting mform.
+     *
+     * @throws \coding_exception
+     */
+    public function get_mform_set_data() {
+        if (!empty($this->rule)) {
+            $rule = fullclone($this->rule);
+            $rule->description = array('text' => $rule->description, 'format' => $rule->descriptionformat);
+            $rule->template = array('text' => $rule->template, 'format' => $rule->templateformat);
+            return $rule;
+        }
+        throw new \coding_exception('Invalid call to get_mform_set_data.');
+    }
+
+    /**
+     * Method to get event name.
+     *
+     * @return string
+     * @throws \coding_exception
+     */
+    public function get_event_name() {
+        $eventclass = $this->eventname;
+        if (class_exists($eventclass)) {
+            return $eventclass::get_name();
+        }
+        return get_string('eventnotfound', 'tool_monitor');
+    }
+
+    /**
+     * Get filter description.
+     *
+     * @return string
+     */
+    public function get_filters_description() {
+        $a = new \stdClass();
+        $a->freq = $this->frequency;
+        $mins = $this->timewindow / MINSECS; // Convert seconds to minutes.
+        $a->mins = $mins;
+        return get_string('freqdesc', 'tool_monitor', $a);
+    }
+
+    /**
+     * Get properly formatted name of the rule associated.
+     *
+     * @param \context $context context where this name would be displayed.
+     *
+     * @return string Formatted name of the rule.
+     */
+    public function get_name(\context $context) {
+        return format_text($this->name, FORMAT_HTML, array('context' => $context));
+    }
+
+    /**
+     * Get properly formatted description of the rule associated.
+     *
+     * @param \context $context context where this description would be displayed.
+     *
+     * @return string Formatted description of the rule.
+     */
+    public function get_description(\context $context) {
+        return format_text($this->description, $this->descriptionformat, array('context' => $context));
+    }
+
+    /**
+     * Get name of the plugin associated with this rule
+     *
+     * @return string Plugin name.
+     */
+    public function get_plugin_name() {
+        if ($this->plugin === 'core') {
+            $string = get_string('core', 'tool_monitor');
+        } else if (get_string_manager()->string_exists('pluginname', $this->plugin)) {
+            $string = get_string('pluginname', $this->plugin);
+        } else {
+            $string = $this->plugin;
+        }
+        return $string;
+    }
+}
diff --git a/admin/tool/monitor/classes/rule_form.php b/admin/tool/monitor/classes/rule_form.php
new file mode 100644 (file)
index 0000000..a069779
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The mform for creating and editing a rule.
+ *
+ * @copyright 2014 onwards Simey Lameze <lameze@gmail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package   tool_monitor
+ */
+
+namespace tool_monitor;
+
+require_once($CFG->dirroot.'/lib/formslib.php');
+
+/**
+ * The mform for creating and editing a rule.
+ *
+ * @since     Moodle 2.8
+ * @copyright 2014 onwards Simey Lameze <lameze@gmail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package   tool_monitor
+ */
+class rule_form extends \moodleform {
+
+    /**
+     * Mform class definition
+     *
+     */
+    public function definition () {
+        $mform = $this->_form;
+        $eventlist = $this->_customdata['eventlist'];
+        $pluginlist = $this->_customdata['pluginlist'];
+        $rule = $this->_customdata['rule'];
+        $courseid = $this->_customdata['courseid'];
+
+        // General section header.
+        $mform->addElement('header', 'general', get_string('general'));
+
+        // Hidden course ID.
+        $mform->addElement('hidden', 'courseid');
+        $mform->setType('courseid', PARAM_INT);
+
+        // We are editing a existing rule.
+        if (!empty($rule->id)) {
+            // Hidden rule id.
+            $mform->addElement('hidden', 'ruleid');
+            $mform->setType('ruleid', PARAM_INT);
+            $mform->setConstant('ruleid', $rule->id);
+
+            // Force course id.
+            $courseid = $rule->courseid;
+        }
+
+        // Make course id a constant.
+        $mform->setConstant('courseid', $courseid);
+
+        if (empty($courseid)) {
+            $context = \context_system::instance();
+        } else {
+            $context = \context_course::instance($courseid);
+        }
+
+        $editoroptions = array(
+            'subdirs' => 0,
+            'maxbytes' => 0,
+            'maxfiles' => 0,
+            'changeformat' => 0,
+            'context' => $context,
+            'noclean' => 0,
+            'trusttext' => 0
+        );
+
+        // Name field.
+        $mform->addElement('text', 'name', get_string('name', 'tool_monitor'), 'size="50"');
+        $mform->addRule('name', get_string('required'), 'required');
+        $mform->setType('name', PARAM_TEXT);
+        $mform->addHelpButton('name', 'name', 'tool_monitor');
+
+        // Plugin field.
+        $mform->addElement('select', 'plugin', get_string('selectplugin', 'tool_monitor'), $pluginlist);
+        $mform->addRule('plugin', get_string('required'), 'required');
+        $mform->addHelpButton('plugin', 'selectplugin', 'tool_monitor');
+
+        // Event field.
+        $mform->addElement('select', 'eventname', get_string('selectevent', 'tool_monitor'), $eventlist);
+        $mform->addRule('eventname', get_string('required'), 'required');
+        $mform->addHelpButton('eventname', 'selectevent', 'tool_monitor');
+
+        // Description field.
+        $mform->addElement('editor', 'description', get_string('description', 'tool_monitor'), $editoroptions);
+        $mform->addHelpButton('description', 'description', 'tool_monitor');
+
+        // Filters.
+        $mform->addElement('header', 'customisefilters', get_string('customisefilters', 'tool_monitor'));
+        $freq = array(1 => 1, 5 => 5, 10 => 10, 20 => 20, 30 => 30, 40 => 40, 50 => 50, 60 => 60, 70 => 70, 80 => 80, 90 => 90,
+                100 => 100, 1000 => 1000);
+        $mform->addElement('select', 'frequency', get_string('selectfrequency', 'tool_monitor'), $freq);
+        $mform->addRule('frequency', get_string('required'), 'required');
+        $mform->addHelpButton('frequency', 'selectfrequency', 'tool_monitor');
+
+        $mins = array(1 => 1, 5 => 5, 10 => 10, 15 => 15, 20 => 20, 25 => 25, 30 => 30, 35 => 35, 40 => 40, 45 => 45, 50 => 50,
+                55 => 55,  60 => 60);
+        $mform->addElement('select', 'minutes', get_string('selectminutes', 'tool_monitor'), $mins);
+        $mform->addRule('minutes', get_string('required'), 'required');
+
+        // Message template.
+        $mform->addElement('header', 'customisemessage', get_string('customisemessage', 'tool_monitor'));
+        $mform->addElement('editor', 'template', get_string('messagetemplate', 'tool_monitor'), $editoroptions);
+        $mform->setDefault('template', get_string('defaultmessagetpl', 'tool_monitor'));
+        $mform->addRule('template', get_string('required'), 'required');
+        $mform->addHelpButton('template', 'messagetemplate', 'tool_monitor');
+
+        // Action buttons.
+        $this->add_action_buttons(false, get_string('savechanges'));
+    }
+
+    /**
+     * Form validation
+     *
+     * @param array $data data from the form.
+     * @param array $files files uploaded.
+     *
+     * @return array of errors.
+     */
+    public function validation($data, $files) {
+        $errors = parent::validation($data, $files);
+
+        if (!eventlist::validate_event_plugin($data['plugin'], $data['eventname'])) {
+            $errors['eventname'] = get_string('errorincorrectevent', 'tool_monitor');
+        }
+
+        return $errors;
+    }
+}
\ No newline at end of file
diff --git a/admin/tool/monitor/classes/rule_manager.php b/admin/tool/monitor/classes/rule_manager.php
new file mode 100644 (file)
index 0000000..b0c60e0
--- /dev/null
@@ -0,0 +1,280 @@
+<?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/>.
+
+/**
+ * Rule manager class.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Simey Lameze <lameze@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_monitor;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Rule manager class.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Simey Lameze <lameze@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class rule_manager {
+
+    /**
+     * Create a new rule.
+     *
+     * @param \stdClass $ruledata data to insert as new rule entry.
+     *
+     * @return rule An instance of rule class.
+     */
+    public static function add_rule($ruledata) {
+        global $DB;
+
+        $now = time();
+        $ruledata->timecreated = $now;
+        $ruledata->timemodified = $now;
+
+        $ruledata->id = $DB->insert_record('tool_monitor_rules', $ruledata);
+
+        // Trigger a rule created event.
+        if ($ruledata->id) {
+            if (!empty($ruledata->courseid)) {
+                $courseid = $ruledata->courseid;
+                $context = \context_course::instance($ruledata->courseid);
+            } else {
+                $courseid = 0;
+                $context = \context_system::instance();
+            }
+
+            $params = array(
+                'objectid' => $ruledata->id,
+                'courseid' => $courseid,
+                'context' => $context
+            );
+            $event = \tool_monitor\event\rule_created::create($params);
+            $event->trigger();
+        }
+
+        return new rule($ruledata);
+    }
+
+    /**
+     * Clean data submitted by mform.
+     *
+     * @param \stdClass $mformdata data to insert as new rule entry.
+     *
+     * @return \stdClass Cleaned rule data.
+     */
+    public static function clean_ruledata_form($mformdata) {
+        global $USER;
+
+        $rule = new \stdClass();
+        if (!empty($mformdata->ruleid)) {
+            $rule->id = $mformdata->ruleid;
+        }
+        $rule->userid = empty($mformdata->userid) ? $USER->id : $mformdata->userid;
+        $rule->courseid = $mformdata->courseid;
+        $rule->name = $mformdata->name;
+        $rule->plugin = $mformdata->plugin;
+        $rule->eventname = $mformdata->eventname;
+        $rule->description = $mformdata->description['text'];
+        $rule->descriptionformat = $mformdata->description['format'];
+        $rule->frequency = $mformdata->frequency;
+        $rule->timewindow = $mformdata->minutes * MINSECS;
+        $rule->template = $mformdata->template['text'];
+        $rule->templateformat = $mformdata->template['format'];
+
+        return $rule;
+    }
+
+    /**
+     * Delete a rule and associated subscriptions, by rule id.
+     *
+     * @param int $ruleid id of rule to be deleted.
+     * @param \context|null $coursecontext the context of the course - this is passed when we
+     *      can not get the context via \context_course as the course has been deleted.
+     *
+     * @return bool
+     */
+    public static function delete_rule($ruleid, $coursecontext = null) {
+        global $DB;
+
+        subscription_manager::remove_all_subscriptions_for_rule($ruleid, $coursecontext);
+
+        // Retrieve the rule from the DB before we delete it, so we have a record when we trigger a rule deleted event.
+        $rule = $DB->get_record('tool_monitor_rules', array('id' => $ruleid));
+
+        $success = $DB->delete_records('tool_monitor_rules', array('id' => $ruleid));
+
+        // If successful trigger a rule deleted event.
+        if ($success) {
+            // It is possible that we are deleting rules associated with a deleted course, so we should be
+            // passing the context as the second parameter.
+            if (!is_null($coursecontext)) {
+                $context = $coursecontext;
+                $courseid = $rule->courseid;
+            } else if (!empty($rule->courseid) && ($context = \context_course::instance($rule->courseid,
+                    IGNORE_MISSING))) {
+                $courseid = $rule->courseid;
+            } else {
+                $courseid = 0;
+                $context = \context_system::instance();
+            }
+
+            $params = array(
+                'objectid' => $rule->id,
+                'courseid' => $courseid,
+                'context' => $context
+            );
+            $event = \tool_monitor\event\rule_deleted::create($params);
+            $event->add_record_snapshot('tool_monitor_rules', $rule);
+            $event->trigger();
+        }
+
+        return $success;
+    }
+
+    /**
+     * Get an instance of rule class.
+     *
+     * @param \stdClass|int $ruleorid A rule object from database or rule id.
+     *
+     * @return rule object with rule id.
+     */
+    public static function get_rule($ruleorid) {
+        global $DB;
+        if (!is_object($ruleorid)) {
+            $rule = $DB->get_record('tool_monitor_rules', array('id' => $ruleorid), '*', MUST_EXIST);
+        } else {
+            $rule = $ruleorid;
+        }
+
+        return new rule($rule);
+    }
+
+    /**
+     * Update rule data.
+     *
+     * @throws \coding_exception if $record->ruleid is invalid.
+     * @param object $ruledata rule data to be updated.
+     *
+     * @return bool
+     */
+    public static function update_rule($ruledata) {
+        global $DB;
+        if (!self::get_rule($ruledata->id)) {
+            throw new \coding_exception('Invalid rule ID.');
+        }
+        $ruledata->timemodified = time();
+
+        $success = $DB->update_record('tool_monitor_rules', $ruledata);
+
+        // If successful trigger a rule updated event.
+        if ($success) {
+            // If we do not have the course id we need to retrieve it.
+            if (!isset($ruledata->courseid)) {
+                $courseid = $DB->get_field('tool_monitor_rules', 'courseid', array('id' => $ruledata->id), MUST_EXIST);
+            } else {
+                $courseid = $ruledata->courseid;
+            }
+
+            if (!empty($courseid)) {
+                $context = \context_course::instance($courseid);
+            } else {
+                $context = \context_system::instance();
+            }
+
+            $params = array(
+                'objectid' => $ruledata->id,
+                'courseid' => $courseid,
+                'context' => $context
+            );
+            $event = \tool_monitor\event\rule_updated::create($params);
+            $event->trigger();
+        }
+
+        return $success;
+    }
+
+    /**
+     * Get rules by course id.
+     *
+     * @param int $courseid course id of the rule.
+     * @param int $limitfrom Limit from which to fetch rules.
+     * @param int $limitto  Limit to which rules need to be fetched.
+     *
+     * @return array List of rules for the given course id, also includes system wide rules.
+     */
+    public static function get_rules_by_courseid($courseid, $limitfrom = 0, $limitto = 0) {
+        global $DB;
+        $select = "courseid = ? OR courseid = ?";
+        return self::get_instances($DB->get_records_select('tool_monitor_rules', $select, array(0, $courseid), null, '*',
+                $limitfrom, $limitto));
+    }
+
+    /**
+     * Get rule count by course id.
+     *
+     * @param int $courseid course id of the rule.
+     *
+     * @return int count of rules present in system visible in the given course id.
+     */
+    public static function count_rules_by_courseid($courseid) {
+        global $DB;
+        $select = "courseid = ? OR courseid = ?";
+        return $DB->count_records_select('tool_monitor_rules', $select, array(0, $courseid));
+    }
+
+    /**
+     * Get rules by plugin name.
+     *
+     * @param string $plugin plugin name of the rule.
+     *
+     * @return array List of rules for the given plugin name.
+     */
+    public static function get_rules_by_plugin($plugin) {
+        global $DB;
+        return self::get_instances($DB->get_records('tool_monitor_rules', array('plugin' => $plugin)));
+    }
+
+    /**
+     * Get rules by event name.
+     *
+     * @param string $eventname event name of the rule.
+     *
+     * @return array List of rules for the given event.
+     */
+    public static function get_rules_by_event($eventname) {
+        global $DB;
+        return self::get_instances($DB->get_records('tool_monitor_rules', array('eventname' => $eventname)));
+    }
+
+    /**
+     * Helper method to convert db records to instances.
+     *
+     * @param array $arr of rules.
+     *
+     * @return array of rules as instances.
+     */
+    protected static function get_instances($arr) {
+        $result = array();
+        foreach ($arr as $key => $sub) {
+            $result[$key] = new rule($sub);
+        }
+        return $result;
+    }
+}
diff --git a/admin/tool/monitor/classes/subscription.php b/admin/tool/monitor/classes/subscription.php
new file mode 100644 (file)
index 0000000..a729dc8
--- /dev/null
@@ -0,0 +1,192 @@
+<?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 represents a single subscription.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class represents a single subscription instance (i.e with all the subscription info).
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class subscription {
+    /**
+     * @var \stdClass
+     */
+    protected $subscription;
+
+    /**
+     * Constructor.
+     *
+     * use {@link \tool_monitor\subscription_manager::get_subscription} to get an instance instead of directly calling this method.
+     *
+     * @param \stdClass $subscription
+     */
+    public function __construct($subscription) {
+        $this->subscription = $subscription;
+    }
+
+    /**
+     * Magic get method.
+     *
+     * @param string $prop property to get.
+     *
+     * @return mixed
+     * @throws \coding_exception
+     */
+    public function __get($prop) {
+        if (property_exists($this->subscription, $prop)) {
+            return $this->subscription->$prop;
+        }
+        throw new \coding_exception('Property "' . $prop . '" doesn\'t exist');
+    }
+
+    /**
+     * Get a human readable name for instances associated with this subscription.
+     *
+     * @return string
+     * @throws \coding_exception
+     */
+    public function get_instance_name() {
+        if ($this->plugin === 'core') {
+            $string = get_string('allevents', 'tool_monitor');
+        } else {
+            if ($this->cmid == 0) {
+                $string = get_string('allmodules', 'tool_monitor');
+            } else {
+                $cms = get_fast_modinfo($this->courseid);
+                $cms = $cms->get_cms();
+                if (isset($cms[$this->cmid])) {
+                    $string = $cms[$this->cmid]->get_formatted_name(); // Instance name.
+                } else {
+                    // Something is wrong, instance is not present anymore.
+                    $string = get_string('invalidmodule', 'tool_monitor');
+                }
+            }
+        }
+
+        return $string;
+    }
+
+    /**
+     * Method to get event name.
+     *
+     * @return string
+     * @throws \coding_exception
+     */
+    public function get_event_name() {
+        $eventclass = $this->eventname;
+        if (class_exists($eventclass)) {
+            return $eventclass::get_name();
+        }
+        return get_string('eventnotfound', 'tool_monitor');
+    }
+
+    /**
+     * Get filter description.
+     *
+     * @return string
+     */
+    public function get_filters_description() {
+        $a = new \stdClass();
+        $a->freq = $this->frequency;
+        $mins = $this->timewindow / MINSECS; // Convert seconds to minutes.
+        $a->mins = $mins;
+        return get_string('freqdesc', 'tool_monitor', $a);
+    }
+
+    /**
+     * Get properly formatted name of the rule associated.
+     *
+     * @param \context $context context where this name would be displayed.
+     *
+     * @return string Formatted name of the rule.
+     */
+    public function get_name(\context $context) {
+        return format_text($this->name, FORMAT_HTML, array('context' => $context));
+    }
+
+    /**
+     * Get properly formatted description of the rule associated.
+     *
+     * @param \context $context context where this description would be displayed.
+     *
+     * @return string Formatted description of the rule.
+     */
+    public function get_description(\context $context) {
+        return format_text($this->description, $this->descriptionformat, array('context' => $context));
+    }
+
+    /**
+     * Get name of the plugin associated with this rule
+     *
+     * @return string Plugin name.
+     */
+    public function get_plugin_name() {
+        if ($this->plugin === 'core') {
+            $string = get_string('core', 'tool_monitor');
+        } else if (get_string_manager()->string_exists('pluginname', $this->plugin)) {
+            $string = get_string('pluginname', $this->plugin);
+        } else {
+            $string = $this->plugin;
+        }
+        return $string;
+    }
+
+    /**
+     * Get properly formatted name of the course associated.
+     *
+     * @param \context $context context where this name would be displayed.
+     *
+     * @return string Formatted name of the rule.
+     */
+    public function get_course_name(\context $context) {
+        global $SITE;
+        $courseid = $this->courseid;
+        if (empty($courseid)) {
+            $coursename = format_string($SITE->fullname, true, array('context' => $context));
+        } else {
+            $course = get_course($this->courseid);
+            $link = new \moodle_url('/course/view.php', array('id' => $course->id));
+            $coursename = format_string($course->fullname, true, array('context' => $context));
+            $coursename = \html_writer::link($link, $coursename);
+        }
+        return $coursename;
+    }
+
+    /**
+     * Can the current user manage the rule associate with this subscription?
+     *
+     * @return bool true if the current user can manage this rule, else false.
+     */
+    public function can_manage_rule() {
+        $courseid = $this->rulecourseid;
+        $context = empty($courseid) ? \context_system::instance() : \context_course::instance($courseid);
+        return has_capability('tool/monitor:managerules', $context);
+    }
+}
diff --git a/admin/tool/monitor/classes/subscription_manager.php b/admin/tool/monitor/classes/subscription_manager.php
new file mode 100644 (file)
index 0000000..ba2236b
--- /dev/null
@@ -0,0 +1,366 @@
+<?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 to manage subscriptions.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class to manage subscriptions.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class subscription_manager {
+    /**
+     * Subscribe a user to a given rule.
+     *
+     * @param int $ruleid  Rule id.
+     * @param int $courseid Course id.
+     * @param int $cmid Course module id.
+     * @param int $userid User who is subscribing, defaults to $USER.
+     *
+     * @return bool|int returns id of the created subscription.
+     */
+    public static function create_subscription($ruleid, $courseid, $cmid, $userid = 0) {
+        global $DB, $USER;
+
+        $subscription = new \stdClass();
+        $subscription->ruleid = $ruleid;
+        $subscription->courseid = $courseid;
+        $subscription->cmid = $cmid;
+        $subscription->userid = empty($userid) ? $USER->id : $userid;
+        if ($DB->record_exists('tool_monitor_subscriptions', (array)$subscription)) {
+            // Subscription already exists.
+            return false;
+        }
+
+        $subscription->timecreated = time();
+        $subscription->id = $DB->insert_record('tool_monitor_subscriptions', $subscription);
+
+        // Trigger a subscription created event.
+        if ($subscription->id) {
+            if (!empty($subscription->courseid)) {
+                $courseid = $subscription->courseid;
+                $context = \context_course::instance($subscription->courseid);
+            } else {
+                $courseid = 0;
+                $context = \context_system::instance();
+            }
+
+            $params = array(
+                'objectid' => $subscription->id,
+                'courseid' => $courseid,
+                'context' => $context
+            );
+            $event = \tool_monitor\event\subscription_created::create($params);
+            $event->trigger();
+        }
+
+        return $subscription->id;
+    }
+
+    /**
+     * Delete a subscription.
+     *
+     * @param subscription|int $subscriptionorid an instance of subscription class or id.
+     * @param bool $checkuser Check if the subscription belongs to current user before deleting.
+     *
+     * @return bool
+     * @throws \coding_exception if $checkuser is true and the subscription doesn't belong to the current user.
+     */
+    public static function delete_subscription($subscriptionorid, $checkuser = true) {
+        global $DB, $USER;
+        if (is_object($subscriptionorid)) {
+            $subscription = $subscriptionorid;
+        } else {
+            $subscription = self::get_subscription($subscriptionorid);
+        }
+        if ($checkuser && $subscription->userid != $USER->id) {
+            throw new \coding_exception('Invalid subscription supplied');
+        }
+
+        // Store the subscription before we delete it.
+        $subscription = $DB->get_record('tool_monitor_subscriptions', array('id' => $subscription->id));
+
+        $success = $DB->delete_records('tool_monitor_subscriptions', array('id' => $subscription->id));
+
+        // If successful trigger a subscription_deleted event.
+        if ($success) {
+            if (!empty($subscription->courseid)) {
+                $courseid = $subscription->courseid;
+                $context = \context_course::instance($subscription->courseid);
+            } else {
+                $courseid = 0;
+                $context = \context_system::instance();
+            }
+
+            $params = array(
+                'objectid' => $subscription->id,
+                'courseid' => $courseid,
+                'context' => $context
+            );
+            $event = \tool_monitor\event\subscription_deleted::create($params);
+            $event->add_record_snapshot('tool_monitor_subscriptions', $subscription);
+            $event->trigger();
+        }
+
+        return $success;
+    }
+
+    /**
+     * Delete all subscriptions for a user.
+     *
+     * @param int $userid user id.
+     *
+     * @return mixed
+     */
+    public static function delete_user_subscriptions($userid) {
+        global $DB;
+        return $DB->delete_records('tool_monitor_subscriptions', array('userid' => $userid));
+    }
+
+    /**
+     * Delete all subscriptions for a course module.
+     *
+     * @param int $cmid cm id.
+     *
+     * @return mixed
+     */
+    public static function delete_cm_subscriptions($cmid) {
+        global $DB;
+        return $DB->delete_records('tool_monitor_subscriptions', array('cmid' => $cmid));
+    }
+
+    /**
+     * Delete all subscribers for a given rule.
+     *
+     * @param int $ruleid rule id.
+     * @param \context|null $coursecontext the context of the course - this is passed when we
+     *      can not get the context via \context_course as the course has been deleted.
+     *
+     * @return bool
+     */
+    public static function remove_all_subscriptions_for_rule($ruleid, $coursecontext = null) {
+        global $DB;
+
+        // Store all the subscriptions we have to delete.
+        $subscriptions = $DB->get_recordset('tool_monitor_subscriptions', array('ruleid' => $ruleid));
+
+        // Now delete them.
+        $success = $DB->delete_records('tool_monitor_subscriptions', array('ruleid' => $ruleid));
+
+        // If successful and there were subscriptions that were deleted trigger a subscription deleted event.
+        if ($success && $subscriptions) {
+            foreach ($subscriptions as $subscription) {
+                // It is possible that we are deleting rules associated with a deleted course, so we should be
+                // passing the context as the second parameter.
+                if (!is_null($coursecontext)) {
+                    $context = $coursecontext;
+                    $courseid = $subscription->courseid;
+                } else if (!empty($subscription->courseid) && ($coursecontext =
+                        \context_course::instance($subscription->courseid, IGNORE_MISSING))) {
+                    $courseid = $subscription->courseid;
+                    $context = $coursecontext;
+                } else {
+                    $courseid = 0;
+                    $context = \context_system::instance();
+                }
+
+                $params = array(
+                    'objectid' => $subscription->id,
+                    'courseid' => $courseid,
+                    'context' => $context
+                );
+                $event = \tool_monitor\event\subscription_deleted::create($params);
+                $event->add_record_snapshot('tool_monitor_subscriptions', $subscription);
+                $event->trigger();
+            }
+        }
+
+        return $success;
+    }
+
+    /**
+     * Get a subscription instance for an given subscription id.
+     *
+     * @param subscription|int $subscriptionorid an instance of subscription class or id.
+     *
+     * @return subscription returns a instance of subscription class.
+     */
+    public static function get_subscription($subscriptionorid) {
+        global $DB;
+
+        if (is_object($subscriptionorid)) {
+            return new subscription($subscriptionorid);
+        }
+
+        $sql = self::get_subscription_join_rule_sql();
+        $sql .= "WHERE s.id = :id";
+        $sub = $DB->get_record_sql($sql, array('id' => $subscriptionorid), MUST_EXIST);
+        return new subscription($sub);
+    }
+
+    /**
+     * Get an array of subscriptions for a given user in a given course.
+     *
+     * @param int $courseid course id.
+     * @param int $limitfrom Limit from which to fetch rules.
+     * @param int $limitto  Limit to which rules need to be fetched.
+     * @param int $userid Id of the user for which the subscription needs to be fetched. Defaults to $USER;
+     * @param string $order Order to sort the subscriptions.
+     *
+     * @return array list of subscriptions
+     */
+    public static function get_user_subscriptions_for_course($courseid, $limitfrom = 0, $limitto = 0, $userid = 0,
+            $order = 's.timecreated DESC' ) {
+        global $DB, $USER;
+        if ($userid == 0) {
+            $userid = $USER->id;
+        }
+        $sql = self::get_subscription_join_rule_sql();
+        $sql .= "WHERE s.courseid = :courseid AND s.userid = :userid ORDER BY $order";
+
+        return self::get_instances($DB->get_records_sql($sql, array('courseid' => $courseid, 'userid' => $userid), $limitfrom,
+                $limitto));
+    }
+
+    /**
+     * Get count of subscriptions for a given user in a given course.
+     *
+     * @param int $courseid course id.
+     * @param int $userid Id of the user for which the subscription needs to be fetched. Defaults to $USER;
+     *
+     * @return int number of subscriptions
+     */
+    public static function count_user_subscriptions_for_course($courseid, $userid = 0) {
+        global $DB, $USER;
+        if ($userid == 0) {
+            $userid = $USER->id;
+        }
+        $sql = self::get_subscription_join_rule_sql(true);
+        $sql .= "WHERE s.courseid = :courseid AND s.userid = :userid";
+
+        return $DB->count_records_sql($sql, array('courseid' => $courseid, 'userid' => $userid));
+    }
+
+    /**
+     * Get an array of subscriptions for a given user.
+     *
+     * @param int $limitfrom Limit from which to fetch rules.
+     * @param int $limitto  Limit to which rules need to be fetched.
+     * @param int $userid Id of the user for which the subscription needs to be fetched. Defaults to $USER;
+     * @param string $order Order to sort the subscriptions.
+     *
+     * @return array list of subscriptions
+     */
+    public static function get_user_subscriptions($limitfrom = 0, $limitto = 0, $userid = 0,
+                                                             $order = 's.timecreated DESC' ) {
+        global $DB, $USER;
+        if ($userid == 0) {
+            $userid = $USER->id;
+        }
+        $sql = self::get_subscription_join_rule_sql();
+        $sql .= "WHERE s.userid = :userid ORDER BY $order";
+
+        return self::get_instances($DB->get_records_sql($sql, array('userid' => $userid), $limitfrom, $limitto));
+    }
+
+    /**
+     * Get count of subscriptions for a given user.
+     *
+     * @param int $userid Id of the user for which the subscription needs to be fetched. Defaults to $USER;
+     *
+     * @return int number of subscriptions
+     */
+    public static function count_user_subscriptions($userid = 0) {
+        global $DB, $USER;;
+        if ($userid == 0) {
+            $userid = $USER->id;
+        }
+        $sql = self::get_subscription_join_rule_sql(true);
+        $sql .= "WHERE s.userid = :userid";
+
+        return $DB->count_records_sql($sql, array('userid' => $userid));
+    }
+
+    /**
+     * Return a list of subscriptions for a given event.
+     *
+     * @param \stdClass $event the event object.
+     *
+     * @return array
+     */
+    public static function get_subscriptions_by_event(\stdClass $event) {
+        global $DB;
+
+        $sql = self::get_subscription_join_rule_sql();
+        if ($event->contextlevel == CONTEXT_MODULE && $event->contextinstanceid != 0) {
+            $sql .= "WHERE r.eventname = :eventname AND s.courseid = :courseid AND (s.cmid = :cmid OR s.cmid = 0)";
+            $params = array('eventname' => $event->eventname, 'courseid' => $event->courseid, 'cmid' => $event->contextinstanceid);
+        } else {
+            $sql .= "WHERE r.eventname = :eventname AND (s.courseid = :courseid OR s.courseid = 0)";
+            $params = array('eventname' => $event->eventname, 'courseid' => $event->courseid);
+        }
+        return self::get_instances($DB->get_records_sql($sql, $params));
+    }
+
+    /**
+     * Return sql to join rule and subscription table.
+     *
+     * @param bool $count Weather if this is a count query or not.
+     *
+     * @return string the sql.
+     */
+    protected static function get_subscription_join_rule_sql($count = false) {
+        if ($count) {
+            $select = "SELECT COUNT(s.id) ";
+        } else {
+            $select = "SELECT s.*, r.description, r.descriptionformat, r.name, r.userid as ruleuserid, r.courseid as rulecourseid,
+            r.plugin, r.eventname, r.template, r.templateformat, r.frequency, r.timewindow";
+        }
+        $sql = $select . "
+                  FROM {tool_monitor_rules} r
+                  JOIN {tool_monitor_subscriptions} s
+                        ON r.id = s.ruleid ";
+        return $sql;
+    }
+
+    /**
+     * Helper method to convert db records to instances.
+     *
+     * @param array $arr of subscriptions.
+     *
+     * @return array of subscriptions as instances.
+     */
+    protected static function get_instances($arr) {
+        $result = array();
+        foreach ($arr as $key => $sub) {
+            $result[$key] = new subscription($sub);
+        }
+        return $result;
+    }
+}
diff --git a/admin/tool/monitor/classes/task/clean_events.php b/admin/tool/monitor/classes/task/clean_events.php
new file mode 100644 (file)
index 0000000..9efb360
--- /dev/null
@@ -0,0 +1,142 @@
+<?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/>.
+
+/**
+ * Clean the tool_monitor_events table.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\task;
+
+/**
+ * Simple task to clean the tool_monitor_events table.
+ */
+class clean_events extends \core\task\scheduled_task {
+
+    /**
+     * Get a descriptive name for this task.
+     *
+     * @return string
+     */
+    public function get_name() {
+        return get_string('taskcleanevents', 'tool_monitor');
+    }
+
+    /**
+     * Performs the cleaning of events.
+     */
+    public function execute() {
+        global $DB;
+
+        // Array to store which events have been triggered in which course.
+        $courses = array();
+
+        // Firstly, let's go through the site wide rules. There may be multiple rules for the site that rely on
+        // the same event being triggered, so we only remove the events when they reach the max timewindow.
+        if ($siterules = $DB->get_recordset('tool_monitor_rules', array('courseid' => 0), 'timewindow DESC')) {
+            // Go through each rule and check if there are any events we can remove.
+            foreach ($siterules as $rule) {
+                // Check if we have already processed this event.
+                if (isset($courses[$rule->courseid][$rule->eventname])) {
+                    continue;
+                }
+                // Store the timewindow for this event.
+                $courses[$rule->courseid][$rule->eventname] = $rule->timewindow;
+                // Delete any events that may exist that have exceeded the timewindow.
+                $DB->delete_records_select('tool_monitor_events', 'eventname = :eventname AND
+                    courseid = :courseid AND timecreated <= :timewindow',
+                    array('eventname' => $rule->eventname, 'courseid' => $rule->courseid,
+                        'timewindow' => time() - $rule->timewindow));
+            }
+            // Free resources.
+            $siterules->close();
+        }
+
+        // Now, get the course rules. The same applies here - there may be multiple rules for the course that rely on
+        // the same event being triggered, so we only remove the events when they reach the max timewindow.
+        if ($rules = $DB->get_recordset_select('tool_monitor_rules', 'courseid != 0', array(), 'timewindow DESC')) {
+            // Go through each rule and check if there are any events we can remove.
+            foreach ($rules as $rule) {
+                // Check if we have already processed this event for this particular course.
+                if (isset($courses[$rule->courseid][$rule->eventname])) {
+                    continue;
+                }
+                // Add the course and event to the list.
+                $courses[$rule->courseid][$rule->eventname] = $rule->timewindow;
+                // If there is a site wide rule listening for this event do not remove it unless the maximum
+                // timewindow between the two has exceeded.
+                $timewindow = $rule->timewindow;
+                if (isset($courses[0][$rule->eventname]) && ($courses[0][$rule->eventname] > $timewindow)) {
+                    $timewindow = $courses[0][$rule->eventname];
+                }
+                // Delete any events that may exist that have exceeded the timewindow.
+                $DB->delete_records_select('tool_monitor_events', 'eventname = :eventname AND
+                    courseid = :courseid AND timecreated <= :timewindow',
+                        array('eventname' => $rule->eventname, 'courseid' => $rule->courseid,
+                            'timewindow' => time() - $timewindow));
+            }
+            // Free resources.
+            $rules->close();
+        }
+
+        if ($siterules || $rules) { // Check that there are rules present.
+            // Get a list of all the events we have been through.
+            $allevents = array();
+            foreach ($courses as $key => $value) {
+                foreach ($courses[$key] as $event => $notused) {
+                    $allevents[] = $event;
+                }
+            }
+            // Remove all the events in the table that are not applicable to any rule. There may be a rule in one course
+            // listening for a certain event, but not in another course, so we can delete the event from the course
+            // where there is no rule. We also have to consider site wide rules. We may have an event that is triggered
+            // in a course we can't remove because there is a site wide rule for this event.
+            if ($events = $DB->get_recordset('tool_monitor_events')) {
+                // Array to store which events we need to remove.
+                $eventstodelete = array();
+                // Store the current time.
+                $now = time();
+                foreach ($events as $event) {
+                    // If the event is not required for a course rule and there is no site wide rule for it, or
+                    // it has extended past or equal to the timewindow for the site rule - it can be deleted.
+                    if (!isset($courses[$event->courseid][$event->eventname]) && (!isset($courses[0][$event->eventname])
+                        || $courses[0][$event->eventname] <= ($now - $event->timecreated))) {
+                        $eventstodelete[] = $event->id;
+                    }
+                }
+                // Free resources.
+                $events->close();
+
+                // Remove the events.
+                if (!empty($eventstodelete)) {
+                    list($eventidsql, $params) = $DB->get_in_or_equal($eventstodelete);
+                    $DB->delete_records_select('tool_monitor_events', "id $eventidsql", $params);
+                }
+            }
+
+            // Remove all the events in the table that are not used by any rule.
+            if (!empty($allevents)) {
+                list($eventnamesql, $params) = $DB->get_in_or_equal($allevents, SQL_PARAMS_QM, 'param', false);
+                $DB->delete_records_select('tool_monitor_events', "eventname $eventnamesql", $params);
+            }
+        } else { // No rules, just remove everything.
+            $DB->delete_records('tool_monitor_events');
+        }
+    }
+}
diff --git a/admin/tool/monitor/db/access.php b/admin/tool/monitor/db/access.php
new file mode 100644 (file)
index 0000000..d1b687c
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Capabilities.
+ *
+ * This files lists capabilities related to tool_monitor.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$capabilities = array(
+
+    'tool/monitor:subscribe' => array(
+        'riskbitmask' => RISK_PERSONAL,
+        'captype' => 'read',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => array(
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW
+        ),
+    ),
+
+    'tool/monitor:managerules' => array(
+        'riskbitmask' => RISK_XSS,
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => array(
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW
+        ),
+    ),
+);
+
diff --git a/admin/tool/monitor/db/events.php b/admin/tool/monitor/db/events.php
new file mode 100644 (file)
index 0000000..0b1859f
--- /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/>.
+
+/**
+ * This file definies observers needed by the tool.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// List of observers.
+$observers = array(
+    array(
+        'eventname'   => '\core\event\course_deleted',
+        'priority'    => 1,
+        'callback'    => '\tool_monitor\eventobservers::course_deleted',
+    ),
+    array(
+        'eventname'   => '*',
+        'callback'    => '\tool_monitor\eventobservers::process_event',
+    ),
+    array(
+        'eventname'   => '\core\event\user_deleted',
+        'callback'    => '\tool_monitor\eventobservers::user_deleted',
+    ),
+    array(
+        'eventname'   => '\core\event\course_module_deleted',
+        'callback'    => '\tool_monitor\eventobservers::course_module_deleted',
+    )
+);
diff --git a/admin/tool/monitor/db/install.xml b/admin/tool/monitor/db/install.xml
new file mode 100644 (file)
index 0000000..39d2bb9
--- /dev/null
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<XMLDB PATH="tool/monitor/db" VERSION="20140708" COMMENT="XMLDB file for Moodle tool/monitor"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
+>
+  <TABLES>
+    <TABLE NAME="tool_monitor_rules" COMMENT="Table to store rules">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="description" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Description of the rule"/>
+        <FIELD NAME="descriptionformat" TYPE="int" LENGTH="1" NOTNULL="true" SEQUENCE="false" COMMENT="Description format"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="254" NOTNULL="true" SEQUENCE="false" COMMENT="Name of the rule"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Id of user who created the rule"/>
+        <FIELD NAME="courseid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Id of course to which this rule belongs."/>
+        <FIELD NAME="plugin" TYPE="char" LENGTH="254" NOTNULL="true" SEQUENCE="false" COMMENT="Frankenstlye name of the plguin"/>
+        <FIELD NAME="eventname" TYPE="char" LENGTH="254" NOTNULL="true" SEQUENCE="false" COMMENT="Fully qualified name of the event"/>
+        <FIELD NAME="template" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Message template"/>
+        <FIELD NAME="templateformat" TYPE="int" LENGTH="1" NOTNULL="true" SEQUENCE="false" COMMENT="Template format"/>
+        <FIELD NAME="frequency" TYPE="int" LENGTH="4" NOTNULL="true" SEQUENCE="false" COMMENT="Frequency"/>
+        <FIELD NAME="timewindow" TYPE="int" LENGTH="5" NOTNULL="true" SEQUENCE="false" COMMENT="Time window in seconds"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Timestamp when this rule was last modified"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Time stamp of when this rule was created"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="courseanduser" UNIQUE="false" FIELDS="courseid, userid" COMMENT="Index on courseid and userid"/>
+        <INDEX NAME="eventname" UNIQUE="false" FIELDS="eventname" COMMENT="eventname"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="tool_monitor_subscriptions" COMMENT="Table to store user subscriptions to various rules">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="courseid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Course id of the subscription"/>
+        <FIELD NAME="ruleid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Rule id"/>
+        <FIELD NAME="cmid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Course module id"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="User id of the subscriber"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Timestamp of when this subscription was created"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="rulekey" TYPE="foreign" FIELDS="ruleid" REFTABLE="tool_monitor_rules" REFFIELDS="id" COMMENT="Foreign key"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="courseanduser" UNIQUE="false" FIELDS="courseid, userid" COMMENT="Course and user"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="tool_monitor_history" COMMENT="Table to store history of message notifications sent">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="sid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Subscription id"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="User to whom this notification was sent"/>
+        <FIELD NAME="timesent" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Timestamp of when the message was sent."/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="subscrptionid" TYPE="foreign" FIELDS="sid" REFTABLE="tool_monitor_subscriptions" REFFIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="sid_userid_timesent" UNIQUE="true" FIELDS="sid, userid, timesent"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="tool_monitor_events" COMMENT="A table that keeps a log of events related to subscriptions">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="eventname" TYPE="char" LENGTH="254" NOTNULL="true" SEQUENCE="false" COMMENT="Event name"/>
+        <FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Context id"/>
+        <FIELD NAME="contextlevel" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Context level"/>
+        <FIELD NAME="contextinstanceid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Context instance id"/>
+        <FIELD NAME="link" TYPE="char" LENGTH="254" NOTNULL="true" SEQUENCE="false" COMMENT="Link to the event location"/>
+        <FIELD NAME="courseid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="course id"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Time created"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+    </TABLE>
+  </TABLES>
+</XMLDB>
\ No newline at end of file
diff --git a/admin/tool/monitor/db/messages.php b/admin/tool/monitor/db/messages.php
new file mode 100644 (file)
index 0000000..e80f6ef
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Message providers list.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$messageproviders = array (
+    // Notify a user that a rule has happened.
+    'notification' => array (
+        'capability'  => 'tool/monitor:subscribe'
+    )
+);
diff --git a/admin/tool/monitor/db/tasks.php b/admin/tool/monitor/db/tasks.php
new file mode 100644 (file)
index 0000000..e70c344
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file defines tasks performed by the tool.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// List of tasks.
+$tasks = array(
+    array(
+        'classname' => 'tool_monitor\task\clean_events',
+        'blocking' => 0,
+        'minute' => '*',
+        'hour' => '*',
+        'day' => '*',
+        'dayofweek' => '*',
+        'month' => '*'
+    )
+);
diff --git a/admin/tool/monitor/edit.php b/admin/tool/monitor/edit.php
new file mode 100644 (file)
index 0000000..c69e92e
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file gives an overview of the monitors present in site.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+require(__DIR__ . '/../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+
+$ruleid = optional_param('ruleid', 0, PARAM_INT);
+$courseid = optional_param('courseid', 0, PARAM_INT);
+
+// Validate course id.
+if (empty($courseid)) {
+    require_login();
+    $context = context_system::instance();
+    $coursename = format_string($SITE->fullname, true, array('context' => $context));
+    $PAGE->set_context($context);
+} else {
+    $course = get_course($courseid);
+    require_login($course);
+    $context = context_course::instance($course->id);
+    $coursename = format_string($course->fullname, true, array('context' => $context));
+}
+
+// Check for caps.
+require_capability('tool/monitor:managerules', $context);
+
+// Set up the page.
+$a = new stdClass();
+$a->coursename = $coursename;
+$a->reportname = get_string('pluginname', 'tool_monitor');
+$title = get_string('title', 'tool_monitor', $a);
+$url = new moodle_url("/admin/tool/monitor/edit.php", array('courseid' => $courseid, 'ruleid' => $ruleid));
+$manageurl = new moodle_url("/admin/tool/monitor/managerules.php", array('courseid' => $courseid));
+
+$PAGE->set_url($url);
+$PAGE->set_pagelayout('report');
+$PAGE->set_title($title);
+$PAGE->set_heading($title);
+
+// Get data ready for mform.
+$eventlist = tool_monitor\eventlist::get_all_eventlist(true);
+$pluginlist = tool_monitor\eventlist::get_plugin_list();
+$eventlist = array_merge(array('' => get_string('choosedots')), $eventlist);
+$pluginlist = array_merge(array('' => get_string('choosedots')), $pluginlist);
+
+// Set up the yui module.
+$PAGE->requires->yui_module('moodle-tool_monitor-dropdown', 'Y.M.tool_monitor.DropDown.init',
+        array(array('eventlist' => $eventlist)));
+
+// Site level report.
+if (empty($courseid)) {
+    admin_externalpage_setup('toolmonitorrules', '', null, '', array('pagelayout' => 'report'));
+} else {
+    // Course level report.
+    $PAGE->navigation->override_active_url($manageurl);
+}
+
+// Mform setup.
+if (!empty($ruleid)) {
+    $rule = \tool_monitor\rule_manager::get_rule($ruleid)->get_mform_set_data();
+    $rule->minutes = $rule->timewindow / MINSECS;
+} else {
+    $rule = new stdClass();
+}
+
+$mform = new tool_monitor\rule_form(null, array('eventlist' => $eventlist, 'pluginlist' => $pluginlist, 'rule' => $rule,
+        'courseid' => $courseid));
+
+if ($mformdata = $mform->get_data()) {
+    $rule = \tool_monitor\rule_manager::clean_ruledata_form($mformdata);
+
+    if (empty($rule->id)) {
+        \tool_monitor\rule_manager::add_rule($rule);
+    } else {
+        \tool_monitor\rule_manager::update_rule($rule);
+    }
+
+    redirect($manageurl);
+} else {
+    echo $OUTPUT->header();
+    $mform->set_data($rule);
+    $mform->display();
+    echo $OUTPUT->footer();
+}
+
diff --git a/admin/tool/monitor/help.php b/admin/tool/monitor/help.php
new file mode 100644 (file)
index 0000000..572b4ba
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Displays help on a new page.
+ *
+ * @copyright 2014 Mark Nelson <markn@moodle.com>
+ * @package tool_monitor
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('NO_MOODLE_COOKIES', true);
+
+require_once('../../../config.php');
+
+$type = required_param('type', PARAM_ALPHA);
+$id = required_param('id', PARAM_INT);
+$lang = optional_param('lang', 'en', PARAM_LANG);
+
+// We don't actually modify the session here as we have NO_MOODLE_COOKIES set.
+$SESSION->lang = $lang;
+
+$PAGE->set_url('/admin/tool/monitor/help.php');
+$PAGE->set_pagelayout('popup');
+
+if ($type == 'rule') {
+    $item = \tool_monitor\rule_manager::get_rule($id);
+} else { // Must be a subscription.
+    $item = \tool_monitor\subscription_manager::get_subscription($id);
+}
+
+if ($item->courseid) {
+    $PAGE->set_context(context_course::instance($item->courseid));
+} else { // Must be system context.
+    $PAGE->set_context(context_system::instance());
+}
+
+// Get the help string data.
+$data = tool_monitor\output\helpicon\renderable::get_help_string_parameters($type, $id);
+
+echo $OUTPUT->header();
+if (!empty($data->heading)) {
+    echo $OUTPUT->heading($data->heading, 1, 'helpheading');
+}
+echo $data->text;
+if (isset($data->completedoclink)) {
+    echo $data->completedoclink;
+}
+echo $OUTPUT->footer();
diff --git a/admin/tool/monitor/help_ajax.php b/admin/tool/monitor/help_ajax.php
new file mode 100644 (file)
index 0000000..429ff3b
--- /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/>.
+
+/**
+ * Displays help via AJAX call.
+ *
+ * @copyright 2014 Mark Nelson <markn@moodle.com>
+ * @package tool_monitor
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('NO_MOODLE_COOKIES', true);
+define('AJAX_SCRIPT', true);
+
+require_once('../../../config.php');
+
+$type = required_param('type', PARAM_ALPHA);
+$id = required_param('id', PARAM_INT);
+$lang = optional_param('lang', 'en', PARAM_LANG);
+
+// We don't actually modify the session here as we have NO_MOODLE_COOKIES set.
+$SESSION->lang = $lang;
+$PAGE->set_url('/admin/tool/monitor/help_ajax.php');
+
+if ($type == 'rule') {
+    $item = \tool_monitor\rule_manager::get_rule($id);
+} else { // Must be a subscription.
+    $item = \tool_monitor\subscription_manager::get_subscription($id);
+}
+
+if ($item->courseid) {
+    $PAGE->set_context(context_course::instance($item->courseid));
+} else { // Must be system context.
+    $PAGE->set_context(context_system::instance());
+}
+
+echo json_encode(tool_monitor\output\helpicon\renderable::get_help_string_parameters($type, $id, true));
diff --git a/admin/tool/monitor/index.php b/admin/tool/monitor/index.php
new file mode 100644 (file)
index 0000000..45c7309
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This page lets users to manage rules for a given course.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+
+$courseid = optional_param('courseid', 0, PARAM_INT);
+$action = optional_param('action', '', PARAM_ALPHA);
+$cmid = optional_param('cmid', 0, PARAM_INT);
+$ruleid = optional_param('ruleid', 0, PARAM_INT);
+$subscriptionid = optional_param('subscriptionid', 0, PARAM_INT);
+
+// Validate course id.
+if (empty($courseid)) {
+    require_login();
+} else {
+    // They might want to see rules for this course.
+    $course = get_course($courseid);
+    require_login($course);
+    $coursecontext = context_course::instance($course->id);
+    // Check for caps.
+    require_capability('tool/monitor:subscribe', $coursecontext);
+    $coursename = format_string($course->fullname, true, array('context' => $coursecontext));
+}
+
+// Always build the page in site context.
+$context = context_system::instance();
+$sitename = format_string($SITE->fullname, true, array('context' => $context));
+$PAGE->set_context($context);
+
+// Set up the page.
+$a = new stdClass();
+$a->coursename = $sitename;
+$a->reportname = get_string('pluginname', 'tool_monitor');
+$title = get_string('title', 'tool_monitor', $a);
+$indexurl = new moodle_url("/admin/tool/monitor/index.php", array('courseid' => $courseid));
+
+$PAGE->set_url($indexurl);
+$PAGE->set_pagelayout('report');
+$PAGE->set_title($title);
+$PAGE->set_heading($title);
+
+echo $OUTPUT->header();
+
+// Create/delete subscription if needed.
+if (!empty($action)) {
+    require_sesskey();
+    switch ($action) {
+        case 'subscribe' :
+            $rule = \tool_monitor\rule_manager::get_rule($ruleid);
+            $rule->subscribe_user($courseid, $cmid);
+            echo $OUTPUT->notification(get_string('subcreatesuccess', 'tool_monitor'), 'notifysuccess');
+            break;
+        case 'unsubscribe' :
+            \tool_monitor\subscription_manager::delete_subscription($subscriptionid);
+            echo $OUTPUT->notification(get_string('subdeletesuccess', 'tool_monitor'), 'notifysuccess');
+            break;
+        default:
+    }
+}
+
+// Render the current subscriptions list.
+$totalsubs = \tool_monitor\subscription_manager::count_user_subscriptions();
+$renderer = $PAGE->get_renderer('tool_monitor', 'managesubs');
+if (!empty($totalsubs)) {
+    // Show the subscriptions section only if there are subscriptions.
+    $subs = new \tool_monitor\output\managesubs\subs('toolmonitorsubs', $indexurl, $courseid);
+    echo $OUTPUT->heading(get_string('currentsubscriptions', 'tool_monitor'));
+    echo $renderer->render($subs);
+}
+
+// Render the potential rules list.
+$totalrules = \tool_monitor\rule_manager::count_rules_by_courseid($courseid);
+echo $OUTPUT->heading(get_string('rulescansubscribe', 'tool_monitor'));
+$rules = new \tool_monitor\output\managesubs\rules('toolmonitorrules', $indexurl, $courseid);
+echo $renderer->render($rules);
+if (empty($totalrules)) {
+    // No rules present. Show a link to manage rules page if permissions permit.
+    echo html_writer::start_div();
+    echo html_writer::tag('span', get_string('norules', 'tool_monitor'));
+    if (has_capability('tool/monitor:managerules', $context)) {
+        $manageurl = new moodle_url("/admin/tool/monitor/managerules.php", array('courseid' => $courseid));
+        $a = html_writer::link($manageurl, get_string('managerules', 'tool_monitor'));
+        $link = "&nbsp;";
+        $link .= html_writer::tag('span', get_string('manageruleslink', 'tool_monitor', $a));
+        echo $link;
+    }
+    echo html_writer::end_div();
+}
+echo $OUTPUT->footer();
diff --git a/admin/tool/monitor/lang/en/tool_monitor.php b/admin/tool/monitor/lang/en/tool_monitor.php
new file mode 100644 (file)
index 0000000..151cf0b
--- /dev/null
@@ -0,0 +1,99 @@
+<?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/>.
+
+/**
+ * Lang strings.
+ *
+ * This files lists lang strings related to tool_monitor.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['addrule'] = 'Add a new rule';
+$string['allevents'] = 'All events';
+$string['allmodules'] = 'All modules';
+$string['core'] = 'Core';
+$string['customisefilters'] = 'Select the frequency of the events';
+$string['customisemessage'] = 'Customise the notification message';
+$string['currentsubscriptions'] = 'Your current subscriptions';
+$string['description_help'] = "Description is displayed to users when they want to subscribe to this rule. This helps them understand what the rule is about.";
+$string['defaultmessagetpl'] = 'Rule "{rulename}" has happened. You can find further details at {link}';
+$string['deleterule'] = 'Delete rule';
+$string['deletesubscription'] = 'Delete subscription';
+$string['description'] = 'Description:';
+$string['duplicaterule'] = 'Duplicate rule';
+$string['editrule'] = 'Edit rule';
+$string['eventnotfound'] = 'Event not found';
+$string['eventrulecreated'] = 'Rule created';
+$string['eventruledeleted'] = 'Rule deleted';
+$string['eventruleupdated'] = 'Rule updated';
+$string['eventsubcreated'] = 'Subscription created';
+$string['eventsubcriteriamet'] = 'Subscription criteria met';
+$string['eventsubdeleted'] = 'Subscription deleted';
+$string['errorincorrectevent'] = 'Please select an event related to the selected plugin';
+$string['freqdesc'] = '{$a->freq} times in {$a->mins} minutes';
+$string['frequency'] = 'Frequency';
+$string['invalidmodule'] = 'Invalid module';
+$string['norules'] = 'There are no rules you can subscribe to.';
+$string['manageruleslink'] = 'You can manage rules from {$a} page.';
+$string['moduleinstance'] = 'Module instance';
+$string['manage'] = 'Manage';
+$string['managesubscriptions'] = 'Event monitoring';
+$string['managerules'] = 'Event monitoring rules';
+$string['messageheader'] = 'Customise your notification message';
+$string['messageprovider:notification'] = 'Notifications of rule subscriptions';
+$string['messagetemplate'] = 'Message template';
+$string['messagetemplate_help'] = 'This is the content of the message that will be sent to users, when the given conditions of the rule are met. You are allowed to use following templates in this.
+<br /> {link} - Link to the location where the event happened.
+<br /> {modulelink} - Link to the module where the event has happened.
+<br /> {rulename} - Name of this rule.
+<br /> {description} - Rule description.
+<br /> {eventname} - Name of the event associated with the rule.';
+$string['minutes'] = 'in minutes:';
+$string['name'] = 'Name of the rule: ';
+$string['name_help'] = "Choose a name for the rule.";
+$string['nopermission'] = "No permission";
+$string['pluginname'] = 'Event monitor';
+$string['processevents'] = 'Process events';
+$string['ruleareyousure'] = 'Are you sure you want to delete rule "{$a}"?';
+$string['rulecopysuccess'] = 'Rule successfully duplicated';
+$string['ruledeletesuccess'] = 'Rule successfully deleted';
+$string['rulehelp'] = 'Rule details';
+$string['rulehelp_help'] = 'This rule listens for when the event \'{$a->eventname}\' in \'{$a->eventcomponent}\' has been triggered {$a->frequency} time(s) in {$a->minutes} minute(s).';
+$string['rulenopermissions'] = 'You do not have permissions to "{$a} a rule"';
+$string['rulescansubscribe'] = 'Rules you can subscribe to';
+$string['selectacourse'] = 'Select a course';
+$string['selectcourse'] = 'Visit this report at course level to get a list of possible modules';
+$string['selectevent'] = 'Select an event:';
+$string['selectevent_help'] = "Select an event to monitor. Please note that some events are only triggered for the entire site (e.g. 'course created') and will never trigger when subscribed to from within a course.";
+$string['selectfrequency'] = 'Frequency of events:';
+$string['selectfrequency_help'] = "Frequency defines the denisty of the event occurrence. Select criterias to define how frequently the event should happen to trigger the notification.";
+$string['selectminutes'] = 'in minutes:';
+$string['selectplugin'] = 'Select the plugin type:';
+$string['selectplugin_help'] = "Select a plugin that you are interested in monitoring. The event list below would be updated to display events from the selected plugin.";
+$string['subareyousure'] = 'Are you sure you want to delete this subscription for the rule "{$a}"?';
+$string['subcreatesuccess'] = "Subscription successfully created";
+$string['subdeletesuccess'] = "Subscription successfully removed";
+$string['subhelp'] = 'Subscription details';
+$string['subhelp_help'] = 'This subscription listens for when the event \'{$a->eventname}\' has been triggered in \'{$a->moduleinstance}\' {$a->frequency} time(s) in {$a->minutes} minute(s).';
+$string['taskcleanevents'] = 'Removes any unnecessary event monitor events';
+$string['title'] = '{$a->coursename} : {$a->reportname}';
+$string['monitor:managerules'] = 'Manage event monitor rules';
+$string['monitor:subscribe'] = 'Subscribe to event monitor rules';
+$string['unsubscribe'] = 'Unsubscribe';
+
diff --git a/admin/tool/monitor/lib.php b/admin/tool/monitor/lib.php
new file mode 100644 (file)
index 0000000..d06ec5e
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This page lists public api for tool_monitor plugin.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * This function extends the navigation with the tool items
+ *
+ * @param navigation_node $navigation The navigation node to extend
+ * @param stdClass        $course     The course to object for the tool
+ * @param context         $context    The context of the course
+ */
+function tool_monitor_extend_navigation_course($navigation, $course, $context) {
+
+    if (has_capability('tool/monitor:managerules', $context)) {
+        $url = new moodle_url('/admin/tool/monitor/managerules.php', array('courseid' => $course->id));
+        $settingsnode = navigation_node::create(get_string('managerules', 'tool_monitor'), $url, navigation_node::TYPE_SETTING,
+                null, null, new pix_icon('i/settings', ''));
+        $reportnode = $navigation->get('coursereports');
+
+        if (isset($settingsnode) && !empty($reportnode)) {
+            $reportnode->add_node($settingsnode);
+        }
+    }
+}
+
+/**
+ * This function extends the navigation with the tool items for user settings node.
+ *
+ * @param navigation_node $navigation  The navigation node to extend
+ * @param stdClass        $user        The user object
+ * @param context         $usercontext The context of the user
+ * @param stdClass        $course      The course to object for the tool
+ * @param context         $coursecontext     The context of the course
+ */
+function tool_monitor_extend_navigation_user_settings($navigation, $user, $usercontext, $course, $coursecontext) {
+    global $USER;
+    if (($USER->id == $user->id) && (has_capability('tool/monitor:subscribe', $coursecontext))) {
+        $url = new moodle_url('/admin/tool/monitor/index.php', array('courseid' => $course->id));
+        $subsnode = navigation_node::create(get_string('managesubscriptions', 'tool_monitor'), $url,
+                navigation_node::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
+
+        if (isset($subsnode) && !empty($navigation)) {
+            $navigation->add_node($subsnode);
+        }
+    }
+}
diff --git a/admin/tool/monitor/managerules.php b/admin/tool/monitor/managerules.php
new file mode 100644 (file)
index 0000000..8259fec
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This page lets users to manage rules for a given course.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+
+$courseid = optional_param('courseid', 0, PARAM_INT);
+$ruleid = optional_param('ruleid', 0, PARAM_INT);
+$action = optional_param('action', '', PARAM_ALPHA);
+
+// Validate course id.
+if (empty($courseid)) {
+    require_login();
+    $context = context_system::instance();
+    $coursename = format_string($SITE->fullname, true, array('context' => $context));
+    $PAGE->set_context($context);
+} else {
+    $course = get_course($courseid);
+    require_login($course);
+    $context = context_course::instance($course->id);
+    $coursename = format_string($course->fullname, true, array('context' => $context));
+}
+
+// Check for caps.
+require_capability('tool/monitor:managerules', $context);
+
+// Set up the page.
+$a = new stdClass();
+$a->coursename = $coursename;
+$a->reportname = get_string('pluginname', 'tool_monitor');
+$title = get_string('title', 'tool_monitor', $a);
+$manageurl = new moodle_url("/admin/tool/monitor/managerules.php", array('courseid' => $courseid));
+
+$PAGE->set_url($manageurl);
+$PAGE->set_pagelayout('report');
+$PAGE->set_title($title);
+$PAGE->set_heading($title);
+
+// Site level report.
+if (empty($courseid)) {
+    admin_externalpage_setup('toolmonitorrules', '', null, '', array('pagelayout' => 'report'));
+}
+
+echo $OUTPUT->header();
+
+// Copy/delete rule if needed.
+if (!empty($action) && $ruleid) {
+    require_sesskey();
+    $rule = \tool_monitor\rule_manager::get_rule($ruleid);
+    if ($rule->can_manage_rule()) {
+        switch ($action) {
+            case 'copy' :
+                $rule->duplicate_rule($courseid);
+                echo $OUTPUT->notification(get_string('rulecopysuccess', 'tool_monitor'), 'notifysuccess');
+                break;
+            case 'delete' :
+                $rule->delete_rule();
+                echo $OUTPUT->notification(get_string('ruledeletesuccess', 'tool_monitor'), 'notifysuccess');
+                break;
+            default:
+        }
+    } else {
+        // User doesn't have permissions. Should never happen for real users.
+        throw new moodle_exception('rulenopermissions', 'tool_monitor', $manageurl, $action);
+    }
+}
+
+// Render the rule list.
+$renderable = new \tool_monitor\output\managerules\renderable('toolmonitorrules', $manageurl, $courseid);
+$renderer = $PAGE->get_renderer('tool_monitor', 'managerules');
+echo $renderer->render($renderable);
+echo $OUTPUT->footer();
diff --git a/admin/tool/monitor/settings.php b/admin/tool/monitor/settings.php
new file mode 100644 (file)
index 0000000..9e32468
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Links and settings
+ *
+ * This file contains links and settings used by tool_monitor
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die;
+
+if ($hassiteconfig) {
+
+    // Manage rules page.
+    $url = new moodle_url('/admin/tool/monitor/managerules.php', array('courseid' => 0));
+    $temp = new admin_externalpage('toolmonitorrules', get_string('managerules', 'tool_monitor'), $url,
+        'tool/monitor:managerules');
+    $ADMIN->add('reports', $temp);
+}
diff --git a/admin/tool/monitor/tests/behat/rule.feature b/admin/tool/monitor/tests/behat/rule.feature
new file mode 100644 (file)
index 0000000..80f74d3
--- /dev/null
@@ -0,0 +1,161 @@
+@javascript @tool @tool_monitor @tool_monitor_rules
+Feature: tool_monitor_rule
+  In order to manage rules
+  As an admin
+  I need to create a rule, edit a rule, duplicate a rule and delete a rule
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And   I log in as "admin"
+    And   I follow "Course 1"
+    And   I navigate to "Event monitoring rules" node in "Course administration > Reports"
+    And   I press "Add a new rule"
+    And   I set the following fields to these values:
+      | name              | New rule course level                             |
+      | plugin            | Forum                                             |
+      | eventname         | Post created                                      |
+      | id_description    | I want a rule to monitor posts created on a forum |
+      | frequency         | 1                                                 |
+      | minutes           | 1                                                 |
+      | Message template  | The forum post was created. {modulelink}          |
+    And   I press "Save changes"
+    And   I navigate to "Event monitoring rules" node in "Site administration > Reports"
+    And   I press "Add a new rule"
+    And   I set the following fields to these values:
+      | name              | New rule site level                               |
+      | plugin            | Forum                                             |
+      | eventname         | Post created                                      |
+      | id_description    | I want a rule to monitor posts created on a forum |
+      | frequency         | 1                                                 |
+      | minutes           | 1                                                 |
+      | Message template  | The forum post was created. {modulelink}          |
+    And  I press "Save changes"
+    And  I log out
+
+  Scenario: Add a rule on course level
+    Given I log in as "teacher1"
+    And   I am on homepage
+    And   I follow "Course 1"
+    And   I navigate to "Event monitoring rules" node in "Course administration > Reports"
+    When  I press "Add a new rule"
+    And   I set the following fields to these values:
+      | name              | New rule                                          |
+      | plugin            | Forum                                             |
+      | eventname         | Post created                                      |
+      | id_description    | I want a rule to monitor posts created on a forum |
+      | frequency         | 1                                                 |
+      | minutes           | 1                                                 |
+      | Message template  | The forum post was created. {modulelink}          |
+    And   I press "Save changes"
+    Then  I should see "New rule"
+    And   I should see "I want a rule to monitor posts created on a forum"
+    And   I should see "Forum"
+    And   I should see "Post created"
+    And   I should see "1 times in 1 minutes"
+
+  Scenario: Delete a rule on course level
+    Given I log in as "teacher1"
+    And   I follow "Course 1"
+    And   I navigate to "Event monitoring rules" node in "Course administration > Reports"
+    When  I click on "Delete rule" "link"
+    Then  I should see "Are you sure you want to delete rule \"New rule course level\"?"
+    And   I press "Yes"
+    And   I should see "Rule successfully deleted"
+    And   I should not see "New rule course level"
+
+  Scenario: Edit a rule on course level
+    Given I log in as "teacher1"
+    And   I follow "Course 1"
+    And   I navigate to "Event monitoring rules" node in "Course administration > Reports"
+    When  I click on "Edit rule" "link"
+    And   I set the following fields to these values:
+      | name              | New rule quiz                                  |
+      | plugin            | Quiz                                           |
+      | eventname         | Quiz attempt deleted                           |
+      | id_description    | I want a rule to monitor quiz attempts deleted |
+      | frequency         | 5                                              |
+      | minutes           | 5                                              |
+      | Message template  | Quiz attempt deleted. {modulelink}             |
+    And   I press "Save changes"
+    Then  I should see "New rule quiz"
+    And   I should see "I want a rule to monitor quiz attempts deleted"
+    And   I should see "Quiz attempt deleted"
+    And   I should see "5 times in 5 minutes"
+
+  Scenario: Duplicate a rule on course level
+    Given I log in as "teacher1"
+    And   I follow "Course 1"
+    And   I navigate to "Event monitoring rules" node in "Course administration > Reports"
+    When  I click on "Duplicate rule" "link"
+    Then  I should see "Rule successfully duplicated"
+    And   "#toolmonitorrules_r1" "css_element" should appear before "#toolmonitorrules_r2" "css_element"
+    And   I should see "New rule"
+    And   I should see "I want a rule to monitor posts created on a forum"
+    And   I should see "Forum"
+    And   I should see "Post created"
+    And   I should see "1 times in 1 minutes"
+
+  Scenario: Add a rule on site level
+    Given I log in as "admin"
+    And   I navigate to "Event monitoring rules" node in "Site administration > Reports"
+    When  I press "Add a new rule"
+    And   I set the following fields to these values:
+      | name              | New rule                                          |
+      | plugin            | Forum                                             |
+      | eventname         | Post created                                      |
+      | id_description    | I want a rule to monitor posts created on a forum |
+      | frequency         | 1                                                 |
+      | minutes           | 1                                                 |
+      | Message template  | The forum post was created. {modulelink}          |
+    And   I press "Save changes"
+    Then  I should see "New rule"
+    And   I should see "I want a rule to monitor posts created on a forum"
+    And   I should see "Forum"
+    And   I should see "Post created"
+    And   I should see "1 times in 1 minutes"
+
+  Scenario: Delete a rule on site level
+    Given I log in as "admin"
+    And   I navigate to "Event monitoring rules" node in "Site administration > Reports"
+    When  I click on "Delete rule" "link"
+    Then  I should see "Are you sure you want to delete rule \"New rule site level\"?"
+    And   I press "Yes"
+    And   I should see "Rule successfully deleted"
+    And   I should not see "New rule site level"
+
+  Scenario: Edit a rule on site level
+    Given I log in as "admin"
+    And   I navigate to "Event monitoring rules" node in "Site administration > Reports"
+    When  I click on "Edit rule" "link"
+    And   I set the following fields to these values:
+      | name              | New Rule Quiz                                  |
+      | plugin            | Quiz                                           |
+      | eventname         | Quiz attempt deleted                           |
+      | id_description    | I want a rule to monitor quiz attempts deleted |
+      | frequency         | 5                                              |
+      | minutes           | 5                                              |
+      | Message template  | Quiz attempt deleted. {modulelink}             |
+    And   I press "Save changes"
+    Then  I should see "New Rule Quiz"
+    And   I should see "I want a rule to monitor quiz attempts deleted"
+    And   I should see "Quiz attempt deleted"
+    And   I should see "5 times in 5 minutes"
+
+  Scenario: Duplicate a rule on site level
+    Given I log in as "admin"
+    And   I navigate to "Event monitoring rules" node in "Site administration > Reports"
+    When  I click on "Duplicate rule" "link"
+    Then  I should see "Rule successfully duplicated"
+    And   "#toolmonitorrules_r2" "css_element" should appear after "#toolmonitorrules_r1" "css_element"
+    And   I should see "I want a rule to monitor posts created on a forum"
+    And   I should see "Forum"
+    And   I should see "Post created"
+    And   I should see "1 times in 1 minutes"
diff --git a/admin/tool/monitor/tests/behat/subscription.feature b/admin/tool/monitor/tests/behat/subscription.feature
new file mode 100644 (file)
index 0000000..8d78a0a
--- /dev/null
@@ -0,0 +1,125 @@
+@javascript @tool @tool_monitor @tool_monitor_subscriptions
+Feature: tool_monitor_subscriptions
+  In order to monitor events and receive notifications
+  As an user
+  I need to create a new rule, subscribe to it, receive notification and delete subscription
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And   I log in as "admin"
+    And   I follow "Course 1"
+    And   I navigate to "Event monitoring rules" node in "Course administration > Reports"
+    And   I press "Add a new rule"
+    And   I set the following fields to these values:
+      | name              | New rule course level                             |
+      | plugin            | Core                                              |
+      | eventname         | Course viewed                                     |
+      | id_description    | I want a rule to monitor when a course is viewed. |
+      | frequency         | 1                                                 |
+      | minutes           | 1                                                 |
+      | Message template  | The course was viewed. {modulelink}               |
+    And   I press "Save changes"
+    And   I navigate to "Event monitoring rules" node in "Site administration > Reports"
+    And   I press "Add a new rule"
+    And   I set the following fields to these values:
+      | name              | New rule site level                               |
+      | plugin            | Core                                              |
+      | eventname         | Course viewed                                     |
+      | id_description    | I want a rule to monitor when a course is viewed. |
+      | frequency         | 1                                                 |
+      | minutes           | 1                                                 |
+      | Message template  | The course was viewed. {modulelink}               |
+    And  I press "Save changes"
+    And  I log out
+
+  Scenario: Subscribe to a rule on course level
+    Given I log in as "teacher1"
+    And   I follow "Course 1"
+    And   I navigate to "Event monitoring" node in "My profile settings"
+    And   I set the field "courseid" to "Course 1"
+    When  I set the field "cmid" to "All events"
+    Then  I should see "Subscription successfully created"
+    And   "#toolmonitorsubs_r0" "css_element" should exist
+
+  Scenario: Delete a subscription on course level
+    Given I log in as "teacher1"
+    And   I follow "Course 1"
+    And   I navigate to "Event monitoring" node in "My profile settings"
+    And   I set the field "courseid" to "Course 1"
+    And   I set the field "cmid" to "All events"
+    And   I should see "Subscription successfully created"
+    When  I click on "Delete subscription" "link"
+    And   I should see "Are you sure you want to delete this subscription for the rule \"New rule course level\"?"
+    And   I press "Yes"
+    Then  I should see "Subscription successfully removed"
+    And   "#toolmonitorsubs_r0" "css_element" should not exist
+
+  Scenario: Subscribe to a rule on site level
+    Given I log in as "admin"
+    And   I navigate to "Event monitoring" node in "My profile settings"
+    And   I set the field "courseid" to "Site"
+    When  I set the field "cmid" to "All events"
+    Then  I should see "Subscription successfully created"
+    And   "#toolmonitorsubs_r0" "css_element" should exist
+
+  Scenario: Delete a subscription on site level
+    Given I log in as "admin"
+    And   I navigate to "Event monitoring" node in "My profile settings"
+    And   I set the field "courseid" to "Site"
+    And   I set the field "cmid" to "All events"
+    And   I should see "Subscription successfully created"
+    And   "#toolmonitorsubs_r0" "css_element" should exist
+    When  I click on "Delete subscription" "link"
+    And   I should see "Are you sure you want to delete this subscription for the rule \"New rule site level\"?"
+    And   I press "Yes"
+    Then  I should see "Subscription successfully removed"
+    And   "#toolmonitorsubs_r0" "css_element" should not exist
+
+  Scenario: Receiving notification on site level
+    Given I log in as "admin"
+    And   I navigate to "Messaging" node in "My profile settings"
+    And   I click on "input[name^=tool_monitor_notification_loggedin]" "css_element"
+    And   I press "Update profile"
+    And   I am on homepage
+    And   I follow "Course 1"
+    And   I navigate to "Event monitoring" node in "My profile settings"
+    And   I set the field "courseid" to "Site"
+    And   I set the field "cmid" to "All events"
+    And   I should see "Subscription successfully created"
+    And   "#toolmonitorsubs_r0" "css_element" should exist
+    And   I am on homepage
+    And   I trigger cron
+    And   I am on homepage
+    And   I expand "My profile" node
+    When  I follow "Messages"
+    And   I follow "Do not reply to this email (1)"
+    Then  I should see "The course was viewed."
+
+  Scenario: Receiving notification on course level
+    Given I log in as "teacher1"
+    And   I navigate to "Messaging" node in "My profile settings"
+    And   I click on "input[name^=tool_monitor_notification_loggedin]" "css_element"
+    And   I press "Update profile"
+    And   I am on homepage
+    And   I follow "Course 1"
+    And   I navigate to "Event monitoring" node in "My profile settings"
+    And   I set the field "courseid" to "Course 1"
+    And   I set the field "cmid" to "All events"
+    And   I should see "Subscription successfully created"
+    And   "#toolmonitorsubs_r0" "css_element" should exist
+    And   I am on homepage
+    And   I follow "Course 1"
+    And   I trigger cron
+    And   I am on homepage
+    And   I expand "My profile" node
+    When  I follow "Messages"
+    And   I follow "Do not reply to this email (1)"
+    Then  I should see "The course was viewed."
diff --git a/admin/tool/monitor/tests/eventobservers_test.php b/admin/tool/monitor/tests/eventobservers_test.php
new file mode 100644 (file)
index 0000000..26d4086
--- /dev/null
@@ -0,0 +1,485 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for event observers.
+ *
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/blog/locallib.php');
+require_once($CFG->dirroot . '/blog/lib.php');
+
+/**
+ * Class tool_monitor_eventobservers_testcase
+ *
+ * Tests for event observers
+ */
+class tool_monitor_eventobservers_testcase extends advanced_testcase {
+
+    /**
+     * Test observer for course delete event.
+     */
+    public function test_course_deleted() {
+        global $DB;
+
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        $user = $this->getDataGenerator()->create_user();
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        $rule = new stdClass();
+        $rule->userid = $user->id;
+        $rule->courseid = $course1->id;
+        $rule->plugin = 'test';
+
+        $sub = new stdClass();
+        $sub->courseid = $course1->id;
+        $sub->userid = $user->id;
+
+        // Add 10 rules for this course with subscriptions.
+        for ($i = 0; $i < 10; $i++) {
+            $createdrule = $monitorgenerator->create_rule($rule);
+            $sub->ruleid = $createdrule->id;
+            $monitorgenerator->create_subscription($sub);
+        }
+
+        // Add 10 random rules for course 2.
+        $rule->courseid = $course2->id;
+        for ($i = 0; $i < 10; $i++) {
+            $createdrule = $monitorgenerator->create_rule($rule);
+            $sub->courseid = $rule->courseid;
+            $sub->ruleid = $createdrule->id;
+            $monitorgenerator->create_subscription($sub);
+        }
+
+        // Verify data before course delete.
+        $totalrules = \tool_monitor\rule_manager::get_rules_by_plugin('test');
+        $this->assertCount(20, $totalrules);
+        $courserules = \tool_monitor\rule_manager::get_rules_by_courseid($course1->id);
+        $this->assertCount(10, $courserules);
+        $totalsubs = $DB->get_records('tool_monitor_subscriptions');
+        $this->assertCount(20, $totalsubs);
+        $coursesubs = \tool_monitor\subscription_manager::get_user_subscriptions_for_course($course1->id, 0, 0, $user->id);
+        $this->assertCount(10, $coursesubs);
+
+        // Let us delete the course now.
+        delete_course($course1->id, false);
+
+        // Verify data after course delete.
+        $totalrules = \tool_monitor\rule_manager::get_rules_by_plugin('test');
+        $this->assertCount(10, $totalrules);
+        $courserules = \tool_monitor\rule_manager::get_rules_by_courseid($course1->id);
+        $this->assertCount(0, $courserules); // Making sure all rules are deleted.
+        $totalsubs = $DB->get_records('tool_monitor_subscriptions');
+        $this->assertCount(10, $totalsubs);
+        $coursesubs = \tool_monitor\subscription_manager::get_user_subscriptions_for_course($course1->id, 0, 0, $user->id);
+        $this->assertCount(0, $coursesubs); // Making sure all subscriptions are deleted.
+    }
+
+    /**
+     * This tests if writing of the events to the table tool_monitor_events is working fine.
+     */
+    public function test_flush() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        // Create events and verify data is fine.
+        $course = $this->getDataGenerator()->create_course();
+
+        $initialevents = $DB->get_records('tool_monitor_events');
+        $initalcount = count($initialevents);
+        $event = \mod_book\event\course_module_instance_list_viewed::create_from_course($course);
+        $event->trigger();
+
+        $events = $DB->get_records('tool_monitor_events');
+        $count = count($events);
+        $this->assertEquals($initalcount + 1, $count);
+        $monitorevent = array_pop($events);
+
+        // Match values.
+        $this->assertEquals($event->eventname, $monitorevent->eventname);
+        $this->assertEquals($event->contextid, $monitorevent->contextid);
+        $this->assertEquals($event->contextlevel, $monitorevent->contextlevel);
+        $this->assertEquals($event->get_url()->out(), $monitorevent->link);
+        $this->assertEquals($event->courseid, $monitorevent->courseid);
+        $this->assertEquals($event->timecreated, $monitorevent->timecreated);
+    }
+
+    /**
+     * Test the notification sending features.
+     */
+    public function test_process_event() {
+
+        global $DB, $USER;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $msgsink = $this->redirectMessages();
+
+        // Generate data.
+        $course = $this->getDataGenerator()->create_course();
+        $toolgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        $rulerecord = new stdClass();
+        $rulerecord->courseid = $course->id;
+        $rulerecord->eventname = '\mod_book\event\course_module_instance_list_viewed';
+        $rulerecord->frequency = 1;
+
+        $rule = $toolgenerator->create_rule($rulerecord);
+
+        $subrecord = new stdClass();
+        $subrecord->courseid = $course->id;
+        $subrecord->ruleid = $rule->id;
+        $subrecord->userid = $USER->id;
+        $toolgenerator->create_subscription($subrecord);
+
+        $recordexists = $DB->record_exists('task_adhoc', array('component' => 'tool_monitor'));
+        $this->assertFalse($recordexists);
+
+        // Now let us trigger the event.
+        $event = \mod_book\event\course_module_instance_list_viewed::create_from_course($course);
+        $event->trigger();
+
+        $this->verify_processed_data($msgsink);
+
+        // Clean up.
+        \tool_monitor\rule_manager::delete_rule($rule->id);
+        $DB->delete_records('tool_monitor_events');
+
+        // Let us create a rule with more than 1 frequency.
+        $rulerecord->frequency = 5;
+        $rule = $toolgenerator->create_rule($rulerecord);
+        $subrecord->ruleid = $rule->id;
+        $toolgenerator->create_subscription($subrecord);
+
+        // Let us trigger events.
+        for ($i = 0; $i < 5; $i++) {
+            $event = \mod_book\event\course_module_instance_list_viewed::create_from_course($course);
+            $event->trigger();
+            if ($i != 4) {
+                $this->verify_message_not_sent_yet($msgsink);
+            }
+        }
+
+        $this->verify_processed_data($msgsink);
+
+        // Clean up.
+        \tool_monitor\rule_manager::delete_rule($rule->id);
+        $DB->delete_records('tool_monitor_events');
+
+        // Now let us create a rule specific to a module instance.
+        $cm = new stdClass();
+        $cm->course = $course->id;
+        $book = $this->getDataGenerator()->create_module('book', $cm);
+        $rulerecord->eventname = '\mod_book\event\course_module_viewed';
+        $rulerecord->cmid = $book->cmid;
+        $rule = $toolgenerator->create_rule($rulerecord);
+        $subrecord->ruleid = $rule->id;
+        $toolgenerator->create_subscription($subrecord);
+
+        // Let us trigger events.
+        $params = array(
+            'context' => context_module::instance($book->cmid),
+            'objectid' => $book->id
+        );
+        for ($i = 0; $i < 5; $i++) {
+            $event = \mod_book\event\course_module_viewed::create($params);
+            $event->trigger();
+            if ($i != 4) {
+                $this->verify_message_not_sent_yet($msgsink);
+            }
+        }
+
+        $this->verify_processed_data($msgsink);
+
+        // Clean up.
+        \tool_monitor\rule_manager::delete_rule($rule->id);
+        $DB->delete_records('tool_monitor_events');
+
+        // Now let us create a rule for event that happens in category context events.
+        $rulerecord->eventname = '\core\event\course_category_created';
+        $rulerecord->courseid = 0;
+        $rule = $toolgenerator->create_rule($rulerecord);
+        $subrecord->courseid = 0;
+        $subrecord->ruleid = $rule->id;
+        $toolgenerator->create_subscription($subrecord);
+
+        // Let us trigger events.
+        for ($i = 0; $i < 5; $i++) {
+            $this->getDataGenerator()->create_category();
+            if ($i != 4) {
+                $this->verify_message_not_sent_yet($msgsink);
+            }
+        }
+        $this->verify_processed_data($msgsink);
+
+        // Clean up.
+        \tool_monitor\rule_manager::delete_rule($rule->id);
+        $DB->delete_records('tool_monitor_events');
+
+        // Now let us create a rule at site level.
+        $rulerecord->eventname = '\core\event\blog_entry_created';
+        $rulerecord->courseid = 0;
+        $rule = $toolgenerator->create_rule($rulerecord);
+        $subrecord->courseid = 0;
+        $subrecord->ruleid = $rule->id;
+        $toolgenerator->create_subscription($subrecord);
+
+        // Let us trigger events.
+        $blog = new blog_entry();
+        $blog->subject = "Subject of blog";
+        $blog->userid = $USER->id;
+        $states = blog_entry::get_applicable_publish_states();
+        $blog->publishstate = reset($states);
+        for ($i = 0; $i < 5; $i++) {
+            $newblog = fullclone($blog);
+            $newblog->add();
+            if ($i != 4) {
+                $this->verify_message_not_sent_yet($msgsink);
+            }
+        }
+
+        $this->verify_processed_data($msgsink);
+    }
+
+    /**
+     * Run adhoc tasks.
+     */
+    protected function run_adhock_tasks() {
+        while ($task = \core\task\manager::get_next_adhoc_task(time())) {
+            $task->execute();
+            \core\task\manager::adhoc_task_complete($task);
+        }
+    }
+
+    /**
+     * Verify that task was scheduled and a message was sent as expected.
+     *
+     * @param phpunit_message_sink $msgsink Message sink
+     */
+    protected function verify_processed_data(phpunit_message_sink $msgsink) {
+        global $DB, $USER;
+
+        $recordexists = $DB->count_records('task_adhoc', array('component' => 'tool_monitor'));
+        $this->assertEquals(1, $recordexists); // We should have an adhock task now to send notifications.
+        $this->run_adhock_tasks();
+        $this->assertEquals(1, $msgsink->count());
+        $msgs = $msgsink->get_messages();
+        $msg = array_pop($msgs);
+        $this->assertEquals($USER->id, $msg->useridto);
+        $this->assertEquals(1, $msg->notification);
+        $msgsink->clear();
+    }
+
+    /**
+     * Verify that a message was not sent.
+     *
+     * @param phpunit_message_sink $msgsink Message sink
+     */
+    protected function verify_message_not_sent_yet(phpunit_message_sink $msgsink) {
+        $msgs = $msgsink->get_messages();
+        $this->assertCount(0, $msgs);
+        $msgsink->clear();
+    }
+
+    /**
+     * Tests for replace_placeholders method.
+     */
+    public function test_replace_placeholders() {
+        global $USER;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $msgsink = $this->redirectMessages();
+
+        // Generate data.
+        $course = $this->getDataGenerator()->create_course();
+        $toolgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $context = \context_user::instance($USER->id, IGNORE_MISSING);
+
+        // Creating book.
+        $cm = new stdClass();
+        $cm->course = $course->id;
+        $book = $this->getDataGenerator()->create_module('book', $cm);
+
+        // Creating rule.
+        $rulerecord = new stdClass();
+        $rulerecord->courseid = $course->id;
+        $rulerecord->eventname = '\mod_book\event\course_module_viewed';
+        $rulerecord->cmid = $book->cmid;
+        $rulerecord->frequency = 1;
+        $rulerecord->template = '{link} {modulelink} {rulename} {description} {eventname}';
+
+        $rule = $toolgenerator->create_rule($rulerecord);
+
+        // Creating subscription.
+        $subrecord = new stdClass();
+        $subrecord->courseid = $course->id;
+        $subrecord->ruleid = $rule->id;
+        $subrecord->userid = $USER->id;
+        $toolgenerator->create_subscription($subrecord);
+
+        // Now let us trigger the event.
+        $params = array(
+            'context' => context_module::instance($book->cmid),
+            'objectid' => $book->id
+        );
+
+        $event = \mod_book\event\course_module_viewed::create($params);
+        $event->trigger();
+        $this->run_adhock_tasks();
+        $msgs = $msgsink->get_messages();
+        $msg = array_pop($msgs);
+
+        $modurl = new moodle_url('/mod/book/view.php', array('id' => $book->cmid));
+        $expectedmsg = $event->get_url()->out() . ' ' .
+                        $modurl->out()  . ' ' .
+                        $rule->get_name($context) . ' ' .
+                        $rule->get_description($context) . ' ' .
+                        $rule->get_event_name();
+
+        $this->assertEquals($expectedmsg, $msg->fullmessage);
+    }
+
+    /**
+     * Test observer for user delete event.
+     */
+    public function test_user_deleted() {
+        global $DB;
+
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        $user = $this->getDataGenerator()->create_user();
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        $rule = new stdClass();
+        $rule->userid = $user->id;
+        $rule->courseid = $course1->id;
+        $rule->plugin = 'test';
+
+        $sub = new stdClass();
+        $sub->courseid = $course1->id;
+        $sub->userid = $user->id;
+
+        // Add 10 rules for this course with subscriptions.
+        for ($i = 0; $i < 10; $i++) {
+            $createdrule = $monitorgenerator->create_rule($rule);
+            $sub->ruleid = $createdrule->id;
+            $monitorgenerator->create_subscription($sub);
+        }
+
+        // Add 10 random rules for course 2.
+        $rule->courseid = $course2->id;
+        for ($i = 0; $i < 10; $i++) {
+            $createdrule = $monitorgenerator->create_rule($rule);
+            $sub->courseid = $rule->courseid;
+            $sub->ruleid = $createdrule->id;
+            $monitorgenerator->create_subscription($sub);
+        }
+
+        // Verify data before user delete.
+        $totalrules = \tool_monitor\rule_manager::get_rules_by_plugin('test');
+        $this->assertCount(20, $totalrules);
+        $totalsubs = $DB->get_records('tool_monitor_subscriptions');
+        $this->assertCount(20, $totalsubs);
+
+        // Let us delete the user now.
+        delete_user($user);
+
+        // Verify data after course delete.
+        $totalrules = \tool_monitor\rule_manager::get_rules_by_plugin('test');
+        $this->assertCount(20, $totalrules);
+        $totalsubs = $DB->get_records('tool_monitor_subscriptions');
+        $this->assertCount(0, $totalsubs); // Make sure all subscriptions are deleted.
+    }
+
+    /**
+     * Test observer for course module delete event.
+     */
+    public function test_course_module_deleted() {
+        global $DB;
+
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        $user = $this->getDataGenerator()->create_user();
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        // Now let us create a rule specific to a module instance.
+        $cm = new stdClass();
+        $cm->course = $course1->id;
+        $book = $this->getDataGenerator()->create_module('book', $cm);
+
+        $rule = new stdClass();
+        $rule->userid = $user->id;
+        $rule->courseid = $course1->id;
+        $rule->plugin = 'test';
+
+        $sub = new stdClass();
+        $sub->courseid = $course1->id;
+        $sub->userid = $user->id;
+        $sub->cmid = $book->cmid;
+
+        // Add 10 rules for this course with subscriptions for this module.
+        for ($i = 0; $i < 10; $i++) {
+            $createdrule = $monitorgenerator->create_rule($rule);
+            $sub->ruleid = $createdrule->id;
+            $monitorgenerator->create_subscription($sub);
+        }
+
+        // Add 10 random rules for course 2.
+        $rule->courseid = $course2->id;
+        for ($i = 0; $i < 10; $i++) {
+            $createdrule = $monitorgenerator->create_rule($rule);
+            $sub->courseid = $rule->courseid;
+            $sub->ruleid = $createdrule->id;
+            $sub->cmid = 0;
+            $monitorgenerator->create_subscription($sub);
+        }
+
+        // Verify data before module delete.
+        $totalrules = \tool_monitor\rule_manager::get_rules_by_plugin('test');
+        $this->assertCount(20, $totalrules);
+        $totalsubs = $DB->get_records('tool_monitor_subscriptions');
+        $this->assertCount(20, $totalsubs);
+
+        // Let us delete the user now.
+        course_delete_module($book->cmid);
+
+        // Verify data after course delete.
+        $totalrules = \tool_monitor\rule_manager::get_rules_by_plugin('test');
+        $this->assertCount(20, $totalrules);
+        $totalsubs = $DB->get_records('tool_monitor_subscriptions');
+        $this->assertCount(10, $totalsubs); // Make sure only relevant subscriptions are deleted.
+    }
+
+}
diff --git a/admin/tool/monitor/tests/events_test.php b/admin/tool/monitor/tests/events_test.php
new file mode 100644 (file)
index 0000000..9f55c19
--- /dev/null
@@ -0,0 +1,337 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Events tests.
+ *
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Tests that the tool_monitor events are valid and triggered correctly.
+ */
+class tool_monitor_events_testcase extends advanced_testcase {
+
+    /**
+     * Tests set up.
+     */
+    public function setUp() {
+        $this->resetAfterTest();
+    }
+
+    /**
+     * Test the rule created event.
+     */
+    public function test_rule_created() {
+        // Create the items we need to create a rule.
+        $course = $this->getDataGenerator()->create_course();
+        $user = $this->getDataGenerator()->create_user();
+
+        // Create the variables for the rule we want to create.
+        $ruledata = new stdClass();
+        $ruledata->userid = $user->id;
+        $ruledata->courseid = $course->id;
+        $ruledata->description = 'Rule description';
+        $ruledata->descriptionformat = FORMAT_HTML;
+        $ruledata->template = 'A message template';
+        $ruledata->templateformat = FORMAT_HTML;
+        $ruledata->frequency = 1;
+        $ruledata->timewindow = 60;
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $rule = \tool_monitor\rule_manager::add_rule($ruledata);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event contains the expected values.
+        $this->assertInstanceOf('\tool_monitor\event\rule_created', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($rule->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+
+        // Now let's add a system rule (courseid = 0).
+        $ruledata->courseid = 0;
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\rule_manager::add_rule($ruledata);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event uses the system context.
+        $this->assertInstanceOf('\tool_monitor\event\rule_created', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+    }
+
+    /**
+     * Test the rule updated event.
+     */
+    public function test_rule_updated() {
+        // Create the items we need.
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $course = $this->getDataGenerator()->create_course();
+
+        // Create the rule we are going to update.
+        $createrule = new stdClass();
+        $createrule->courseid = $course->id;
+        $rule = $monitorgenerator->create_rule($createrule);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $updaterule = new stdClass();
+        $updaterule->id = $rule->id;
+        \tool_monitor\rule_manager::update_rule($updaterule);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event contains the expected values.
+        $this->assertInstanceOf('\tool_monitor\event\rule_updated', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($rule->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+
+        // Now let's update a system rule (courseid = 0).
+        $createrule->courseid = 0;
+        $rule = $monitorgenerator->create_rule($createrule);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $updaterule = new stdClass();
+        $updaterule->id = $rule->id;
+        \tool_monitor\rule_manager::update_rule($updaterule);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event uses the system context.
+        $this->assertInstanceOf('\tool_monitor\event\rule_updated', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+    }
+
+    /**
+     * Test the rule deleted event.
+     */
+    public function test_rule_deleted() {
+        // Create the items we need.
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $course = $this->getDataGenerator()->create_course();
+
+        // Create the rule we are going to delete.
+        $createrule = new stdClass();
+        $createrule->courseid = $course->id;
+        $rule = $monitorgenerator->create_rule($createrule);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\rule_manager::delete_rule($rule->id);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event contains the expected values.
+        $this->assertInstanceOf('\tool_monitor\event\rule_deleted', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($rule->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+
+        // Now let's delete a system rule (courseid = 0).
+        $createrule = new stdClass();
+        $createrule->courseid = 0;
+        $rule = $monitorgenerator->create_rule($createrule);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\rule_manager::delete_rule($rule->id);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event uses the system context.
+        $this->assertInstanceOf('\tool_monitor\event\rule_deleted', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+    }
+
+    /**
+     * Test the subscription created event.
+     */
+    public function test_subscription_created() {
+        // Create the items we need to test this.
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        // Create a rule to subscribe to.
+        $rule = $monitorgenerator->create_rule();
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $subscriptionid = \tool_monitor\subscription_manager::create_subscription($rule->id, $course->id, 0, $user->id);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event contains the expected values.
+        $this->assertInstanceOf('\tool_monitor\event\subscription_created', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($subscriptionid, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+
+        // Create a system subscription - trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\subscription_manager::create_subscription($rule->id, 0, 0, $user->id);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event uses the system context.
+        $this->assertInstanceOf('\tool_monitor\event\subscription_created', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+    }
+
+    /**
+     * Test the subscription deleted event.
+     */
+    public function test_subscription_deleted() {
+        // Create the items we need to test this.
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        // Create a rule to subscribe to.
+        $rule = $monitorgenerator->create_rule();
+
+        $sub = new stdClass();
+        $sub->courseid = $course->id;
+        $sub->userid = $user->id;
+        $sub->ruleid = $rule->id;
+
+        // Create the subscription we are going to delete.
+        $subscription = $monitorgenerator->create_subscription($sub);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\subscription_manager::delete_subscription($subscription->id, false);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event contains the expected values.
+        $this->assertInstanceOf('\tool_monitor\event\subscription_deleted', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($subscription->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+
+        // Now let's delete a system subscription.
+        $sub = new stdClass();
+        $sub->courseid = 0;
+        $sub->userid = $user->id;
+        $sub->ruleid = $rule->id;
+
+        // Create the subscription we are going to delete.
+        $subscription = $monitorgenerator->create_subscription($sub);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\subscription_manager::delete_subscription($subscription->id, false);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event uses the system context.
+        $this->assertInstanceOf('\tool_monitor\event\subscription_deleted', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+
+        // Now, create a bunch of subscriptions for the rule we created.
+        $sub->courseid = $course->id;
+        for ($i = 1; $i <= 10; $i++) {
+            $sub->userid = $i;
+            $subscription = $monitorgenerator->create_subscription($sub);
+            if ($i == 1) {
+                $subscription1 = $subscription;
+            }
+        }
+
+        // Trigger and capture the events.
+        $sink = $this->redirectEvents();
+        \tool_monitor\subscription_manager::remove_all_subscriptions_for_rule($rule->id);
+        $events = $sink->get_events();
+
+        // Check that there were 10 events in total.
+        $this->assertCount(10, $events);
+
+        // Get the first event and ensure it is valid (we can assume the rest are the same).
+        $event = reset($events);
+        $this->assertInstanceOf('\tool_monitor\event\subscription_deleted', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($subscription1->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the subscription criteria met event.
+     */
+    public function test_subscription_criteria_met() {
+        // Create the items we need to test this.
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
+        $bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
+        $chapter = $bookgenerator->create_chapter(array('bookid' => $book->id));
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        // Create a rule we want to subscribe to.
+        $rule = new stdClass();
+        $rule->userid = $user->id;
+        $rule->courseid = $course->id;
+        $rule->plugin = 'mod_book';
+        $rule->eventname = '\mod_book\event\chapter_viewed';
+        $rule->frequency = 1;
+        $rule->timewindow = 60;
+        $rule = $monitorgenerator->create_rule($rule);
+
+        // Create the subscription.
+        $sub = new stdClass();
+        $sub->courseid = $course->id;
+        $sub->userid = $user->id;
+        $sub->ruleid = $rule->id;
+        $monitorgenerator->create_subscription($sub);
+
+        // Now create the \mod_book\event\chapter_viewed event we are listening for.
+        $context = context_module::instance($book->cmid);
+        $event = \mod_book\event\chapter_viewed::create_from_chapter($book, $context, $chapter);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\eventobservers::process_event($event);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event contains the expected values.
+        $this->assertInstanceOf('\tool_monitor\event\subscription_criteria_met', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEventContextNotUsed($event);
+    }
+}
diff --git a/admin/tool/monitor/tests/generator/lib.php b/admin/tool/monitor/tests/generator/lib.php
new file mode 100644 (file)
index 0000000..26c973d
--- /dev/null
@@ -0,0 +1,200 @@
+<?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/>.
+
+/**
+ * Event monitor data generator
+ *
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2014 onwards Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event monitor data generator class
+ *
+ * @since       Moodle 2.8
+ * @package     tool_monitor
+ * @category    test
+ * @copyright   2014 onwards Simey Lameze <simey@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_monitor_generator extends testing_module_generator {
+
+    /**
+     * @var int keep track of how many rules have been created.
+     */
+    protected $rulecount;
+
+    /**
+     * Function to generate rule data.
+     *
+     * @param \stdClass|array $record data to insert as rule entry.
+     *
+     * @return \tool_monitor\rule An instance of rule class.
+     */
+    public function create_rule($record = null) {
+        global $USER;
+
+        $this->rulecount++;
+        $i = $this->rulecount;
+        $now = time();
+        $record = (object)(array)$record;
+
+        if (!isset($record->userid)) {
+            $record->userid = $USER->id;
+        }
+        if (!isset($record->courseid)) {
+            $record->courseid = 0;
+        }
+        if (!isset($record->name)) {
+            $record->name = 'Test rule ' . $i;
+        }
+        if (!isset($record->description)) {
+            $record->description = 'Rule description ' . $i;
+        }
+        if (!isset($record->descriptionformat)) {
+            $record->descriptionformat = FORMAT_HTML;
+        }
+        if (!isset($record->frequency)) {
+            $record->frequency = 5;
+        }
+        if (!isset($record->minutes)) {
+            $record->minutes = 5;
+        }
+        if (!isset($record->template)) {
+            $record->template = 'Rule message template ' . $i;
+        }
+        if (!isset($record->templateformat)) {
+            $record->templateformat = FORMAT_HTML;
+        }
+        if (!isset($record->timewindow)) {
+            $record->timewindow = $record->minutes * 60;
+        }
+        if (!isset($record->timecreated)) {
+            $record->timecreated = $now;
+        }
+        if (!isset($record->timemodified)) {
+            $record->timemodified = $now;
+        }
+        if (!isset($record->plugin)) {
+            $record->plugin = 'core';
+        }
+        if (!isset($record->eventname)) {
+            $record->eventname = '\core\event\blog_entry_created';
+        }
+
+        unset($record->minutes); // Remove the minutes shortcut to the timewindow.
+        return \tool_monitor\rule_manager::add_rule($record);
+    }
+
+    /**
+     * Function to generate subscription data.
+     *
+     * @throws coding_exception if $record->ruleid or $record->userid not present.
+     * @param \stdClass|array $record data to insert as subscription entry.
+     *
+     * @return \tool_monitor\subscription An instance of the subscription class.
+     */
+    public function create_subscription($record = null) {
+
+        if (!isset($record->timecreated)) {
+            $record->timecreated = time();
+        }
+        if (!isset($record->courseid)) {
+            $record->courseid = 0;
+        }
+        if (!isset($record->ruleid)) {
+            throw new coding_exception('$record->ruleid must be present in tool_monitor_generator::create_subscription()');
+        }
+        if (!isset($record->cmid)) {
+            $record->cmid = 0;
+        }
+        if (!isset($record->userid)) {
+            throw new coding_exception('$record->userid must be present in tool_monitor_generator::create_subscription()');
+        }
+
+        $sid = \tool_monitor\subscription_manager::create_subscription($record->ruleid, $record->courseid,
+                $record->cmid, $record->userid);
+        return \tool_monitor\subscription_manager::get_subscription($sid);
+    }
+
+    /**
+     * Function to generate event entries.
+     *
+     * @param \stdClass|array $record data to insert as event entry.
+     *
+     * @return \stdClass $record An object representing the newly created event entry.
+     */
+    public function create_event_entries($record = null) {
+        global $DB, $CFG;
+
+        $record = (object)(array)$record;
+        $context = \context_system::instance();
+
+        if (!isset($record->eventname)) {
+            $record->eventname = '\core\event\user_loggedin';
+        }
+        if (!isset($record->contextid)) {
+            $record->contextid = $context->id;
+        }
+        if (!isset($record->contextlevel)) {
+            $record->contextlevel = $context->contextlevel;
+        }
+        if (!isset($record->contextinstanceid)) {
+            $record->contextinstanceid = $context->instanceid;
+        }
+        if (!isset($record->link)) {
+            $record->link = $CFG->wwwroot . '/user/profile.php';
+        }
+        if (!isset($record->courseid)) {
+            $record->courseid = 0;
+        }
+        if (!isset($record->timecreated)) {
+            $record->timecreated = time();
+        }
+        $record->id = $DB->insert_record('tool_monitor_events', $record, true);
+
+        return $record;
+    }
+
+    /**
+     * Function to generate history data.
+     *
+     * @throws coding_exception if $record->sid or $record->userid not present.
+     * @param \stdClass|array $record data to insert as history entry.
+     *
+     * @return \stdClass $record An object representing the newly created history entry.
+     */
+    public function create_history($record = null) {
+        global $DB;
+        $record = (object)(array)$record;
+        if (!isset($record->sid)) {
+            throw new coding_exception('subscription ID must be present in tool_monitor_generator::create_history() $record');
+        }
+        if (!isset($record->userid)) {
+            throw new coding_exception('user ID must be present in tool_monitor_generator::create_history() $record');
+        }
+        if (!isset($record->timesent)) {
+            $record->timesent = time();
+        }
+        $record->id = $DB->insert_record('tool_monitor_history', $record, true);
+
+        return $record;
+    }
+}
diff --git a/admin/tool/monitor/tests/generator_test.php b/admin/tool/monitor/tests/generator_test.php
new file mode 100644 (file)
index 0000000..7e6403e
--- /dev/null
@@ -0,0 +1,133 @@
+<?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/>.
+
+/**
+ * PHPUnit data generator tests.
+ *
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2014 onwards Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * PHPUnit data generator test case.
+ *
+ * @since      Moodle 2.8
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2014 onwards Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_monitor_generator_testcase extends advanced_testcase {
+
+    /**
+     * Test create_rule data generator.
+     */
+    public function test_create_rule() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+        $course = $this->getDataGenerator()->create_course();
+        $user = $this->getDataGenerator()->create_user();
+
+        $rulegenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        $record = new stdClass();
+        $record->courseid = $course->id;
+        $record->userid = $user->id;
+
+        $rule = $rulegenerator->create_rule($record);
+        $this->assertInstanceOf('tool_monitor\rule', $rule);
+        $this->assertEquals($rule->userid, $record->userid);
+        $this->assertEquals($rule->courseid, $record->courseid);
+    }
+
+    /**
+     * Test create_subscription data generator.
+     */
+    public function test_create_subscription() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $rule = $monitorgenerator->create_rule();
+
+        $record = new stdClass();
+        $record->courseid = $course->id;
+        $record->userid = $user->id;
+        $record->ruleid = $rule->id;
+
+        $subscription = $monitorgenerator->create_subscription($record);
+        $this->assertEquals($record->courseid, $subscription->courseid);
+        $this->assertEquals($record->ruleid, $subscription->ruleid);
+        $this->assertEquals($record->userid, $subscription->userid);
+        $this->assertEquals(0, $subscription->cmid);
+
+        // Make sure rule id is always required.
+        $this->setExpectedException('coding_exception');
+        unset($record->ruleid);
+        $monitorgenerator->create_subscription($record);
+    }
+
+    /**
+     * Test create_event data generator.
+     */
+    public function test_create_event_entries() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+        $context = \context_system::instance();
+
+        // Default data generator values.
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        // First create and assertdata using default values.
+        $eventdata = $monitorgenerator->create_event_entries();
+        $this->assertEquals('\core\event\user_loggedin', $eventdata->eventname);
+        $this->assertEquals($context->id, $eventdata->contextid);
+        $this->assertEquals($context->contextlevel, $eventdata->contextlevel);
+    }
+
+    /**
+     * Test create_history data generator.
+     */
+    public function test_create_history() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+        $user = $this->getDataGenerator()->create_user();
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $rule = $monitorgenerator->create_rule();
+
+        $record = new \stdClass();
+        $record->userid = $user->id;
+        $record->ruleid = $rule->id;
+        $sid = $monitorgenerator->create_subscription($record)->id;
+        $record->sid = $sid;
+        $historydata = $monitorgenerator->create_history($record);
+        $this->assertEquals($record->userid, $historydata->userid);
+        $this->assertEquals($record->sid, $historydata->sid);
+
+        // Test using default values.
+        $record->userid = 1;
+        $record->sid = 1;
+        $historydata = $monitorgenerator->create_history($record);
+        $this->assertEquals(1, $historydata->userid);
+        $this->assertEquals(1, $historydata->sid);
+    }
+}
\ No newline at end of file
diff --git a/admin/tool/monitor/tests/rule_manager_test.php b/admin/tool/monitor/tests/rule_manager_test.php
new file mode 100644 (file)
index 0000000..72d04e8
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for rule manager api.
+ *
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2014 onwards Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+/**
+ * Tests for rule manager.
+ *
+ * Class tool_monitor_rule_manager_testcase
+ */
+class tool_monitor_rule_manager_testcase extends advanced_testcase {
+
+    /**
+     * Test add_rule method.
+     */
+    public function test_add_rule() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $now = time();
+
+        $rule = new stdClass();
+        $rule->userid = $user->id;
+        $rule->courseid = $course->id;
+        $rule->name = 'test rule 1';
+        $rule->plugin = 'core';
+        $rule->eventname = '\core\event\course_updated';
+        $rule->description = 'test description 1';
+        $rule->descriptionformat = FORMAT_HTML;
+        $rule->frequency = 15;
+        $rule->template = 'test template message';
+        $rule->templateformat = FORMAT_HTML;
+        $rule->timewindow = 300;
+        $rule->timecreated = $now;
+        $rule->timemodified = $now;
+
+        $ruledata = \tool_monitor\rule_manager::add_rule($rule);
+        foreach ($rule as $prop => $value) {
+            $this->assertEquals($ruledata->$prop, $value);
+        }
+    }
+
+    /**
+     * Test get_rule method.
+     */
+    public function test_get_rule() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $rule = $monitorgenerator->create_rule();
+        $rules1 = \tool_monitor\rule_manager::get_rule($rule->id);
+        $this->assertInstanceOf('tool_monitor\rule', $rules1);
+        $this->assertEquals($rules1, $rule);
+    }
+
+    /**
+     * Test update_rule method.
+     */
+    public function test_update_rule() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $rule = $monitorgenerator->create_rule();
+
+        $ruledata = new stdClass;
+        $ruledata->id = $rule->id;
+        $ruledata->frequency = 25;
+
+        \tool_monitor\rule_manager::update_rule($ruledata);
+        $this->assertEquals(25, $ruledata->frequency);
+
+    }
+
+    /**
+     * Test get_rules_by_courseid method.
+     */
+    public function test_get_rules_by_courseid() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+
+        $record = new stdClass();
+        $record->courseid = $course1->id;
+
+        $record2 = new stdClass();
+        $record2->courseid = $course2->id;
+
+        $ruleids = array();
+        for ($i = 0; $i < 10; $i++) {
+            $rule = $monitorgenerator->create_rule($record);
+            $ruleids[] = $rule->id;
+            $rule = $monitorgenerator->create_rule(); // Create some site level rules.
+            $ruleids[] = $rule->id;
+            $rule = $monitorgenerator->create_rule($record2); // Create rules in a different course.
+        }
+        $ruledata = \tool_monitor\rule_manager::get_rules_by_courseid($course1->id);
+        $this->assertEquals($ruleids, array_keys($ruledata));
+        $this->assertCount(20, $ruledata);
+    }
+
+    /**
+     * Test get_rules_by_plugin method.
+     */
+    public function test_get_rules_by_plugin() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        $record = new stdClass();
+        $record->plugin = 'core';
+
+        $record2 = new stdClass();
+        $record2->plugin = 'mod_assign';
+
+        $ruleids = array();
+        for ($i = 0; $i < 10; $i++) {
+            $rule = $monitorgenerator->create_rule($record);
+            $ruleids[] = $rule->id;
+            $rule = $monitorgenerator->create_rule($record2); // Create rules in a different plugin.
+        }
+
+        $ruledata = \tool_monitor\rule_manager::get_rules_by_plugin('core');
+        $this->assertEquals($ruleids, array_keys($ruledata));
+        $this->assertCount(10, $ruledata);
+    }
+
+    /**
+     * Test get_rules_by_event method.
+     */
+    public function test_get_rules_by_event() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $rule = $monitorgenerator->create_rule();
+
+        $record = new stdClass();
+        $record->eventname = '\core\event\calendar_event_created';
+
+        $record2 = new stdClass();
+        $record2->eventname = '\core\event\calendar_event_updated';
+
+        $ruleids = array();
+        for ($i = 0; $i < 10; $i++) {
+            $rule = $monitorgenerator->create_rule($record);
+            $ruleids[] = $rule->id;
+            $rule = $monitorgenerator->create_rule($record2); // Create rules in a different plugin.
+        }
+
+        $ruledata = \tool_monitor\rule_manager::get_rules_by_event('\core\event\calendar_event_created');
+        $this->assertEmpty(array_diff(array_keys($ruledata), $ruleids));
+        $this->assertCount(10, $ruledata);
+    }
+}
\ No newline at end of file
diff --git a/admin/tool/monitor/tests/task_clean_events_test.php b/admin/tool/monitor/tests/task_clean_events_test.php
new file mode 100644 (file)
index 0000000..6737c1b
--- /dev/null
@@ -0,0 +1,219 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for the tool_monitor clean events task.
+ *
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+/**
+ * Class used to test the tool_monitor clean events task.
+ */
+class tool_monitor_task_clean_events_testcase extends advanced_testcase {
+
+    /**
+     * Test set up.
+     */
+    public function setUp() {
+        $this->resetAfterTest(true);
+    }
+
+    /**
+     * Tests the cleaning up of events.
+     */
+    public function test_clean_events() {
+        global $DB;
+
+        // Create the necessary items for testing.
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
+        $book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
+        $bookcontext = context_module::instance($book->cmid);
+        $bookchapter = $bookgenerator->create_chapter(array('bookid' => $book->id));
+        $course2 = $this->getDataGenerator()->create_course();
+        $book2 = $this->getDataGenerator()->create_module('book', array('course' => $course2->id));
+        $book2context = context_module::instance($book2->cmid);
+        $book2chapter = $bookgenerator->create_chapter(array('bookid' => $book2->id));
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        // Let's set some data for the rules we need before we can generate them.
+        $rule = new stdClass();
+        $rule->userid = $user->id;
+        $rule->courseid = $course->id;
+        $rule->plugin = 'mod_book';
+        $rule->eventname = '\mod_book\event\course_module_viewed';
+        $rule->timewindow = 500;
+
+        // Let's add a few rules we want to monitor.
+        $rule1 = $monitorgenerator->create_rule($rule);
+
+        $rule->eventname = '\mod_book\event\course_module_instance_list_viewed';
+        $rule2 = $monitorgenerator->create_rule($rule);
+
+        // Add the same rules for the same course, but this time with a lower timewindow (used to test that we do not
+        // remove an event for a course if there is still a rule where the maximum timewindow has not been reached).
+        $rule->eventname = '\mod_book\event\course_module_viewed';
+        $rule->timewindow = 200;
+        $rule3 = $monitorgenerator->create_rule($rule);
+
+        $rule->eventname = '\mod_book\event\course_module_instance_list_viewed';
+        $rule4 = $monitorgenerator->create_rule($rule);
+
+        // Add another rule in a different course.
+        $rule->courseid = $course2->id;
+        $rule->eventname = '\mod_book\event\chapter_viewed';
+        $rule->timewindow = 200;
+        $rule5 = $monitorgenerator->create_rule($rule);
+
+        // Add a site wide rule.
+        $rule->courseid = 0;
+        $rule->eventname = '\mod_book\event\chapter_viewed';
+        $rule->timewindow = 500;
+        $rule6 = $monitorgenerator->create_rule($rule);
+
+        // Now let's populate the tool_monitor table with the events associated with those rules.
+        \mod_book\event\course_module_viewed::create_from_book($book, $bookcontext)->trigger();
+        \mod_book\event\course_module_instance_list_viewed::create_from_course($course)->trigger();
+        \mod_book\event\chapter_viewed::create_from_chapter($book, $bookcontext, $bookchapter)->trigger();
+
+        // Let's trigger the viewed events again, but in another course. The rules created for these events are
+        // associated with another course, so these events should get deleted when we trigger the cleanup task.
+        \mod_book\event\course_module_viewed::create_from_book($book2, $book2context)->trigger();
+        \mod_book\event\course_module_instance_list_viewed::create_from_course($course2)->trigger();
+        // Trigger a chapter_viewed event in this course - this should not get deleted as the rule is site wide.
+        \mod_book\event\chapter_viewed::create_from_chapter($book2, $book2context, $book2chapter)->trigger();
+
+        // Trigger a bunch of other events.
+        $eventparams = array(
+            'context' => context_course::instance($course->id)
+        );
+        for ($i = 0; $i < 5; $i++) {
+            \mod_quiz\event\course_module_instance_list_viewed::create($eventparams)->trigger();
+            \mod_scorm\event\course_module_instance_list_viewed::create($eventparams)->trigger();
+        }
+
+        // Check that the events exist - there will be additional events for creating courses, modules and rules.
+        $this->assertEquals(26, $DB->count_records('tool_monitor_events'));
+
+        // Run the task and check that all the quiz, scorm and rule events are removed as well as the course_module_*
+        // viewed events in the second course.
+        $task = new \tool_monitor\task\clean_events();
+        $task->execute();
+
+        $events = $DB->get_records('tool_monitor_events');
+        $this->assertEquals(4, count($events));
+        $event1 = array_shift($events);
+        $event2 = array_shift($events);
+        $event3 = array_shift($events);
+        $event4 = array_shift($events);
+        $this->assertEquals('\mod_book\event\course_module_viewed', $event1->eventname);
+        $this->assertEquals($course->id, $event1->courseid);
+        $this->assertEquals('\mod_book\event\course_module_instance_list_viewed', $event2->eventname);
+        $this->assertEquals($course->id, $event2->courseid);
+        $this->assertEquals('\mod_book\event\chapter_viewed', $event3->eventname);
+        $this->assertEquals($course->id, $event3->courseid);
+        $this->assertEquals('\mod_book\event\chapter_viewed', $event4->eventname);
+        $this->assertEquals($course2->id, $event4->courseid);
+
+        // Update the timewindow for two of the rules.
+        $updaterule = new stdClass();
+        $updaterule->id = $rule1->id;
+        $updaterule->timewindow = 0;
+        \tool_monitor\rule_manager::update_rule($updaterule);
+        $updaterule->id = $rule2->id;
+        \tool_monitor\rule_manager::update_rule($updaterule);
+
+        // Run the task and check that the events remain as we still have not reached the maximum timewindow.
+        $task = new \tool_monitor\task\clean_events();
+        $task->execute();
+
+        $this->assertEquals(4, $DB->count_records('tool_monitor_events'));
+
+        // Now, remove the rules associated with course_module_* events so they get deleted.
+        \tool_monitor\rule_manager::delete_rule($rule1->id);
+        \tool_monitor\rule_manager::delete_rule($rule2->id);
+        \tool_monitor\rule_manager::delete_rule($rule3->id);
+        \tool_monitor\rule_manager::delete_rule($rule4->id);
+
+        // Run the task and check all the course_module_* events are gone.
+        $task = new \tool_monitor\task\clean_events();
+        $task->execute();
+
+        // We now should only have the chapter_viewed events.
+        $events = $DB->get_records('tool_monitor_events');
+        $this->assertEquals(2, count($events));
+        $event1 = array_shift($events);
+        $event2 = array_shift($events);
+        $this->assertEquals('\mod_book\event\chapter_viewed', $event1->eventname);
+        $this->assertEquals($course->id, $event1->courseid);
+        $this->assertEquals('\mod_book\event\chapter_viewed', $event2->eventname);
+        $this->assertEquals($course2->id, $event2->courseid);
+
+        // Set the timewindow of the rule for the event chapter_viewed in the second course to 0.
+        $updaterule->id = $rule5->id;
+        \tool_monitor\rule_manager::update_rule($updaterule);
+
+        // Run the task.
+        $task = new \tool_monitor\task\clean_events();
+        $task->execute();
+
+        // Check that nothing was deleted as we still have a site wide rule for the chapter_viewed event.
+        $this->assertEquals(2, $DB->count_records('tool_monitor_events'));
+
+        // Set the timewindow back to 500.
+        $updaterule->id = $rule5->id;
+        $updaterule->timewindow = 500;
+        \tool_monitor\rule_manager::update_rule($updaterule);
+
+        // Set the site rule timewindow to 0.
+        $updaterule->id = $rule6->id;
+        $updaterule->timewindow = 0;
+        \tool_monitor\rule_manager::update_rule($updaterule);
+
+        // Run the task.
+        $task = new \tool_monitor\task\clean_events();
+        $task->execute();
+
+        // We now should only have one chapter_viewed event for the second course.
+        $events = $DB->get_records('tool_monitor_events');
+        $this->assertEquals(1, count($events));
+        $event1 = array_shift($events);
+        $this->assertEquals('\mod_book\event\chapter_viewed', $event1->eventname);
+        $this->assertEquals($course2->id, $event1->courseid);
+
+        // Remove the site rule.
+        \tool_monitor\rule_manager::delete_rule($rule6->id);
+
+        // Remove the last remaining rule.
+        \tool_monitor\rule_manager::delete_rule($rule5->id);
+
+        // Run the task.
+        $task = new \tool_monitor\task\clean_events();
+        $task->execute();
+
+        // There should be no events left.
+        $this->assertEquals(0, $DB->count_records('tool_monitor_events'));
+    }
+}
diff --git a/admin/tool/monitor/version.php b/admin/tool/monitor/version.php
new file mode 100644 (file)
index 0000000..b8150b0
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Version info
+ *
+ * This file contains version information about tool_monitor.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$plugin->version   = 2014061901;       // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2014061900;       // Requires this Moodle version.
+$plugin->component = 'tool_monitor'; // Full name of the plugin (used for diagnostics).
diff --git a/admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown-debug.js b/admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown-debug.js
new file mode 100644 (file)
index 0000000..79184dd
Binary files /dev/null and b/admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown-debug.js differ
diff --git a/admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown-min.js b/admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown-min.js
new file mode 100644 (file)
index 0000000..c62a013
Binary files /dev/null and b/admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown-min.js differ
diff --git a/admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown.js b/admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown.js
new file mode 100644 (file)
index 0000000..79184dd
Binary files /dev/null and b/admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown.js differ
diff --git a/admin/tool/monitor/yui/src/dropdown/build.json b/admin/tool/monitor/yui/src/dropdown/build.json
new file mode 100644 (file)
index 0000000..66630f6
--- /dev/null
@@ -0,0 +1,10 @@
+{
+    "name": "moodle-tool_monitor-dropdown",
+    "builds": {
+        "moodle-tool_monitor-dropdown": {
+            "jsfiles": [
+                "dropdown.js"
+            ]
+        }
+    }
+}
diff --git a/admin/tool/monitor/yui/src/dropdown/js/dropdown.js b/admin/tool/monitor/yui/src/dropdown/js/dropdown.js
new file mode 100644 (file)
index 0000000..ff839b6
--- /dev/null
@@ -0,0 +1,103 @@
+/**
+ * A module to manage dropdowns on the rule add/edit form.
+ *
+ * @module moodle-tool_monitor-dropdown
+ */
+
+/**
+ * A module to manage dependent selects on the edit page.
+ *
+ * @since Moodle 2.8
+ * @class moodle-tool_monitor.dropdown
+ * @extends Base
+ * @constructor
+ */
+function DropDown() {
+    DropDown.superclass.constructor.apply(this, arguments);
+}
+
+
+var SELECTORS = {
+        PLUGIN: '#id_plugin',
+        EVENTNAME: '#id_eventname',
+        OPTION: 'option',
+        CHOOSE: 'option[value=""]'
+    };
+
+Y.extend(DropDown, Y.Base, {
+
+    /**
+     * Reference to the plugin node.
+     *
+     * @property plugin
+     * @type Object
+     * @default null
+     * @protected
+     */
+    plugin: null,
+
+    /**
+     * Reference to the plugin node.
+     *
+     * @property eventname
+     * @type Object
+     * @default null
+     * @protected
+     */
+    eventname: null,
+
+    /**
+     * Initializer.
+     * Basic setup and delegations.
+     *
+     * @method initializer
+     */
+    initializer: function() {
+        this.plugin = Y.one(SELECTORS.PLUGIN);
+        this.eventname = Y.one(SELECTORS.EVENTNAME);
+        this.plugin.on('change', this.updateEventsList, this);
+    },
+
+    /**
+     * Method to update the events list drop down when plugin list drop down is changed.
+     *
+     * @method updateEventsList
+     */
+    updateEventsList: function() {
+        var node, options, choosenode;
+        var plugin = this.plugin.get('value'); // Get component name.
+        var namespace = '\\' + plugin + '\\';
+        this.eventname.all(SELECTORS.OPTION).remove(true); // Delete all nodes.
+        options = this.get('eventlist');
+
+        // Mark the default choose node as visible and selected.
+        choosenode = Y.Node.create('<option value="">' + options[''] + '</option>');
+        choosenode.set('selected', 'selected');
+        this.eventname.appendChild(choosenode);
+
+        Y.Object.each(options, function(value, key) {
+            // Make sure we highlight only nodes with correct namespace.
+            if (key.substring(0, namespace.length) === namespace) {
+                node = Y.Node.create('<option value="' + key + '">' + value + '</option>');
+                this.eventname.appendChild(node);
+            }
+        }, this);
+
+    }
+}, {
+    NAME: 'dropDown',
+    ATTRS: {
+        /**
+         * A list of events with components.
+         *
+         * @attribute eventlist
+         * @default null
+         * @type Object
+         */
+        eventlist: null
+    }
+});
+
+Y.namespace('M.tool_monitor.DropDown').init = function(config) {
+    return new DropDown(config);
+};
diff --git a/admin/tool/monitor/yui/src/dropdown/meta/dropdown.json b/admin/tool/monitor/yui/src/dropdown/meta/dropdown.json
new file mode 100644 (file)
index 0000000..34fa63f
--- /dev/null
@@ -0,0 +1,9 @@
+{
+  "moodle-tool_monitor-dropdown": {
+    "requires": [
+        "base",
+        "event",
+        "node"
+    ]
+  }
+}
index fd33545..a173e72 100644 (file)
@@ -35,6 +35,13 @@ global $CFG;
  */
 class tool_uploadcourse_course_testcase extends advanced_testcase {
 
+    /**
+     * Tidy up open files that may be left open.
+     */
+    protected function tearDown() {
+        gc_collect_cycles();
+    }
+
     public function test_proceed_without_prepare() {
         $this->resetAfterTest(true);
         $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;