MDL-59890 calendar: Add support for the category to vault
authorAndrew Nicols <andrew@nicols.co.uk>
Tue, 19 Sep 2017 03:39:39 +0000 (11:39 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Tue, 3 Oct 2017 13:28:31 +0000 (21:28 +0800)
24 files changed:
calendar/classes/external/event_exporter.php
calendar/classes/local/api.php
calendar/classes/local/event/container.php
calendar/classes/local/event/data_access/event_vault.php
calendar/classes/local/event/data_access/event_vault_interface.php
calendar/classes/local/event/entities/action_event.php
calendar/classes/local/event/entities/event.php
calendar/classes/local/event/entities/event_interface.php
calendar/classes/local/event/factories/event_abstract_factory.php
calendar/classes/local/event/mappers/event_mapper.php
calendar/classes/local/event/proxies/coursecat_proxy.php [new file with mode: 0644]
calendar/classes/local/event/strategies/raw_event_retrieval_strategy.php
calendar/classes/local/event/strategies/raw_event_retrieval_strategy_interface.php
calendar/lib.php
calendar/tests/action_event_test.php
calendar/tests/container_test.php
calendar/tests/coursecat_proxy_test.php [new file with mode: 0644]
calendar/tests/event_factory_test.php
calendar/tests/event_mapper_test.php
calendar/tests/event_test.php
calendar/tests/helpers.php
calendar/tests/raw_event_retrieval_strategy_test.php
calendar/tests/repeat_event_collection_test.php
lib/coursecatlib.php

index 2642423..d687eb1 100644 (file)
@@ -50,6 +50,11 @@ class event_exporter extends event_exporter_base {
     protected static function define_other_properties() {
 
         $values = parent::define_other_properties();
+
+        $values['isactionevent'] = ['type' => PARAM_BOOL];
+        $values['iscourseevent'] = ['type' => PARAM_BOOL];
+        $values['iscategoryevent'] = ['type' => PARAM_BOOL];
+        $values['candelete'] = ['type' => PARAM_BOOL];
         $values['url'] = ['type' => PARAM_URL];
         $values['action'] = [
             'type' => event_action_exporter::read_properties_definition(),
@@ -77,6 +82,9 @@ class event_exporter extends event_exporter_base {
 
         $event = $this->event;
         $context = $this->related['context'];
+        $values['isactionevent'] = false;
+        $values['iscourseevent'] = false;
+        $values['iscategoryevent'] = false;
         if ($moduleproxy = $event->get_course_module()) {
             $modulename = $moduleproxy->get('modname');
             $moduleid = $moduleproxy->get('id');
@@ -86,6 +94,9 @@ class event_exporter extends event_exporter_base {
             $params = array('update' => $moduleid, 'return' => true, 'sesskey' => sesskey());
             $editurl = new \moodle_url('/course/mod.php', $params);
             $values['editurl'] = $editurl->out(false);
+        } else if ($event->get_type() == 'category') {
+            $values['iscategoryevent'] = true;
+            $url = $event->get_category()->get_proxied_instance()->get_view_link();
         } else if ($event->get_type() == 'course') {
             $url = \course_get_url($this->related['course'] ?: SITEID);
         } else {
index 0ba6d54..00713f4 100644 (file)
@@ -71,6 +71,7 @@ class api {
         array $usersfilter = null,
         array $groupsfilter = null,
         array $coursesfilter = null,
+        array $categoriesfilter = null,
         $withduration = true,
         $ignorehidden = true,
         callable $filter = null
@@ -102,6 +103,7 @@ class api {
             $usersfilter,
             $groupsfilter,
             $coursesfilter,
+            $categoriesfilter,
             $withduration,
             $ignorehidden,
             $filter
index 70c2cb7..398304e 100644 (file)
@@ -117,6 +117,15 @@ class container {
                 [self::class, 'apply_component_provide_event_action'],
                 [self::class, 'apply_component_is_event_visible'],
                 function ($dbrow) {
+                    if (!empty($dbrow->categoryid)) {
+                        // This is a category event. Check that the category is visible to this user.
+                        $category = \coursecat::get($dbrow->categoryid, IGNORE_MISSING, true);
+
+                        if (empty($category) || !$category->is_uservisible()) {
+                            return true;
+                        }
+                    }
+
                     // At present we only have a bail-out check for events in course modules.
                     if (empty($dbrow->modulename)) {
                         return false;
index c6c7c68..9111f39 100644 (file)
@@ -33,6 +33,8 @@ use core_calendar\local\event\factories\action_factory_interface;
 use core_calendar\local\event\factories\event_factory_interface;
 use core_calendar\local\event\strategies\raw_event_retrieval_strategy_interface;
 
+require_once($CFG->libdir . '/coursecatlib.php');
+
 /**
  * Event vault class.
  *
@@ -95,6 +97,7 @@ class event_vault implements event_vault_interface {
         array $usersfilter = null,
         array $groupsfilter = null,
         array $coursesfilter = null,
+        array $categoriesfilter = null,
         $withduration = true,
         $ignorehidden = true,
         callable $filter = null
@@ -162,6 +165,7 @@ class event_vault implements event_vault_interface {
             $usersfilter,
             $groupsfilter,
             $coursesfilter,
+            $categoriesfilter,
             $where,
             $params,
             "COALESCE(e.timesort, e.timestart) ASC, e.id ASC",
@@ -197,6 +201,10 @@ class event_vault implements event_vault_interface {
         event_interface $afterevent = null,
         $limitnum = 20
     ) {
+        $categoryids = array_map(function($category) {
+            return $category->id;
+        }, \coursecat::get_all());
+
         $courseids = array_map(function($course) {
             return $course->id;
         }, enrol_get_all_users_courses($user->id));
@@ -219,6 +227,7 @@ class event_vault implements event_vault_interface {
             [$user->id],
             $groupids ? $groupids : null,
             $courseids ? $courseids : null,
+            $categoryids ? $categoryids : null,
             true,
             true,
             function ($event) {
@@ -249,6 +258,7 @@ class event_vault implements event_vault_interface {
                 [$user->id],
                 $groupings[0] ? $groupings[0] : null,
                 [$course->id],
+                [],
                 true,
                 true,
                 function ($event) use ($course) {
@@ -375,6 +385,7 @@ class event_vault implements event_vault_interface {
                 [$userid],
                 null,
                 null,
+                null,
                 $whereconditions,
                 $whereparams,
                 $ordersql,
index 705b832..15c5a0c 100644 (file)
@@ -76,6 +76,7 @@ interface event_vault_interface {
         array $usersfilter = null,
         array $groupsfilter = null,
         array $coursesfilter = null,
+        array $categoriesfilter = null,
         $withduration = true,
         $ignorehidden = true,
         callable $filter = null
index 1c76999..ad1d98c 100644 (file)
@@ -50,6 +50,11 @@ class action_event implements action_event_interface {
      */
     protected $action;
 
+    /**
+     * @var proxy_interface $category Category for this event.
+     */
+    protected $category;
+
     /**
      * Constructor.
      *
@@ -73,6 +78,10 @@ class action_event implements action_event_interface {
         return $this->event->get_description();
     }
 
+    public function get_category() {
+        return $this->event->get_category();
+    }
+
     public function get_course() {
         return $this->event->get_course();
     }
index cd5a1df..f68544b 100644 (file)
@@ -52,6 +52,11 @@ class event implements event_interface {
      */
     protected $description;
 
+    /**
+     * @var proxy_interface $category Category for this event.
+     */
+    protected $category;
+
     /**
      * @var proxy_interface $course Course for this event.
      */
@@ -103,6 +108,7 @@ class event implements event_interface {
      * @param int                        $id             The event's ID in the database.
      * @param string                     $name           The event's name.
      * @param description_interface      $description    The event's description.
+     * @param proxy_interface            $category       The category associated with the event.
      * @param proxy_interface            $course         The course associated with the event.
      * @param proxy_interface            $group          The group associated with the event.
      * @param proxy_interface            $user           The user associated with the event.
@@ -117,6 +123,7 @@ class event implements event_interface {
         $id,
         $name,
         description_interface $description,
+        proxy_interface $category = null,
         proxy_interface $course = null,
         proxy_interface $group = null,
         proxy_interface $user = null,
@@ -130,6 +137,7 @@ class event implements event_interface {
         $this->id = $id;
         $this->name = $name;
         $this->description = $description;
+        $this->category = $category;
         $this->course = $course;
         $this->group = $group;
         $this->user = $user;
@@ -153,6 +161,10 @@ class event implements event_interface {
         return $this->description;
     }
 
+    public function get_category() {
+        return $this->category;
+    }
+
     public function get_course() {
         return $this->course;
     }
index 192e75d..400cf78 100644 (file)
@@ -56,6 +56,13 @@ interface event_interface {
      */
     public function get_description();
 
+    /**
+     * Get the category object associated with the event.
+     *
+     * @return proxy_interface
+     */
+    public function get_category();
+
     /**
      * Get the course object associated with the event.
      *
index 2695d93..b0242ae 100644 (file)
@@ -30,11 +30,14 @@ use core_calendar\local\event\entities\event;
 use core_calendar\local\event\entities\repeat_event_collection;
 use core_calendar\local\event\exceptions\invalid_callback_exception;
 use core_calendar\local\event\proxies\cm_info_proxy;
+use core_calendar\local\event\proxies\coursecat_proxy;
 use core_calendar\local\event\proxies\std_proxy;
 use core_calendar\local\event\value_objects\event_description;
 use core_calendar\local\event\value_objects\event_times;
 use core_calendar\local\event\entities\event_interface;
 
+require_once($CFG->libdir . '/coursecatlib.php');
+
 /**
  * Abstract factory for creating calendar events.
  *
@@ -126,6 +129,7 @@ abstract class event_abstract_factory implements event_factory_interface {
             return null;
         }
 
+        $category = null;
         $course = null;
         $group = null;
         $user = null;
@@ -136,6 +140,8 @@ abstract class event_abstract_factory implements event_factory_interface {
             $module = new cm_info_proxy($dbrow->modulename, $dbrow->instance, $dbrow->courseid);
         }
 
+        $category = new coursecat_proxy($dbrow->categoryid);
+
         $course = new std_proxy($dbrow->courseid, function($id) {
             return calendar_get_course_cached($this->coursecachereference, $id);
         });
@@ -163,6 +169,7 @@ abstract class event_abstract_factory implements event_factory_interface {
             $dbrow->id,
             $dbrow->name,
             new event_description($dbrow->description, $dbrow->format),
+            $category,
             $course,
             $group,
             $user,
index 1561e02..2e33869 100644 (file)
@@ -69,6 +69,7 @@ class event_mapper implements event_mapper_interface {
                 'name' => $coalesce('name'),
                 'description' => $coalesce('description'),
                 'format' => $coalesce('format'),
+                'categoryid' => $coalesce('categoryid'),
                 'courseid' => $coalesce('courseid'),
                 'groupid' => $coalesce('groupid'),
                 'userid' => $coalesce('userid'),
diff --git a/calendar/classes/local/event/proxies/coursecat_proxy.php b/calendar/classes/local/event/proxies/coursecat_proxy.php
new file mode 100644 (file)
index 0000000..c585f3a
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Course category proxy.
+ *
+ * @package    core_calendar
+ * @copyright  2017 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_calendar\local\event\proxies;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/coursecatlib.php');
+
+/**
+ * Course category proxy.
+ *
+ * This returns an instance of a coursecat rather than a stdClass.
+ *
+ * @copyright  2017 Andrew Nicols <andrew@nicols.co.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class coursecat_proxy implements proxy_interface {
+    /**
+     * @var int $id The ID of the database record.
+     */
+    protected $id;
+
+    /**
+     * @var \stdClass $base Base class to get members from.
+     */
+    protected $base;
+
+    /**
+     * @var \coursecat $category The proxied instance.
+     */
+    protected $category;
+
+    /**
+     * coursecat_proxy constructor.
+     *
+     * @param int       $id       The ID of the record in the database.
+     */
+    public function __construct($id) {
+        $this->id = $id;
+        $this->base = (object) [
+            'id' => $id,
+        ];
+    }
+
+    /**
+     * Retrieve a member of the proxied class.
+     *
+     * @param string $member The name of the member to retrieve
+     * @return mixed The member.
+     */
+    public function get($member) {
+        if ($this->base && property_exists($this->base, $member)) {
+            return $this->base->{$member};
+        }
+
+        return $this->get_proxied_instance()->{$member};
+    }
+
+    /**
+     * Get the full instance of the proxied class.
+     *
+     * @return \coursecat
+     */
+    public function get_proxied_instance() : \coursecat {
+        if (!$this->category) {
+            $this->category = \coursecat::get($this->id, IGNORE_MISSING, true);
+        }
+        return $this->category;
+    }
+}
index 252d374..d4775ee 100644 (file)
@@ -40,6 +40,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
         array $usersfilter = null,
         array $groupsfilter = null,
         array $coursesfilter = null,
+        array $categoriesfilter = null,
         array $whereconditions = null,
         array $whereparams = null,
         $ordersql = null,
@@ -51,6 +52,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
             !is_null($usersfilter) ? $usersfilter : true, // True means no filter in old implementation.
             !is_null($groupsfilter) ? $groupsfilter : true,
             !is_null($coursesfilter) ? $coursesfilter : true,
+            !is_null($categoriesfilter) ? $categoriesfilter : true,
             $whereconditions,
             $whereparams,
             $ordersql,
@@ -78,6 +80,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
         $users,
         $groups,
         $courses,
+        $categories,
         $whereconditions,
         $whereparams,
         $ordersql,
@@ -89,7 +92,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
 
         $params = array();
         // Quick test.
-        if (empty($users) && empty($groups) && empty($courses)) {
+        if (empty($users) && empty($groups) && empty($courses) && empty($categories)) {
             return array();
         }
 
@@ -100,11 +103,11 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
         if ((is_array($users) && !empty($users)) or is_numeric($users)) {
             // Events from a number of users.
             list($insqlusers, $inparamsusers) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED);
-            $filters[] = "(e.userid $insqlusers AND e.courseid = 0 AND e.groupid = 0)";
+            $filters[] = "(e.userid $insqlusers AND e.courseid = 0 AND e.groupid = 0 AND e.categoryid = 0)";
             $params = array_merge($params, $inparamsusers);
         } else if ($users === true) {
             // Events from ALL users.
-            $filters[] = "(e.userid != 0 AND e.courseid = 0 AND e.groupid = 0)";
+            $filters[] = "(e.userid != 0 AND e.courseid = 0 AND e.groupid = 0 AND e.categoryid = 0)";
         }
         // Boolean false (no users at all): We don't need to do anything.
 
@@ -130,6 +133,16 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
             $filters[] = "(e.groupid = 0 AND e.courseid != 0)";
         }
 
+        // Category filter.
+        if ((is_array($categories) && !empty($categories)) or is_numeric($categories)) {
+            list($insqlcategories, $inparamscategories) = $DB->get_in_or_equal($categories, SQL_PARAMS_NAMED);
+            $filters[] = "(e.groupid = 0 AND e.courseid = 0 AND e.categoryid $insqlcategories)";
+            $params = array_merge($params, $inparamscategories);
+        } else if ($categories === true) {
+            // Events from ALL categories.
+            $filters[] = "(e.groupid = 0 AND e.courseid = 0 AND e.categoryid != 0)";
+        }
+
         // Security check: if, by now, we have NOTHING in $whereclause, then it means
         // that NO event-selecting clauses were defined. Thus, we won't be returning ANY
         // events no matter what. Allowing the code to proceed might return a completely
@@ -168,7 +181,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
 
         if ($user) {
             // Set filter condition for the user's events.
-            $subqueryconditions[] = "(ev.userid = :user AND ev.courseid = 0 AND ev.groupid = 0)";
+            $subqueryconditions[] = "(ev.userid = :user AND ev.courseid = 0 AND ev.groupid = 0 AND ev.categoryid = 0)";
             $subqueryparams['user'] = $user;
 
             foreach ($usercourses as $courseid) {
@@ -210,10 +223,19 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
         // Set subquery filter condition for the courses.
         if (!empty($subquerycourses)) {
             list($incourses, $incoursesparams) = $DB->get_in_or_equal($subquerycourses, SQL_PARAMS_NAMED);
-            $subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid $incourses)";
+            $subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid $incourses AND ev.categoryid = 0)";
             $subqueryparams = array_merge($subqueryparams, $incoursesparams);
         }
 
+        // Set subquery filter condition for the categories.
+        if ($categories === true) {
+            $subqueryconditions[] = "(ev.categoryid != 0 AND ev.eventtype = 'category')";
+        } else if (!empty($categories)) {
+            list($incategories, $incategoriesparams) = $DB->get_in_or_equal($categories, SQL_PARAMS_NAMED);
+            $subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid = 0 AND ev.categoryid $incategories)";
+            $subqueryparams = array_merge($subqueryparams, $incategoriesparams);
+        }
+
         // Build the WHERE condition for the sub-query.
         if (!empty($subqueryconditions)) {
             $subquerywhere = 'WHERE ' . implode(" OR ", $subqueryconditions);
index cc670d3..9225a39 100644 (file)
@@ -39,6 +39,7 @@ interface raw_event_retrieval_strategy_interface {
      * @param array|null    $usersfilter     Array of users to retrieve events for.
      * @param array|null    $groupsfilter    Array of groups to retrieve events for.
      * @param array|null    $coursesfilter   Array of courses to retrieve events for.
+     * @param array|null    $categoriesfilter Array of categories to retrieve events for.
      * @param array|null    $whereconditions Array of where conditions to restrict results.
      * @param array|null    $whereparams     Array of parameters for $whereconditions.
      * @param string|null   $ordersql        SQL to order results.
@@ -51,6 +52,7 @@ interface raw_event_retrieval_strategy_interface {
         array $usersfilter = null,
         array $groupsfilter = null,
         array $coursesfilter = null,
+        array $categoriesfilter = null,
         array $whereconditions = null,
         array $whereparams = null,
         $ordersql = null,
index f0c8dd1..6b46115 100644 (file)
@@ -3055,11 +3055,12 @@ function core_calendar_user_preferences() {
  * @param boolean $ignorehidden whether to select only visible events or all events
  * @return array $events of selected events or an empty array if there aren't any (or there was an error)
  */
-function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses, $withduration = true, $ignorehidden = true) {
+function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses,
+        $withduration = true, $ignorehidden = true, $categories = []) {
     // Normalise the users, groups and courses parameters so that they are compliant with \core_calendar\local\api::get_events().
     // Existing functions that were using the old calendar_get_events() were passing a mixture of array, int, boolean for these
     // parameters, but with the new API method, only null and arrays are accepted.
-    list($userparam, $groupparam, $courseparam) = array_map(function($param) {
+    list($userparam, $groupparam, $courseparam, $categoryparam) = array_map(function($param) {
         // If parameter is true, return null.
         if ($param === true) {
             return null;
@@ -3077,7 +3078,7 @@ function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses, $
 
         // No normalisation required.
         return $param;
-    }, [$users, $groups, $courses]);
+    }, [$users, $groups, $courses, $categories]);
 
     $mapper = \core_calendar\local\event\container::get_event_mapper();
     $events = \core_calendar\local\api::get_events(
@@ -3092,6 +3093,7 @@ function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses, $
         $userparam,
         $groupparam,
         $courseparam,
+        $categoryparam,
         $withduration,
         $ignorehidden
     );
@@ -3142,7 +3144,7 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
         }
     }
 
-    list($userparam, $groupparam, $courseparam) = array_map(function($param) {
+    list($userparam, $groupparam, $courseparam, $categoryparam) = array_map(function($param) {
         // If parameter is true, return null.
         if ($param === true) {
             return null;
@@ -3160,7 +3162,7 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
 
         // No normalisation required.
         return $param;
-    }, [$calendar->users, $calendar->groups, $calendar->courses]);
+    }, [$calendar->users, $calendar->groups, $calendar->courses, $calendar->categories]);
 
     $events = \core_calendar\local\api::get_events(
         $tstart,
@@ -3174,6 +3176,7 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
         $userparam,
         $groupparam,
         $courseparam,
+        $categoryparam,
         true,
         true,
         function ($event) {
index 70bbf90..acb8643 100644 (file)
@@ -108,6 +108,10 @@ class core_calendar_action_event_test_event implements event_interface {
         return new event_description('asdf', 1);
     }
 
+    public function get_category() {
+        return new \stdClass();
+    }
+
     public function get_course() {
         return new \stdClass();
     }
index e21692e..3298187 100644 (file)
@@ -240,6 +240,84 @@ class core_calendar_container_testcase extends advanced_testcase {
         $this->assertNull($event);
     }
 
+    /**
+     * Test that the event factory deals with invisible categorys as an admin.
+     */
+    public function test_event_factory_when_category_visibility_is_toggled_as_admin() {
+        // Create a hidden category.
+        $category = $this->getDataGenerator()->create_category(['visible' => 0]);
+
+        $eventdata = [
+                'categoryid' => $category->id,
+                'eventtype' => 'category',
+            ];
+        $legacyevent = $this->create_event($eventdata);
+
+        $dbrow = $this->get_dbrow_from_skeleton((object) $eventdata);
+        $dbrow->id = $legacyevent->id;
+
+        $factory = \core_calendar\local\event\container::get_event_factory();
+        $event = $factory->create_instance($dbrow);
+
+        // Module is still visible to admins even if the category is invisible.
+        $this->assertInstanceOf(event_interface::class, $event);
+    }
+
+    /**
+     * Test that the event factory deals with invisible categorys as an user.
+     */
+    public function test_event_factory_when_category_visibility_is_toggled_as_user() {
+        // Create a hidden category.
+        $category = $this->getDataGenerator()->create_category(['visible' => 0]);
+
+        $eventdata = [
+                'categoryid' => $category->id,
+                'eventtype' => 'category',
+            ];
+        $legacyevent = $this->create_event($eventdata);
+
+        $dbrow = $this->get_dbrow_from_skeleton((object) $eventdata);
+        $dbrow->id = $legacyevent->id;
+
+        // Use a standard user.
+        $user = $this->getDataGenerator()->create_user();
+
+        // Set the user to the student.
+        $this->setUser($user);
+
+        $factory = \core_calendar\local\event\container::get_event_factory();
+        $event = $factory->create_instance($dbrow);
+
+        // Module is invisible to non-privileged users.
+        $this->assertNull($event);
+    }
+
+    /**
+     * Test that the event factory deals with invisible categorys as an guest.
+     */
+    public function test_event_factory_when_category_visibility_is_toggled_as_guest() {
+        // Create a hidden category.
+        $category = $this->getDataGenerator()->create_category(['visible' => 0]);
+
+        $eventdata = [
+                'categoryid' => $category->id,
+                'eventtype' => 'category',
+            ];
+        $legacyevent = $this->create_event($eventdata);
+
+        $dbrow = $this->get_dbrow_from_skeleton((object) $eventdata);
+        $dbrow->id = $legacyevent->id;
+
+        // Set the user to the student.
+        $this->setGuestUser();
+
+        $factory = \core_calendar\local\event\container::get_event_factory();
+        $event = $factory->create_instance($dbrow);
+
+        // Module is invisible to guests.
+        $this->assertNull($event);
+    }
+
     /**
      * Test that the event factory deals with completion related events properly.
      */
@@ -264,6 +342,7 @@ class core_calendar_container_testcase extends advanced_testcase {
         $event->userid = 1;
         $event->modulename = 'assign';
         $event->instance = $assign->id;
+        $event->categoryid = 0;
         $event->courseid = $course->id;
         $event->groupid = 0;
         $event->timestart = time();
@@ -312,6 +391,7 @@ class core_calendar_container_testcase extends advanced_testcase {
         $event->userid = $user->id;
         $event->modulename = 'lesson';
         $event->instance = $lesson->id;
+        $event->categoryid = 0;
         $event->courseid = $course->id;
         $event->groupid = 0;
         $event->timestart = time();
@@ -397,6 +477,7 @@ class core_calendar_container_testcase extends advanced_testcase {
                     'name' => 'Test event',
                     'description' => 'Hello',
                     'format' => 1,
+                    'categoryid' => 0,
                     'courseid' => 1,
                     'groupid' => 0,
                     'userid' => 1,
@@ -418,6 +499,7 @@ class core_calendar_container_testcase extends advanced_testcase {
                     'name' => 'Test event',
                     'description' => 'Hello',
                     'format' => 1,
+                    'categoryid' => 0,
                     'courseid' => 1,
                     'groupid' => 1,
                     'userid' => 1,
@@ -459,4 +541,38 @@ class core_calendar_container_testcase extends advanced_testcase {
         $event = new calendar_event($record);
         return $event->create($record, false);
     }
+
+    /**
+     * Pad out a basic DB row with basic information.
+     *
+     * @param   \stdClass   $skeleton the current skeleton
+     * @return  \stdClass
+     */
+    protected function get_dbrow_from_skeleton($skeleton) {
+        $dbrow = (object) [
+            'name' => 'Name',
+            'description' => 'Description',
+            'format' => 1,
+            'categoryid' => 0,
+            'courseid' => 0,
+            'groupid' => 0,
+            'userid' => 0,
+            'repeatid' => 0,
+            'modulename' => '',
+            'instance' => 0,
+            'eventtype' => 'user',
+            'timestart' => 1486396800,
+            'timeduration' => 0,
+            'timesort' => 1486396800,
+            'visible' => 1,
+            'timemodified' => 1485793098,
+            'subscriptionid' => null
+        ];
+
+        foreach ((array) $skeleton as $key => $value) {
+            $dbrow->$key = $value;
+        }
+
+        return $dbrow;
+    }
 }
diff --git a/calendar/tests/coursecat_proxy_test.php b/calendar/tests/coursecat_proxy_test.php
new file mode 100644 (file)
index 0000000..c00fe47
--- /dev/null
@@ -0,0 +1,64 @@
+<?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/>.
+
+/**
+ * coursecat_proxy tests.
+ *
+ * @package     core_calendar
+ * @copyright   2017 Andrew Nicols <andrew@nicols.co.uk>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_calendar\local\event\proxies\coursecat_proxy;
+
+/**
+ * coursecat_proxy testcase.
+ *
+ * @copyright   2017 Andrew Nicols <andrew@nicols.co.uk>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_calendar_coursecat_proxy_testcase extends advanced_testcase {
+
+    public function test_valid_coursecat() {
+        global $DB;
+        $this->resetAfterTest();
+
+        $name = '2027-2028 Academic Year';
+        $generator = $this->getDataGenerator();
+        $category = $generator->create_category([
+                'name' => $name,
+            ]);
+        cache_helper::purge_by_event('changesincoursecat');
+
+        // Fetch the proxy.
+        $startreads = $DB->perf_get_reads();
+        $proxy = new coursecat_proxy($category->id);
+        $this->assertInstanceOf(coursecat_proxy::class, $proxy);
+        $this->assertEquals(0, $DB->perf_get_reads() - $startreads);
+
+        // Fetch the ID - this is known and doesn't require a cache read.
+        $this->assertEquals($category->id, $proxy->get('id'));
+        $this->assertEquals(0, $DB->perf_get_reads() - $startreads);
+
+        // Fetch the name - not known, and requires a read.
+        $this->assertEquals($name, $proxy->get('name'));
+        $this->assertEquals(1, $DB->perf_get_reads() - $startreads);
+
+        $this->assertInstanceOf('coursecat', $proxy->get_proxied_instance());
+    }
+}
index 2cd22ea..9375126 100644 (file)
@@ -114,6 +114,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
                 'name' => 'test',
                 'description' => 'Test description',
                 'format' => 2,
+                'categoryid' => 0,
                 'courseid' => 1,
                 'groupid' => 1,
                 'userid' => 1,
@@ -162,6 +163,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
                 'name' => 'test',
                 'description' => 'Test description',
                 'format' => 2,
+                'categoryid' => 0,
                 'courseid' => 1,
                 'groupid' => 1,
                 'userid' => 1,
@@ -210,6 +212,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
                 'name' => 'test',
                 'description' => 'Test description',
                 'format' => 2,
+                'categoryid' => 0,
                 'courseid' => 1,
                 'groupid' => 1,
                 'userid' => 1,
@@ -258,6 +261,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
                 'name' => 'test',
                 'description' => 'Test description',
                 'format' => 2,
+                'categoryid' => 0,
                 'courseid' => $course->id,
                 'groupid' => 1,
                 'userid' => 1,
@@ -312,6 +316,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
                 'name' => 'test',
                 'description' => 'Test description',
                 'format' => 2,
+                'categoryid' => 0,
                 'courseid' => 0,
                 'groupid' => 1,
                 'userid' => 1,
@@ -345,6 +350,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
                     'name' => 'Test event',
                     'description' => 'Hello',
                     'format' => 1,
+                    'categoryid' => 0,
                     'courseid' => 1,
                     'groupid' => 1,
                     'userid' => 1,
@@ -378,6 +384,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
                     'name' => 'Test event',
                     'description' => 'Hello',
                     'format' => 1,
+                    'categoryid' => 0,
                     'courseid' => 1,
                     'groupid' => 1,
                     'userid' => 1,
@@ -411,6 +418,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
                     'name' => 'Test event',
                     'description' => 'Hello',
                     'format' => 1,
+                    'categoryid' => 0,
                     'courseid' => 1,
                     'groupid' => 1,
                     'userid' => 1,
index 0071a50..8294651 100644 (file)
@@ -202,6 +202,10 @@ class event_mapper_test_action_event implements action_event_interface {
         return $this->event->get_description();
     }
 
+    public function get_category() {
+        return $this->event->get_category();
+    }
+
     public function get_course() {
         return $this->event->get_course();
     }
@@ -255,6 +259,11 @@ class event_mapper_test_action_event implements action_event_interface {
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class event_mapper_test_event implements event_interface {
+    /**
+     * @var proxy_interface $categoryproxy Category proxy.
+     */
+    protected $categoryproxy;
+
     /**
      * @var proxy_interface $courseproxy Course proxy.
      */
@@ -312,6 +321,10 @@ class event_mapper_test_event implements event_interface {
         return new event_description('asdf', 1);
     }
 
+    public function get_category() {
+        return $this->categoryproxy;
+    }
+
     public function get_course() {
         return $this->courseproxy;
     }
index b2e5d9b..8b28046 100644 (file)
@@ -26,6 +26,7 @@ defined('MOODLE_INTERNAL') || die();
 
 use core_calendar\local\event\entities\event;
 use core_calendar\local\event\proxies\std_proxy;
+use core_calendar\local\event\proxies\coursecat_proxy;
 use core_calendar\local\event\value_objects\event_description;
 use core_calendar\local\event\value_objects\event_times;
 use core_calendar\local\event\entities\event_collection_interface;
@@ -48,6 +49,7 @@ class core_calendar_event_testcase extends advanced_testcase {
             $constructorparams['id'],
             $constructorparams['name'],
             $constructorparams['description'],
+            $constructorparams['category'],
             $constructorparams['course'],
             $constructorparams['group'],
             $constructorparams['user'],
@@ -82,6 +84,7 @@ class core_calendar_event_testcase extends advanced_testcase {
                     'id' => 1,
                     'name' => 'Test event 1',
                     'description' => new event_description('asdf', 1),
+                    'category' => new coursecat_proxy(0),
                     'course' => new std_proxy(1, $lamecallable),
                     'group' => new std_proxy(1, $lamecallable),
                     'user' => new std_proxy(1, $lamecallable),
index f8e08fb..2c3b461 100644 (file)
@@ -32,6 +32,7 @@ use core_calendar\local\event\entities\action_event;
 use core_calendar\local\event\entities\event;
 use core_calendar\local\event\entities\repeat_event_collection;
 use core_calendar\local\event\proxies\std_proxy;
+use core_calendar\local\event\proxies\coursecat_proxy;
 use core_calendar\local\event\proxies\cm_info_proxy;
 use core_calendar\local\event\value_objects\action;
 use core_calendar\local\event\value_objects\event_description;
@@ -108,6 +109,7 @@ class action_event_test_factory implements event_factory_interface {
             $record->id,
             $record->name,
             new event_description($record->description, $record->format),
+            new coursecat_proxy($record->categoryid),
             new std_proxy($record->courseid, function($id) {
                 $course = new \stdClass();
                 $course->id = $id;
index 82d7794..9d8b137 100644 (file)
@@ -277,4 +277,69 @@ class core_calendar_raw_event_retrieval_strategy_testcase extends advanced_testc
         $events = $retrievalstrategy->get_raw_events();
         $this->assertCount(3, $events);
     }
+
+    /**
+     * Test retrieval strategy with category specifications.
+     */
+    public function test_get_raw_events_category() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $retrievalstrategy = new raw_event_retrieval_strategy();
+        $generator = $this->getDataGenerator();
+        $category1 = $generator->create_category();
+        $category2 = $generator->create_category();
+        $events = [
+            [
+                'name' => 'E1',
+                'eventtype' => 'category',
+                'description' => '',
+                'format' => 1,
+                'categoryid' => $category1->id,
+                'userid' => 2,
+                'timestart' => time(),
+            ],
+            [
+                'name' => 'E2',
+                'eventtype' => 'category',
+                'description' => '',
+                'format' => 1,
+                'categoryid' => $category2->id,
+                'userid' => 2,
+                'timestart' => time() + 1,
+            ],
+        ];
+
+        foreach ($events as $event) {
+            calendar_event::create($event, false);
+        }
+
+        // Get all events.
+        $events = $retrievalstrategy->get_raw_events(null, null, null, null);
+        $this->assertCount(2, $events);
+
+        $event = array_shift($events);
+        $this->assertEquals('E1', $event->name);
+        $event = array_shift($events);
+        $this->assertEquals('E2', $event->name);
+
+        // Get events for C1 events.
+        $events = $retrievalstrategy->get_raw_events(null, null, null, [$category1->id]);
+        $this->assertCount(1, $events);
+
+        $event = array_shift($events);
+        $this->assertEquals('E1', $event->name);
+
+        // Get events for C2 events.
+        $events = $retrievalstrategy->get_raw_events(null, null, null, [$category2->id]);
+        $this->assertCount(1, $events);
+
+        $event = array_shift($events);
+        $this->assertEquals('E2', $event->name);
+
+        // Get events for several categories.
+        $events = $retrievalstrategy->get_raw_events(null, null, null, [$category1->id, $category2->id]);
+        $this->assertCount(2, $events);
+    }
 }
+
index f59fdd9..ec6320c 100644 (file)
@@ -30,6 +30,7 @@ require_once($CFG->dirroot . '/calendar/lib.php');
 
 use core_calendar\local\event\entities\event;
 use core_calendar\local\event\entities\repeat_event_collection;
+use core_calendar\local\event\proxies\coursecat_proxy;
 use core_calendar\local\event\proxies\std_proxy;
 use core_calendar\local\event\value_objects\event_description;
 use core_calendar\local\event\value_objects\event_times;
@@ -161,6 +162,7 @@ class core_calendar_repeat_event_collection_event_test_factory implements event_
             $dbrow->id,
             $dbrow->name,
             new event_description($dbrow->description, $dbrow->format),
+            new coursecat_proxy($dbrow->categoryid),
             new std_proxy($dbrow->courseid, $identity),
             new std_proxy($dbrow->groupid, $identity),
             new std_proxy($dbrow->userid, $identity),
index d4b56c2..e5af7cb 100644 (file)
@@ -1305,6 +1305,14 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
                 array($this->id));
     }
 
+    /**
+     */
+    public function get_view_link() {
+        return new \moodle_url('/course/index.php', [
+            'categoryid' => $this->id,
+        ]);
+    }
+
     /**
      * Searches courses
      *