MDL-57730 core_calendar: Allow modules to control event visibility

By implementing the mod_{modname}_core_calendar_is_event_visible callback
a module can decide whether or not a user should see an event.

Part of MDL-55611 epic.
This commit is contained in:
Cameron Ball 2017-02-16 16:13:07 +08:00 committed by Damyon Wiese
parent 5df117d438
commit 2a5cce61ba
5 changed files with 185 additions and 45 deletions

View file

@ -74,14 +74,13 @@ class core_container {
*/ */
private static function init() { private static function init() {
if (empty(self::$eventfactory)) { if (empty(self::$eventfactory)) {
$identity = function(event_interface $event) {
return $event;
};
self::$actionfactory = new action_factory(); self::$actionfactory = new action_factory();
self::$actioneventfactory = new action_event_factory(); self::$actioneventfactory = new action_event_factory();
self::$eventmapper = new event_mapper( self::$eventmapper = new event_mapper(
new event_factory( new event_factory($identity, $identity)
function(event_interface $event) {
return $event;
}
)
); );
self::$eventfactory = new event_factory( self::$eventfactory = new event_factory(
function(event_interface $event) { function(event_interface $event) {
@ -96,6 +95,23 @@ class core_container {
); );
return $action ? self::$actioneventfactory->create_instance($event, $action) : $event; return $action ? self::$actioneventfactory->create_instance($event, $action) : $event;
},
function(event_interface $event) {
$mapper = self::$eventmapper;
$eventvisible = component_callback(
'mod_' . $event->get_course_module()->get('modname'),
'core_calendar_is_event_visible',
[
$mapper->from_event_to_legacy_event($event)
]
);
// Module does not implement the callback, event should be visible.
if (is_null($eventvisible)) {
return true;
}
return $eventvisible ? true : false;
} }
); );
} }

View file

@ -43,9 +43,14 @@ use core_calendar\local\interfaces\event_interface;
*/ */
abstract class event_abstract_factory implements event_factory_interface { abstract class event_abstract_factory implements event_factory_interface {
/** /**
* @var callable $callbackapplier Function to apply component callbacks. * @var callable $actioncallbackapplier Function to apply component action callbacks.
*/ */
protected $callbackapplier; protected $actioncallbackapplier;
/**
* @var callable $visibilitycallbackapplier Function to apply component visibility callbacks.
*/
protected $visibilitycallbackapplier;
/** /**
* Applies component actions to the event. * Applies component actions to the event.
@ -55,13 +60,23 @@ abstract class event_abstract_factory implements event_factory_interface {
*/ */
protected abstract function apply_component_action(event_interface $event); protected abstract function apply_component_action(event_interface $event);
/**
* Exposes the event (or not)
*
* @param event_interface $event The event to potentially expose.
* @return event_interface|null The exposed event or null.
*/
protected abstract function expose_event(event_interface $event);
/** /**
* Constructor. * Constructor.
* *
* @param callable $callbackapplier Function to apply component callbacks. * @param callable $actioncallbackapplier Function to apply component action callbacks.
* @param callable $visibilitycallbackapplier Function to apply component visibility callbacks.
*/ */
public function __construct(callable $callbackapplier) { public function __construct(callable $actioncallbackapplier, callable $visibilitycallbackapplier) {
$this->callbackapplier = $callbackapplier; $this->actioncallbackapplier = $actioncallbackapplier;
$this->visibilitycallbackapplier = $visibilitycallbackapplier;
} }
public function create_instance(\stdClass $dbrow) { public function create_instance(\stdClass $dbrow) {
@ -108,25 +123,27 @@ abstract class event_abstract_factory implements event_factory_interface {
}); });
} }
return $this->apply_component_action( return $this->expose_event(
new event( $this->apply_component_action(
$dbrow->id, new event(
$dbrow->name, $dbrow->id,
new event_description($dbrow->description, $dbrow->format), $dbrow->name,
$course, new event_description($dbrow->description, $dbrow->format),
$group, $course,
$user, $group,
new repeat_event_collection($dbrow->id, $this), $user,
$module, new repeat_event_collection($dbrow->id, $this),
$dbrow->eventtype, $module,
new event_times( $dbrow->eventtype,
(new \DateTimeImmutable())->setTimestamp($dbrow->timestart), new event_times(
(new \DateTimeImmutable())->setTimestamp($dbrow->timestart + $dbrow->timeduration), (new \DateTimeImmutable())->setTimestamp($dbrow->timestart),
(new \DateTimeImmutable())->setTimestamp($dbrow->timesort ? $dbrow->timesort : $dbrow->timestart), (new \DateTimeImmutable())->setTimestamp($dbrow->timestart + $dbrow->timeduration),
(new \DateTimeImmutable())->setTimestamp($dbrow->timemodified) (new \DateTimeImmutable())->setTimestamp($dbrow->timesort ? $dbrow->timesort : $dbrow->timestart),
), (new \DateTimeImmutable())->setTimestamp($dbrow->timemodified)
!empty($dbrow->visible), ),
$subscription !empty($dbrow->visible),
$subscription
)
) )
); );
} }

View file

@ -38,17 +38,25 @@ use core_calendar\local\interfaces\event_interface;
*/ */
class event_factory extends event_abstract_factory { class event_factory extends event_abstract_factory {
protected function apply_component_action(event_interface $event) { protected function apply_component_action(event_interface $event) {
if (!$event->get_course_module()) { $callbackapplier = $this->actioncallbackapplier;
return $event;
}
$callbackapplier = $this->callbackapplier;
$callbackresult = $callbackapplier($event); $callbackresult = $callbackapplier($event);
if (!$callbackresult instanceof event_interface) { if (!$callbackresult instanceof event_interface) {
throw new invalid_callback_exception('Event factory callback applier must return an instance of action_interface'); throw new invalid_callback_exception(
'Event factory action callback applier must return an instance of event_interface');
} }
return $callbackresult; return $callbackresult;
} }
protected function expose_event(event_interface $event) {
$callbackapplier = $this->visibilitycallbackapplier;
$callbackresult = $callbackapplier($event);
if (!is_bool($callbackresult)) {
throw new invalid_callback_exception('Event factory visibility callback applier must return true or false');
}
return $callbackresult === true ? $event : null;
}
} }

View file

@ -62,6 +62,10 @@ class core_calendar_container_testcase extends advanced_testcase {
$dbrow->id = $legacyevent->id; $dbrow->id = $legacyevent->id;
$event = $factory->create_instance($dbrow); $event = $factory->create_instance($dbrow);
if (is_null($event)) {
return;
}
$this->assertInstanceOf(event_interface::class, $event); $this->assertInstanceOf(event_interface::class, $event);
$this->assertTrue($event instanceof event || $event instanceof action_event); $this->assertTrue($event instanceof event || $event instanceof action_event);

View file

@ -38,22 +38,38 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
* Test event class getters. * Test event class getters.
* *
* @dataProvider create_instance_testcases() * @dataProvider create_instance_testcases()
* @param array $instanceparams Associative array of instance parameters. * @param \stdClass $dbrow Row from the event table.
* @param callable $actioncallbackapplier Action callback applier.
* @param callable $visibilitycallbackapplier Visibility callback applier.
* @param string $expectedclass Class the factory is expected to produce.
* @param mixed $expectedattributevalue Expected value of the modified attribute.
*/ */
public function test_create_instance( public function test_create_instance(
$dbrow, $dbrow,
callable $callbackapplier, callable $actioncallbackapplier,
callable $visibilitycallbackapplier,
$expectedclass,
$expectedattributevalue $expectedattributevalue
) { ) {
$this->resetAfterTest(true); $this->resetAfterTest(true);
$this->setAdminUser(); $this->setAdminUser();
$event = $this->create_event(); $event = $this->create_event();
$factory = new event_factory($callbackapplier); $factory = new event_factory($actioncallbackapplier, $visibilitycallbackapplier);
$dbrow->id = $event->id; $dbrow->id = $event->id;
$instance = $factory->create_instance($dbrow); $instance = $factory->create_instance($dbrow);
$this->assertEquals($instance->testattribute, $expectedattributevalue); if ($expectedclass) {
$this->assertInstanceOf($expectedclass, $instance);
}
if (is_null($expectedclass)) {
$this->assertNull($instance);
}
if ($expectedattributevalue) {
$this->assertEquals($instance->testattribute, $expectedattributevalue);
}
} }
/** /**
@ -61,13 +77,59 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
* *
* @expectedException \core_calendar\local\event\exceptions\invalid_callback_exception * @expectedException \core_calendar\local\event\exceptions\invalid_callback_exception
*/ */
public function test_invalid_callback() { public function test_invalid_action_callback() {
$this->resetAfterTest(true); $this->resetAfterTest(true);
$this->setAdminUser(); $this->setAdminUser();
$event = $this->create_event(); $event = $this->create_event();
$factory = new event_factory(function () { $factory = new event_factory(
return 'hello'; function () {
}); return 'hello';
},
function () {
return true;
}
);
$factory->create_instance(
(object)[
'id' => $event->id,
'name' => 'test',
'description' => 'Test description',
'format' => 2,
'courseid' => 1,
'groupid' => 1,
'userid' => 1,
'repeatid' => 1,
'modulename' => 'assign',
'instance' => 1,
'eventtype' => 'due',
'timestart' => 123456789,
'timeduration' => 12,
'timemodified' => 123456789,
'timesort' => 123456789,
'visible' => 1,
'subscriptionid' => 1
]
);
}
/**
* Test invalid callback exception.
*
* @expectedException \core_calendar\local\event\exceptions\invalid_callback_exception
*/
public function test_invalid_visibility_callback() {
$this->resetAfterTest(true);
$this->setAdminUser();
$event = $this->create_event();
$factory = new event_factory(
function ($event) {
return $event;
},
function () {
return 'asdf';
}
);
$factory->create_instance( $factory->create_instance(
(object)[ (object)[
@ -94,7 +156,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
public function create_instance_testcases() { public function create_instance_testcases() {
return [ return [
'Sample event record' => [ 'Sample event record with event exposed' => [
'dbrow' => (object)[ 'dbrow' => (object)[
'name' => 'Test event', 'name' => 'Test event',
'description' => 'Hello', 'description' => 'Hello',
@ -113,11 +175,44 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
'visible' => true, 'visible' => true,
'subscriptionid' => 1 'subscriptionid' => 1
], ],
'callbackapplier' => function(event_interface $event) { 'actioncallbackapplier' => function(event_interface $event) {
$event->testattribute = 'Hello'; $event->testattribute = 'Hello';
return $event; return $event;
}, },
'visibilitycallbackapplier' => function(event_interface $event) {
return true;
},
event_interface::class,
'Hello' 'Hello'
],
'Sample event record with event hidden' => [
'dbrow' => (object)[
'name' => 'Test event',
'description' => 'Hello',
'format' => 1,
'courseid' => 1,
'groupid' => 1,
'userid' => 1,
'repeatid' => null,
'modulename' => 'Test module',
'instance' => 1,
'eventtype' => 'Due',
'timestart' => 123456789,
'timeduration' => 123456789,
'timemodified' => 123456789,
'timesort' => 123456789,
'visible' => true,
'subscriptionid' => 1
],
'actioncallbackapplier' => function(event_interface $event) {
$event->testattribute = 'Hello';
return $event;
},
'visibilitycallbackapplier' => function(event_interface $event) {
return false;
},
null,
null
] ]
]; ];
} }