From 162f90e59ef2126d7e68aa38df472ad07a4d1743 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 19 Jul 2017 09:26:15 +0800 Subject: [PATCH 01/17] MDL-59388 core: Add params to link arrows --- lib/weblib.php | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/weblib.php b/lib/weblib.php index 10eaa29dcff..0cea44cb4b7 100644 --- a/lib/weblib.php +++ b/lib/weblib.php @@ -2215,7 +2215,7 @@ function send_headers($contenttype, $cacheable = true) { * @param string $addclass Additional class names for the link, or the arrow character. * @return string HTML string. */ -function link_arrow_right($text, $url='', $accesshide=false, $addclass='') { +function link_arrow_right($text, $url='', $accesshide=false, $addclass='', $addparams = []) { global $OUTPUT; // TODO: move to output renderer. $arrowclass = 'arrow '; if (!$url) { @@ -2234,7 +2234,16 @@ function link_arrow_right($text, $url='', $accesshide=false, $addclass='') { if ($addclass) { $class .= ' '.$addclass; } - return ''.$htmltext.$arrow.''; + + $linkparams = [ + 'class' => $class, + 'href' => $url, + 'title' => preg_replace('/<.*?>/', '', $text), + ]; + + $linkparams += $addparams; + + return html_writer::link($url, $htmltext . $arrow, $linkparams); } return $htmltext.$arrow; } @@ -2248,7 +2257,7 @@ function link_arrow_right($text, $url='', $accesshide=false, $addclass='') { * @param string $addclass Additional class names for the link, or the arrow character. * @return string HTML string. */ -function link_arrow_left($text, $url='', $accesshide=false, $addclass='') { +function link_arrow_left($text, $url='', $accesshide=false, $addclass='', $addparams = []) { global $OUTPUT; // TODO: move to utput renderer. $arrowclass = 'arrow '; if (! $url) { @@ -2267,7 +2276,16 @@ function link_arrow_left($text, $url='', $accesshide=false, $addclass='') { if ($addclass) { $class .= ' '.$addclass; } - return ''.$arrow.$htmltext.''; + + $linkparams = [ + 'class' => $class, + 'href' => $url, + 'title' => preg_replace('/<.*?>/', '', $text), + ]; + + $linkparams += $addparams; + + return html_writer::link($url, $arrow . $htmltext, $linkparams); } return $arrow.$htmltext; } From def443a086b901df6174f5a27ba194b181c9950d Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 19 Jul 2017 09:27:35 +0800 Subject: [PATCH 02/17] MDL-59388 calendar: Move timestamp gen to new function --- calendar/lib.php | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/calendar/lib.php b/calendar/lib.php index 9b7b76f3911..77df74eaab7 100644 --- a/calendar/lib.php +++ b/calendar/lib.php @@ -2148,20 +2148,7 @@ function calendar_get_link_href($linkbase, $d, $m, $y, $time = 0) { $linkbase = new \moodle_url($linkbase); } - // If a day, month and year were passed then convert it to a timestamp. If these were passed - // then we can assume the day, month and year are passed as Gregorian, as no where in core - // should we be passing these values rather than the time. - if (!empty($d) && !empty($m) && !empty($y)) { - if (checkdate($m, $d, $y)) { - $time = make_timestamp($y, $m, $d); - } else { - $time = time(); - } - } else if (empty($time)) { - $time = time(); - } - - $linkbase->param('time', $time); + $linkbase->param('time', calendar_get_timestamp($d, $m, $y, $time)); return $linkbase; } @@ -3499,3 +3486,29 @@ function calendar_output_fragment_event_form($args) { $html .= $mform->render(); return $html; } + +/** + * Calculate the timestamp from the supplied Gregorian Year, Month, and Day. + * + * @param int $d The day + * @param int $m The month + * @param int $y The year + * @param int $time The timestamp to use instead of a separate y/m/d. + * @return int The timestamp + */ +function calendar_get_timestamp($d, $m, $y, $time = 0) { + // If a day, month and year were passed then convert it to a timestamp. If these were passed + // then we can assume the day, month and year are passed as Gregorian, as no where in core + // should we be passing these values rather than the time. + if (!empty($d) && !empty($m) && !empty($y)) { + if (checkdate($m, $d, $y)) { + $time = make_timestamp($y, $m, $d); + } else { + $time = time(); + } + } else if (empty($time)) { + $time = time(); + } + + return $time; +} From b16dca435aeaa0e2dc783deee537f0e7f3c53649 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 19 Jul 2017 09:30:03 +0800 Subject: [PATCH 03/17] MDL-59388 calendar: Add data-time to calendar navigation links --- calendar/lib.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/calendar/lib.php b/calendar/lib.php index 77df74eaab7..55a12be7192 100644 --- a/calendar/lib.php +++ b/calendar/lib.php @@ -2173,7 +2173,11 @@ function calendar_get_link_previous($text, $linkbase, $d, $m, $y, $accesshide = return $text; } - return link_arrow_left($text, (string)$href, $accesshide, 'previous'); + $attrs = [ + 'data-time' => calendar_get_timestamp($d, $m, $y, $time), + ]; + + return link_arrow_left($text, $href->out(false), $accesshide, 'previous', $attrs); } /** @@ -2196,7 +2200,11 @@ function calendar_get_link_next($text, $linkbase, $d, $m, $y, $accesshide = fals return $text; } - return link_arrow_right($text, (string)$href, $accesshide, 'next'); + $attrs = [ + 'data-time' => calendar_get_timestamp($d, $m, $y, $time), + ]; + + return link_arrow_right($text, $href->out(false), $accesshide, 'next', $attrs); } /** From 6ad2bfd124fe4aa54d73ae429331fa11c9f72a19 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 19 Jul 2017 09:35:19 +0800 Subject: [PATCH 04/17] MDL-59388 calendar: Break out event exporter into a base class --- calendar/classes/external/event_exporter.php | 174 ++------------ .../classes/external/event_exporter_base.php | 225 ++++++++++++++++++ 2 files changed, 250 insertions(+), 149 deletions(-) create mode 100644 calendar/classes/external/event_exporter_base.php diff --git a/calendar/classes/external/event_exporter.php b/calendar/classes/external/event_exporter.php index ce78114b53e..e20b43e2210 100644 --- a/calendar/classes/external/event_exporter.php +++ b/calendar/classes/external/event_exporter.php @@ -26,6 +26,7 @@ namespace core_calendar\external; defined('MOODLE_INTERNAL') || die(); + require_once($CFG->dirroot . "/calendar/lib.php"); use \core\external\exporter; @@ -42,112 +43,7 @@ use \renderer_base; * @copyright 2017 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class event_exporter extends exporter { - - /** - * @var event_interface $event - */ - protected $event; - - /** - * Constructor. - * - * @param event_interface $event - * @param array $related The related data. - */ - public function __construct(event_interface $event, $related = []) { - $this->event = $event; - - $starttimestamp = $event->get_times()->get_start_time()->getTimestamp(); - $endtimestamp = $event->get_times()->get_end_time()->getTimestamp(); - $groupid = $event->get_group() ? $event->get_group()->get('id') : null; - $userid = $event->get_user() ? $event->get_user()->get('id') : null; - - $data = new \stdClass(); - $data->id = $event->get_id(); - $data->name = $event->get_name(); - $data->description = $event->get_description()->get_value(); - $data->descriptionformat = $event->get_description()->get_format(); - $data->groupid = $groupid; - $data->userid = $userid; - $data->eventtype = $event->get_type(); - $data->timestart = $starttimestamp; - $data->timeduration = $endtimestamp - $starttimestamp; - $data->timesort = $event->get_times()->get_sort_time()->getTimestamp(); - $data->visible = $event->is_visible() ? 1 : 0; - $data->timemodified = $event->get_times()->get_modified_time()->getTimestamp(); - - if ($repeats = $event->get_repeats()) { - $data->repeatid = $repeats->get_id(); - } - - if ($cm = $event->get_course_module()) { - $data->modulename = $cm->get('modname'); - $data->instance = $cm->get('id'); - } - - parent::__construct($data, $related); - } - - /** - * Return the list of properties. - * - * @return array - */ - protected static function define_properties() { - return [ - 'id' => ['type' => PARAM_INT], - 'name' => ['type' => PARAM_TEXT], - 'description' => [ - 'type' => PARAM_RAW, - 'optional' => true, - 'default' => null, - 'null' => NULL_ALLOWED - ], - 'descriptionformat' => [ - 'type' => PARAM_INT, - 'optional' => true, - 'default' => null, - 'null' => NULL_ALLOWED - ], - 'groupid' => [ - 'type' => PARAM_INT, - 'optional' => true, - 'default' => null, - 'null' => NULL_ALLOWED - ], - 'userid' => [ - 'type' => PARAM_INT, - 'optional' => true, - 'default' => null, - 'null' => NULL_ALLOWED - ], - 'repeatid' => [ - 'type' => PARAM_INT, - 'optional' => true, - 'default' => null, - 'null' => NULL_ALLOWED - ], - 'modulename' => [ - 'type' => PARAM_TEXT, - 'optional' => true, - 'default' => null, - 'null' => NULL_ALLOWED - ], - 'instance' => [ - 'type' => PARAM_INT, - 'optional' => true, - 'default' => null, - 'null' => NULL_ALLOWED - ], - 'eventtype' => ['type' => PARAM_TEXT], - 'timestart' => ['type' => PARAM_INT], - 'timeduration' => ['type' => PARAM_INT], - 'timesort' => ['type' => PARAM_INT], - 'visible' => ['type' => PARAM_INT], - 'timemodified' => ['type' => PARAM_INT], - ]; - } +class event_exporter extends event_exporter_base { /** * Return the list of additional properties. @@ -155,34 +51,26 @@ class event_exporter extends exporter { * @return array */ protected static function define_other_properties() { - return [ - 'url' => ['type' => PARAM_URL], - 'editurl' => [ - 'type' => PARAM_URL, - 'optional' => true - ], - 'icon' => [ - 'type' => event_icon_exporter::read_properties_definition(), - ], - 'action' => [ - 'type' => event_action_exporter::read_properties_definition(), - 'optional' => true, - ], - 'course' => [ - 'type' => course_summary_exporter::read_properties_definition(), - 'optional' => true, - ], - 'canedit' => ['type' => PARAM_BOOL], - 'displayeventsource' => ['type' => PARAM_BOOL], - 'subscription' => [ - 'type' => PARAM_RAW, - 'optional' => true, - 'default' => null, - 'null' => NULL_ALLOWED - ], - 'isactionevent' => ['type' => PARAM_BOOL], - 'candelete' => ['type' => PARAM_BOOL] + + $values = parent::define_other_properties(); + + $values['canedit'] = ['type' => PARAM_BOOL]; + $values['displayeventsource'] = ['type' => PARAM_BOOL]; + $values['subscription'] = [ + 'type' => PARAM_RAW, + 'optional' => true, + 'default' => null, + 'null' => NULL_ALLOWED ]; + $values['isactionevent'] = ['type' => PARAM_BOOL]; + $values['candelete'] = ['type' => PARAM_BOOL]; + $values['url'] = ['type' => PARAM_URL]; + $values['action'] = [ + 'type' => event_action_exporter::read_properties_definition(), + 'optional' => true, + ]; + + return $values; } /** @@ -192,12 +80,14 @@ class event_exporter extends exporter { * @return array Keys are the property names, values are their values. */ protected function get_other_values(renderer_base $output) { - $values = []; + $values = parent::get_other_values($output); + $event = $this->event; $legacyevent = container::get_event_mapper()->from_event_to_legacy_event($event); $context = $this->related['context']; $values['isactionevent'] = false; + if ($moduleproxy = $event->get_course_module()) { $modulename = $moduleproxy->get('modname'); $moduleid = $moduleproxy->get('id'); @@ -215,11 +105,8 @@ class event_exporter extends exporter { require_once($CFG->dirroot.'/course/lib.php'); $url = \course_get_url($this->related['course'] ?: SITEID); } - $timesort = $event->get_times()->get_sort_time()->getTimestamp(); - $iconexporter = new event_icon_exporter($event, ['context' => $context]); $values['url'] = $url->out(false); - $values['icon'] = $iconexporter->export($output); if ($event instanceof action_event_interface) { $actionrelated = [ @@ -253,18 +140,7 @@ class event_exporter extends exporter { $values['subscription'] = json_encode($subscriptiondata); } } + return $values; } - - /** - * Returns a list of objects that are related. - * - * @return array - */ - protected static function define_related() { - return [ - 'context' => 'context', - 'course' => 'stdClass?', - ]; - } } diff --git a/calendar/classes/external/event_exporter_base.php b/calendar/classes/external/event_exporter_base.php new file mode 100644 index 00000000000..bc176d3c1eb --- /dev/null +++ b/calendar/classes/external/event_exporter_base.php @@ -0,0 +1,225 @@ +. + +/** + * Contains event class for displaying a calendar event. + * + * @package core_calendar + * @copyright 2017 Ryan Wyllie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_calendar\external; + +defined('MOODLE_INTERNAL') || die(); + +use \core\external\exporter; +use \core_calendar\local\event\entities\event_interface; +use \core_calendar\local\event\entities\action_event_interface; +use \core_course\external\course_summary_exporter; +use \renderer_base; + +/** + * Class for displaying a calendar event. + * + * @package core_calendar + * @copyright 2017 Ryan Wyllie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class event_exporter_base extends exporter { + + /** + * @var event_interface $event + */ + protected $event; + + /** + * Constructor. + * + * @param event_interface $event + * @param array $related The related data. + */ + public function __construct(event_interface $event, $related = []) { + $this->event = $event; + + $starttimestamp = $event->get_times()->get_start_time()->getTimestamp(); + $endtimestamp = $event->get_times()->get_end_time()->getTimestamp(); + $groupid = $event->get_group() ? $event->get_group()->get('id') : null; + $userid = $event->get_user() ? $event->get_user()->get('id') : null; + + $data = new \stdClass(); + $data->id = $event->get_id(); + $data->name = $event->get_name(); + $data->description = $event->get_description()->get_value(); + $data->descriptionformat = $event->get_description()->get_format(); + $data->groupid = $groupid; + $data->userid = $userid; + $data->eventtype = $event->get_type(); + $data->timestart = $starttimestamp; + $data->timeduration = $endtimestamp - $starttimestamp; + $data->timesort = $event->get_times()->get_sort_time()->getTimestamp(); + $data->visible = $event->is_visible() ? 1 : 0; + $data->timemodified = $event->get_times()->get_modified_time()->getTimestamp(); + + if ($repeats = $event->get_repeats()) { + $data->repeatid = $repeats->get_id(); + } + + if ($cm = $event->get_course_module()) { + $data->modulename = $cm->get('modname'); + $data->instance = $cm->get('id'); + } + + parent::__construct($data, $related); + } + + /** + * Return the list of properties. + * + * @return array + */ + protected static function define_properties() { + return [ + 'id' => ['type' => PARAM_INT], + 'name' => ['type' => PARAM_TEXT], + 'description' => [ + 'type' => PARAM_RAW, + 'optional' => true, + 'default' => null, + 'null' => NULL_ALLOWED + ], + 'descriptionformat' => [ + 'type' => PARAM_INT, + 'optional' => true, + 'default' => null, + 'null' => NULL_ALLOWED + ], + 'groupid' => [ + 'type' => PARAM_INT, + 'optional' => true, + 'default' => null, + 'null' => NULL_ALLOWED + ], + 'userid' => [ + 'type' => PARAM_INT, + 'optional' => true, + 'default' => null, + 'null' => NULL_ALLOWED + ], + 'repeatid' => [ + 'type' => PARAM_INT, + 'optional' => true, + 'default' => null, + 'null' => NULL_ALLOWED + ], + 'modulename' => [ + 'type' => PARAM_TEXT, + 'optional' => true, + 'default' => null, + 'null' => NULL_ALLOWED + ], + 'instance' => [ + 'type' => PARAM_INT, + 'optional' => true, + 'default' => null, + 'null' => NULL_ALLOWED + ], + 'eventtype' => ['type' => PARAM_TEXT], + 'timestart' => ['type' => PARAM_INT], + 'timeduration' => ['type' => PARAM_INT], + 'timesort' => ['type' => PARAM_INT], + 'visible' => ['type' => PARAM_INT], + 'timemodified' => ['type' => PARAM_INT], + ]; + } + + /** + * Return the list of additional properties. + * + * @return array + */ + protected static function define_other_properties() { + return [ + 'url' => ['type' => PARAM_URL], + 'icon' => [ + 'type' => event_icon_exporter::read_properties_definition(), + ], + 'action' => [ + 'type' => event_action_exporter::read_properties_definition(), + 'optional' => true, + ], + 'course' => [ + 'type' => course_summary_exporter::read_properties_definition(), + 'optional' => true, + ] + ]; + } + + /** + * Get the additional values to inject while exporting. + * + * @param renderer_base $output The renderer. + * @return array Keys are the property names, values are their values. + */ + protected function get_other_values(renderer_base $output) { + $values = []; + $event = $this->event; + $context = $this->related['context']; + if ($moduleproxy = $event->get_course_module()) { + $modulename = $moduleproxy->get('modname'); + $moduleid = $moduleproxy->get('id'); + $url = new \moodle_url(sprintf('/mod/%s/view.php', $modulename), ['id' => $moduleid]); + } else { + // TODO MDL-58866 We do not have any way to find urls for events outside of course modules. + global $CFG; + require_once($CFG->dirroot.'/course/lib.php'); + $url = \course_get_url($this->related['course'] ?: SITEID); + } + $timesort = $event->get_times()->get_sort_time()->getTimestamp(); + $iconexporter = new event_icon_exporter($event, ['context' => $context]); + + $values['url'] = $url->out(false); + $values['icon'] = $iconexporter->export($output); + + if ($event instanceof action_event_interface) { + $actionrelated = [ + 'context' => $context, + 'event' => $event + ]; + $actionexporter = new event_action_exporter($event->get_action(), $actionrelated); + $values['action'] = $actionexporter->export($output); + } + + if ($course = $this->related['course']) { + $coursesummaryexporter = new course_summary_exporter($course, ['context' => $context]); + $values['course'] = $coursesummaryexporter->export($output); + } + + return $values; + } + + /** + * Returns a list of objects that are related. + * + * @return array + */ + protected static function define_related() { + return [ + 'context' => 'context', + 'course' => 'stdClass?', + ]; + } +} From 7fe41af525e534d488a7197f4b08951c547bb8cb Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 19 Jul 2017 09:36:29 +0800 Subject: [PATCH 05/17] MDL-59388 calendar: Allow filter to be specified when fetching events --- calendar/classes/local/api.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/calendar/classes/local/api.php b/calendar/classes/local/api.php index 4eb25fdfb8f..e7bddfdd104 100644 --- a/calendar/classes/local/api.php +++ b/calendar/classes/local/api.php @@ -70,7 +70,8 @@ class api { array $groupsfilter = null, array $coursesfilter = null, $withduration = true, - $ignorehidden = true + $ignorehidden = true, + callable $filter = null ) { global $USER; @@ -100,7 +101,8 @@ class api { $groupsfilter, $coursesfilter, $withduration, - $ignorehidden + $ignorehidden, + $filter ); } From c075a3de344f08c41de69ad8a42070457854c667 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 19 Jul 2017 09:37:14 +0800 Subject: [PATCH 06/17] MDL-59388 calendar: Add a helper to convert ymd to timestamp --- calendar/classes/type_base.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/calendar/classes/type_base.php b/calendar/classes/type_base.php index a7d52fe2f57..a211be15806 100644 --- a/calendar/classes/type_base.php +++ b/calendar/classes/type_base.php @@ -217,4 +217,26 @@ abstract class type_base { * @return string locale */ public abstract function locale_win_charset(); + + /** + * Provided with a day, month, year, hour and minute in the specific + * calendar type convert it into the equivalent Unix Time Stamp. + * + * @param int $year + * @param int $month + * @param int $day + * @param int $hour + * @param int $minute + * @return int timestamp + */ + public function convert_to_timestamp($year, $month, $day, $hour = 0, $minute = 0) { + $gregorianinfo = $this->convert_to_gregorian($year, $month, $day, $hour, $minute); + return make_timestamp( + $gregorianinfo['year'], + $gregorianinfo['month'], + $gregorianinfo['day'], + $gregorianinfo['hour'], + $gregorianinfo['minute'], + 0); + } } From 64ff737a2f7bc3d1f7fad50fccb7c1af68ba70da Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 19 Jul 2017 09:34:34 +0800 Subject: [PATCH 07/17] MDL-59388 calendar: Switch monthly view to use templates --- .../external/calendar_event_exporter.php | 90 +++++++ calendar/classes/external/day_exporter.php | 111 +++++++++ .../classes/external/day_name_exporter.php | 47 ++++ calendar/classes/external/month_exporter.php | 226 +++++++++++++++++ calendar/classes/external/week_exporter.php | 108 ++++++++ calendar/lib.php | 105 ++++++++ calendar/renderer.php | 232 +----------------- calendar/templates/month_detailed.mustache | 66 +++++ calendar/templates/month_header.mustache | 5 + calendar/templates/month_navigation.mustache | 5 + calendar/view.php | 81 +++--- theme/boost/scss/moodle/calendar.scss | 6 +- theme/bootstrapbase/less/moodle/calendar.less | 6 +- theme/bootstrapbase/style/moodle.css | 6 +- 14 files changed, 816 insertions(+), 278 deletions(-) create mode 100644 calendar/classes/external/calendar_event_exporter.php create mode 100644 calendar/classes/external/day_exporter.php create mode 100644 calendar/classes/external/day_name_exporter.php create mode 100644 calendar/classes/external/month_exporter.php create mode 100644 calendar/classes/external/week_exporter.php create mode 100644 calendar/templates/month_detailed.mustache create mode 100644 calendar/templates/month_header.mustache create mode 100644 calendar/templates/month_navigation.mustache diff --git a/calendar/classes/external/calendar_event_exporter.php b/calendar/classes/external/calendar_event_exporter.php new file mode 100644 index 00000000000..e3b53a9afb1 --- /dev/null +++ b/calendar/classes/external/calendar_event_exporter.php @@ -0,0 +1,90 @@ +. + +/** + * Contains event class for displaying a calendar event. + * + * @package core_calendar + * @copyright 2017 Ryan Wyllie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_calendar\external; + +defined('MOODLE_INTERNAL') || die(); + +use \core\external\exporter; +use \core_calendar\local\event\entities\event_interface; +use \core_calendar\local\event\entities\action_event_interface; +use \core_course\external\course_summary_exporter; +use \renderer_base; + +/** + * Class for displaying a calendar event. + * + * @package core_calendar + * @copyright 2017 Ryan Wyllie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class calendar_event_exporter extends event_exporter_base { + + /** + * Return the list of additional properties. + * + * @return array + */ + protected static function define_other_properties() { + return [ + 'url' => ['type' => PARAM_URL], + 'icon' => [ + 'type' => event_icon_exporter::read_properties_definition(), + ], + 'course' => [ + 'type' => course_summary_exporter::read_properties_definition(), + 'optional' => true, + ] + ]; + } + + /** + * Get the additional values to inject while exporting. + * + * @param renderer_base $output The renderer. + * @return array Keys are the property names, values are their values. + */ + protected function get_other_values(renderer_base $output) { + $values = parent::get_other_values($output); + + $eventid = $this->event->get_id(); + + $url = new \moodle_url($this->related['daylink'], [], "event_{$eventid}"); + $values['url'] = $url->out(false); + + return $values; + } + + /** + * Returns a list of objects that are related. + * + * @return array + */ + protected static function define_related() { + $related = parent::define_related(); + $related['daylink'] = \moodle_url::class; + + return $related; + } +} diff --git a/calendar/classes/external/day_exporter.php b/calendar/classes/external/day_exporter.php new file mode 100644 index 00000000000..827b3025d3c --- /dev/null +++ b/calendar/classes/external/day_exporter.php @@ -0,0 +1,111 @@ + [ + 'type' => PARAM_INT, + ], + 'minutes' => [ + 'type' => PARAM_INT, + ], + 'hours' => [ + 'type' => PARAM_INT, + ], + 'mday' => [ + 'type' => PARAM_INT, + ], + 'wday' => [ + 'type' => PARAM_INT, + ], + 'year' => [ + 'type' => PARAM_INT, + ], + 'yday' => [ + 'type' => PARAM_INT, + ], + ]; + } + + protected static function define_other_properties() { + return [ + 'timestamp' => [ + 'type' => PARAM_INT, + ], + 'istoday' => [ + 'type' => PARAM_BOOL, + 'default' => false, + ], + 'isweekend' => [ + 'type' => PARAM_BOOL, + 'default' => false, + ], + 'viewdaylink' => [ + 'type' => PARAM_URL, + 'optional' => true, + ], + 'events' => [ + 'type' => event_exporter::read_properties_definition(), + 'multiple' => true, + ], + 'viewdaylink' => [ + 'type' => PARAM_URL, + ], + + //'viewdaylink' => $this->viewdaylink->out(false), + //'createeventlink' => $this->createeventlink, + //'viewdaylinktitle' => $this->get_title($renderer), + //'events' => $this->get_events($renderer), + //'hasevents' => !empty($this->events), + //'eventtypes' => array_unique($this->eventtypes), + //'eventcount' => count($this->events), + //'durationevents' => array_unique($this->durationevents), + ]; + } + + protected function get_other_values(renderer_base $output) { + //$events = new events_exporter($this->related['events'], $this->related); + $return = [ + 'timestamp' => $this->data[0], + ]; + + $url = new moodle_url('/calendar/view.php', [ + 'view' => 'day', + 'time' => $this->data[0], + ]); + $return['viewdaylink'] = $url->out(false); + + + $cache = $this->related['cache']; + $return['events'] = array_map(function($event) use ($cache, $output, $url) { + $context = $cache->get_context($event); + $course = $cache->get_course($event); + $exporter = new calendar_event_exporter($event, [ + 'context' => $context, + 'course' => $course, + 'daylink' => $url, + ]); + + return $exporter->export($output); + }, $this->related['events']); + + return $return; + } + + protected static function define_related() { + return [ + 'events' => '\core_calendar\local\event\entities\event_interface[]', + 'cache' => '\core_calendar\external\events_related_objects_cache', + 'type' => '\core_calendar\type_base', + ]; + } +} diff --git a/calendar/classes/external/day_name_exporter.php b/calendar/classes/external/day_name_exporter.php new file mode 100644 index 00000000000..afa17c78f43 --- /dev/null +++ b/calendar/classes/external/day_name_exporter.php @@ -0,0 +1,47 @@ + $dayno]; + + parent::__construct($data, []); + } + + protected static function define_properties() { + return [ + 'dayno' => [ + 'type' => PARAM_INT, + ], + 'shortname' => [ + // Note: The calendar type class has already formatted the names. + 'type' => PARAM_RAW, + ], + 'fullname' => [ + // Note: The calendar type class has already formatted the names. + 'type' => PARAM_RAW, + ], + ]; + } +} diff --git a/calendar/classes/external/month_exporter.php b/calendar/classes/external/month_exporter.php new file mode 100644 index 00000000000..701a2fd1e16 --- /dev/null +++ b/calendar/classes/external/month_exporter.php @@ -0,0 +1,226 @@ +calendar = $calendar; + $this->firstdayofweek = $type->get_starting_weekday(); + + $this->url = new moodle_url('/calendar/view.php', [ + 'view' => 'month', + 'time' => $calendar->time, + ]); + + if ($this->calendar->courseid) { + $this->url->param('course', $this->calendar->courseid); + } + + $related['type'] = $type; + + parent::__construct([], $related); + } + + protected static function define_other_properties() { + return [ + 'courseid' => [ + 'type' => PARAM_INT, + ], + 'filter_selector' => [ + 'type' => PARAM_RAW, + ], + 'navigation' => [ + 'type' => PARAM_RAW, + ], + 'weeks' => [ + 'type' => week_exporter::read_properties_definition(), + 'multiple' => true, + ], + 'daynames' => [ + 'type' => day_name_exporter::read_properties_definition(), + 'multiple' => true, + ], + 'view' => [ + 'type' => PARAM_ALPHA, + ], + 'previousperiod' => [ + 'type' => PARAM_INT, + ], + 'nextperiod' => [ + 'type' => PARAM_INT, + ], + ]; + } + + protected function get_other_values(renderer_base $output) { + return [ + 'courseid' => $this->calendar->courseid, + 'view' => 'month', + 'previousperiod' => $this->get_previous_month_timestamp(), + 'nextperiod' => $this->get_next_month_timestamp(), + 'filter_selector' => $this->get_course_filter_selector($output), + 'navigation' => $this->get_navigation($output), + 'weeks' => $this->get_weeks($output), + 'daynames' => $this->get_day_names($output), + ]; + } + + /** + * Get the course filter selector. + * TODO Convert to new exporter? + * + * @param renderer_base $output + * return string + */ + protected function get_course_filter_selector(renderer_base $output) { + $content = ''; + $content .= $output->course_filter_selector($this->url, get_string('detailedmonthviewfor', 'calendar')); + if (calendar_user_can_add_event($this->calendar->course)) { + $content .= $output->add_event_button($this->calendar->courseid, 0, 0, 0, $this->calendar->time); + } + + return $content; + } + + /** + * Get the course filter selector. + * TODO Convert to new exporter? + * + * @param renderer_base $output + * return string + */ + protected function get_navigation(renderer_base $output) { + return calendar_top_controls('month', [ + 'id' => $this->calendar->courseid, + 'time' => $this->calendar->time, + ]); + } + + /** + * Get the list of day names for display, re-ordered from the first day + * of the week. + * + * @param renderer_base $output + * @return day_name_exporter[] + */ + protected function get_day_names(renderer_base $output) { + $weekdays = $this->related['type']->get_weekdays(); + $daysinweek = count($weekdays); + + $daynames = []; + for ($i = 0; $i < $daysinweek; $i++) { + // Bump the currentdayno and ensure it loops. + $dayno = ($i + $this->firstdayofweek + $daysinweek) % $daysinweek; + $dayname = new day_name_exporter($dayno, $weekdays[$dayno]); + $daynames[] = $dayname->export($output); + } + + return $daynames; + } + + /** + * Get the list of week days, ordered into weeks and padded according + * to the value of the first day of the week. + * + * @param renderer_base $output + * @return array + */ + protected function get_weeks(renderer_base $output) { + $weeks = []; + $alldays = $this->get_days(); + + $daysinweek = count($this->related['type']->get_weekdays()); + + // Calculate which day number is the first, and last day of the week. + $firstdayofweek = $this->firstdayofweek; + $lastdayofweek = ($firstdayofweek + $daysinweek - 1) % $daysinweek; + + // The first week is special as it may have padding at the beginning. + $day = reset($alldays); + $firstdayno = $day['wday']; + + $prepadding = ($firstdayno + $daysinweek - 1) % $daysinweek; + $daysinfirstweek = $daysinweek - $prepadding; + $days = array_slice($alldays, 0, $daysinfirstweek); + $week = new week_exporter($days, $prepadding, ($daysinweek - count($days) - $prepadding), $this->related); + $weeks[] = $week->export($output); + + // Now chunk up the remaining day. and turn them into weeks. + $daychunks = array_chunk(array_slice($alldays, $daysinfirstweek), $daysinweek); + foreach ($daychunks as $days) { + $week = new week_exporter($days, 0, ($daysinweek - count($days)), $this->related); + $weeks[] = $week->export($output); + } + + return $weeks; + } + + /** + * Get the list of days with the matching date array. + * + * @return array + */ + protected function get_days() { + $date = $this->related['type']->timestamp_to_date_array($this->calendar->time); + $monthdays = $this->related['type']->get_num_days_in_month($date['year'], $date['mon']); + + $days = []; + for ($dayno = 1; $dayno <= $monthdays; $dayno++) { + // Get the gregorian representation of the day. + $timestamp = $this->related['type']->convert_to_timestamp($date['year'], $date['mon'], $dayno); + + $days[] = $this->related['type']->timestamp_to_date_array($timestamp); + } + + return $days; + } + + protected static function define_related() { + return [ + 'events' => '\core_calendar\local\event\entities\event_interface[]', + 'cache' => '\core_calendar\external\events_related_objects_cache', + 'type' => '\core_calendar\type_base', + ]; + } + + protected function get_previous_month_timestamp() { + $date = $this->related['type']->timestamp_to_date_array($this->calendar->time); + $month = calendar_sub_month($date['mon'], $date['year']); + $monthtime = $this->related['type']->convert_to_gregorian($month[1], $month[0], 1); + return make_timestamp($monthtime['year'], $monthtime['month'], $monthtime['day'], $monthtime['hour'], $monthtime['minute']); + } + + protected function get_next_month_timestamp() { + $date = $this->related['type']->timestamp_to_date_array($this->calendar->time); + $month = calendar_sub_month($date['mon'], $date['year']); + $monthtime = $this->related['type']->convert_to_gregorian($month[1], $month[0], 1); + return make_timestamp($monthtime['year'], $monthtime['month'], $monthtime['day'], $monthtime['hour'], $monthtime['minute']); + } +} diff --git a/calendar/classes/external/week_exporter.php b/calendar/classes/external/week_exporter.php new file mode 100644 index 00000000000..c8427cece56 --- /dev/null +++ b/calendar/classes/external/week_exporter.php @@ -0,0 +1,108 @@ +days = $days; + $this->prepadding = $prepadding; + $this->postpadding = $postpadding; + + parent::__construct([], $related); + } + + protected static function define_other_properties() { + return [ + 'prepadding' => [ + 'type' => PARAM_INT, + 'multiple' => true, + ], + 'postpadding' => [ + 'type' => PARAM_INT, + 'multiple' => true, + ], + 'days' => [ + 'type' => day_exporter::read_properties_definition(), + 'multiple' => true, + ], + ]; + } + + protected function get_other_values(renderer_base $output) { + $return = [ + 'prepadding' => [], + 'postpadding' => [], + 'days' => [], + ]; + + for ($i = 0; $i < $this->prepadding; $i++) { + $return['prepadding'][] = $i; + } + for ($i = 0; $i < $this->postpadding; $i++) { + $return['postpadding'][] = $i; + } + + $return['days'] = []; + foreach ($this->days as $daydata) { + $events = []; + foreach ($this->related['events'] as $event) { + $times = $event->get_times(); + $starttime = $times->get_start_time()->getTimestamp(); + $startdate = $this->related['type']->timestamp_to_date_array($starttime); + $endtime = $times->get_end_time()->getTimestamp(); + $enddate = $this->related['type']->timestamp_to_date_array($endtime); + + if ((($startdate['year'] * 366) + $startdate['yday']) > ($daydata['year'] * 366) + $daydata['yday']) { + // Starts after today. + continue; + } + if ((($enddate['year'] * 366) + $enddate['yday']) < ($daydata['year'] * 366) + $daydata['yday']) { + // Ends before today. + continue; + } + + $events[] = $event; + } + + + $day = new day_exporter($daydata, [ + 'events' => $events, + 'cache' => $this->related['cache'], + 'type' => $this->related['type'], + ]); + $return['days'][] = $day->export($output); + } + + return $return; + } + + protected static function define_related() { + return [ + 'events' => '\core_calendar\local\event\entities\event_interface[]', + 'cache' => '\core_calendar\external\events_related_objects_cache', + 'type' => '\core_calendar\type_base', + ]; + } +} diff --git a/calendar/lib.php b/calendar/lib.php index 55a12be7192..7cb8c821df6 100644 --- a/calendar/lib.php +++ b/calendar/lib.php @@ -3422,6 +3422,111 @@ function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses, $ }, []); } + +/** + * Get theh calendar view output. + * + * @param \calendar_information $calendar The calendar being represented + * @param string $view The type of calendar to have displayed + * @return array[array, string] + */ +function calendar_get_view(\calendar_information $calendar, $view) { + global $PAGE, $DB, $OUTPUT; + + $renderer = $PAGE->get_renderer('core_calendar'); + $type = \core_calendar\type_factory::get_calendar_instance(); + + // Calculate the bounds of the month. + $date = $type->timestamp_to_date_array($calendar->time); + $tstart = $type->convert_to_timestamp($date['year'], $date['mon'], 1); + + if ($view === 'day') { + $tend = $tstart + DAYSECS - 1; + $selectortitle = get_string('dayviewfor', 'calendar'); + } else if ($view === 'upcoming') { + $defaultlookahead = isset($CFG->calendar_lookahead) ? intval($CFG->calendar_lookahead) : CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD; + $tend = $tstart + get_user_preferences('calendar_lookahead', $defaultlookahead); + $selectortitle = get_string('upcomingeventsfor', 'calendar'); + } else { + $monthdays = $type->get_num_days_in_month($date['year'], $date['mon']); + $tend = $tstart + ($monthdays * DAYSECS) - 1; + $selectortitle = get_string('detailedmonthviewfor', 'calendar'); + } + + list($userparam, $groupparam, $courseparam) = array_map(function($param) { + // If parameter is true, return null. + if ($param === true) { + return null; + } + + // If parameter is false, return an empty array. + if ($param === false) { + return []; + } + + // If the parameter is a scalar value, enclose it in an array. + if (!is_array($param)) { + return [$param]; + } + + // No normalisation required. + return $param; + }, [$calendar->users, $calendar->groups, $calendar->courses]); + + $events = \core_calendar\local\api::get_events( + $tstart, + $tend, + null, + null, + null, + null, + 40, + null, + $userparam, + $groupparam, + $courseparam, + true, + true, + function ($event) { + if ($proxy = $event->get_course_module()) { + $cminfo = $proxy->get_proxied_instance(); + return $cminfo->uservisible; + + } + + return true; + } + ); + + $related = [ + 'events' => $events, + 'cache' => new \core_calendar\external\events_related_objects_cache($events), + ]; + + if ($view === 'day') { + // TODO: Export the days events in a new exporter. + } else if ($view === 'upcoming') { + $defaultlookahead = CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD; + if (isset($CFG->calendar_lookahead)) { + $defaultlookahead = intval($CFG->calendar_lookahead); + } + $lookahead = get_user_preferences('calendar_lookahead', $defaultlookahead); + + $defaultmaxevents = CALENDAR_DEFAULT_UPCOMING_MAXEVENTS; + if (isset($CFG->calendar_maxevents)) { + $defaultmaxevents = intval($CFG->calendar_maxevents); + } + $maxevents = get_user_preferences('calendar_maxevents', $defaultmaxevents); + // TODO: Export the upcoming events in a new exporter. + } else { + $month = new \core_calendar\external\month_exporter($calendar, $type, $related); + $data = $month->export($renderer); + $template = 'core_calendar/month_detailed'; + } + + return [$data, $template]; +} + /** * Request and render event form fragment. * diff --git a/calendar/renderer.php b/calendar/renderer.php index f1bebaab0c1..6e7cab8ef0a 100644 --- a/calendar/renderer.php +++ b/calendar/renderer.php @@ -128,7 +128,7 @@ class core_calendar_renderer extends plugin_renderer_base { * $month and $year are kept for backwards compatibility. * @return string */ - protected function add_event_button($courseid, $day = null, $month = null, $year = null, $time = null) { + public function add_event_button($courseid, $day = null, $month = null, $year = null, $time = null) { // If a day, month and year were passed then convert it to a timestamp. If these were passed // then we can assume the day, month and year are passed as Gregorian, as no where in core // should we be passing these values rather than the time. This is done for BC. @@ -312,234 +312,6 @@ class core_calendar_renderer extends plugin_renderer_base { return html_writer::tag('div', $eventhtml, array('class' => 'event', 'id' => 'event_' . $event->id)); } - /** - * Displays a month in detail - * - * @param calendar_information $calendar - * @param moodle_url $returnurl the url to return to - * @return string - */ - public function show_month_detailed(calendar_information $calendar, moodle_url $returnurl = null) { - global $CFG; - - if (empty($returnurl)) { - $returnurl = $this->page->url; - } - - // Get the calendar type we are using. - $calendartype = \core_calendar\type_factory::get_calendar_instance(); - - // Store the display settings. - $display = new stdClass; - $display->thismonth = false; - - // Get the specified date in the calendar type being used. - $date = $calendartype->timestamp_to_date_array($calendar->time); - $thisdate = $calendartype->timestamp_to_date_array(time()); - if ($date['mon'] == $thisdate['mon'] && $date['year'] == $thisdate['year']) { - $display->thismonth = true; - $date = $thisdate; - $calendar->time = time(); - } - - // Get Gregorian date for the start of the month. - $gregoriandate = $calendartype->convert_to_gregorian($date['year'], $date['mon'], 1); - // Store the gregorian date values to be used later. - list($gy, $gm, $gd, $gh, $gmin) = array($gregoriandate['year'], $gregoriandate['month'], $gregoriandate['day'], - $gregoriandate['hour'], $gregoriandate['minute']); - - // Get the starting week day for this month. - $startwday = dayofweek(1, $date['mon'], $date['year']); - // Get the days in a week. - $daynames = calendar_get_days(); - // Store the number of days in a week. - $numberofdaysinweek = $calendartype->get_num_weekdays(); - - $display->minwday = calendar_get_starting_weekday(); - $display->maxwday = $display->minwday + ($numberofdaysinweek - 1); - $display->maxdays = calendar_days_in_month($date['mon'], $date['year']); - - // These are used for DB queries, so we want unixtime, so we need to use Gregorian dates. - $display->tstart = make_timestamp($gy, $gm, $gd, $gh, $gmin, 0); - $display->tend = $display->tstart + ($display->maxdays * DAYSECS) - 1; - - // Align the starting weekday to fall in our display range - // This is simple, not foolproof. - if ($startwday < $display->minwday) { - $startwday += $numberofdaysinweek; - } - - // Get events from database - $events = calendar_get_legacy_events($display->tstart, $display->tend, $calendar->users, $calendar->groups, - $calendar->courses); - if (!empty($events)) { - foreach($events as $eventid => $event) { - $event = new calendar_event($event); - if (!empty($event->modulename)) { - $instances = get_fast_modinfo($event->courseid)->get_instances_of($event->modulename); - if (empty($instances[$event->instance]->uservisible)) { - unset($events[$eventid]); - } - } - } - } - - // Extract information: events vs. time - calendar_events_by_day($events, $date['mon'], $date['year'], $eventsbyday, $durationbyday, - $typesbyday, $calendar->courses); - - $output = html_writer::start_tag('div', array('class'=>'header')); - $output .= $this->course_filter_selector($returnurl, get_string('detailedmonthviewfor', 'calendar')); - if (calendar_user_can_add_event($calendar->course)) { - $output .= $this->add_event_button($calendar->course->id, 0, 0, 0, $calendar->time); - } - $output .= html_writer::end_tag('div', array('class'=>'header')); - // Controls - $output .= html_writer::tag('div', calendar_top_controls('month', array('id' => $calendar->courseid, - 'time' => $calendar->time)), array('class' => 'controls')); - - $table = new html_table(); - $table->attributes = array('class'=>'calendarmonth calendartable'); - $table->summary = get_string('calendarheading', 'calendar', userdate($calendar->time, get_string('strftimemonthyear'))); - $table->data = array(); - - // Get the day names as the header. - $header = array(); - for($i = $display->minwday; $i <= $display->maxwday; ++$i) { - $header[] = $daynames[$i % $numberofdaysinweek]['shortname']; - } - $table->head = $header; - - // For the table display. $week is the row; $dayweek is the column. - $week = 1; - $dayweek = $startwday; - - $row = new html_table_row(array()); - - // Paddding (the first week may have blank days in the beginning) - for($i = $display->minwday; $i < $startwday; ++$i) { - $cell = new html_table_cell(' '); - $cell->attributes = array('class'=>'nottoday dayblank'); - $row->cells[] = $cell; - } - - // Now display all the calendar - $weekend = CALENDAR_DEFAULT_WEEKEND; - if (isset($CFG->calendar_weekend)) { - $weekend = intval($CFG->calendar_weekend); - } - - $daytime = strtotime('-1 day', $display->tstart); - for ($day = 1; $day <= $display->maxdays; ++$day, ++$dayweek) { - $daytime = strtotime('+1 day', $daytime); - if($dayweek > $display->maxwday) { - // We need to change week (table row) - $table->data[] = $row; - $row = new html_table_row(array()); - $dayweek = $display->minwday; - ++$week; - } - - // Reset vars - $cell = new html_table_cell(); - $dayhref = calendar_get_link_href(new moodle_url(CALENDAR_URL . 'view.php', - array('view' => 'day', 'course' => $calendar->courseid)), 0, 0, 0, $daytime); - - $cellclasses = array(); - - if ($weekend & (1 << ($dayweek % $numberofdaysinweek))) { - // Weekend. This is true no matter what the exact range is. - $cellclasses[] = 'weekend'; - } - - // Special visual fx if an event is defined - if (isset($eventsbyday[$day])) { - if(count($eventsbyday[$day]) == 1) { - $title = get_string('oneevent', 'calendar'); - } else { - $title = get_string('manyevents', 'calendar', count($eventsbyday[$day])); - } - $cell->text = html_writer::tag('div', html_writer::link($dayhref, $day, array('title'=>$title)), array('class'=>'day')); - } else { - $cell->text = html_writer::tag('div', $day, array('class'=>'day')); - } - - // Special visual fx if an event spans many days - $durationclass = false; - if (isset($typesbyday[$day]['durationglobal'])) { - $durationclass = 'duration_global'; - } else if (isset($typesbyday[$day]['durationcourse'])) { - $durationclass = 'duration_course'; - } else if (isset($typesbyday[$day]['durationgroup'])) { - $durationclass = 'duration_group'; - } else if (isset($typesbyday[$day]['durationuser'])) { - $durationclass = 'duration_user'; - } - if ($durationclass) { - $cellclasses[] = 'duration'; - $cellclasses[] = $durationclass; - } - - // Special visual fx for today - if ($display->thismonth && $day == $date['mday']) { - $cellclasses[] = 'day today'; - } else { - $cellclasses[] = 'day nottoday'; - } - $cell->attributes = array('class'=>join(' ',$cellclasses)); - - if (isset($eventsbyday[$day])) { - $cell->text .= html_writer::start_tag('ul', array('class'=>'events-new')); - foreach($eventsbyday[$day] as $eventindex) { - // If event has a class set then add it to the event
  • tag - $attributes = array(); - if (!empty($events[$eventindex]->class)) { - $attributes['class'] = $events[$eventindex]->class; - } - $dayhref->set_anchor('event_'.$events[$eventindex]->id); - - $eventcontext = $events[$eventindex]->context; - $eventformatopts = array('context' => $eventcontext); - // Get event name. - $eventname = format_string($events[$eventindex]->name, true, $eventformatopts); - // Include course's shortname into the event name, if applicable. - $courseid = $events[$eventindex]->courseid; - if (!empty($courseid) && $courseid !== SITEID) { - $course = get_course($courseid); - $eventnameparams = (object)[ - 'name' => $eventname, - 'course' => format_string($course->shortname, true, $eventformatopts) - ]; - $eventname = get_string('eventnameandcourse', 'calendar', $eventnameparams); - } - $link = html_writer::link($dayhref, $eventname, ['data-action' => 'view-event', - 'data-event-id' => $events[$eventindex]->id]); - $cell->text .= html_writer::tag('li', $link, $attributes); - } - $cell->text .= html_writer::end_tag('ul'); - } - if (isset($durationbyday[$day])) { - $cell->text .= html_writer::start_tag('ul', array('class'=>'events-underway')); - foreach($durationbyday[$day] as $eventindex) { - $cell->text .= html_writer::tag('li', '['.format_string($events[$eventindex]->name,true).']', array('class'=>'events-underway')); - } - $cell->text .= html_writer::end_tag('ul'); - } - $row->cells[] = $cell; - } - - // Paddding (the last week may have blank days at the end) - for($i = $dayweek; $i <= $display->maxwday; ++$i) { - $cell = new html_table_cell(' '); - $cell->attributes = array('class'=>'nottoday dayblank'); - $row->cells[] = $cell; - } - $table->data[] = $row; - $output .= html_writer::table($table); - - return $output; - } - /** * Displays upcoming events * @@ -587,7 +359,7 @@ class core_calendar_renderer extends plugin_renderer_base { * @param string $label The label to use for the course select. * @return string */ - protected function course_filter_selector(moodle_url $returnurl, $label=null) { + public function course_filter_selector(moodle_url $returnurl, $label=null) { global $USER, $SESSION, $CFG; if (!isloggedin() or isguestuser()) { diff --git a/calendar/templates/month_detailed.mustache b/calendar/templates/month_detailed.mustache new file mode 100644 index 00000000000..de10bc2827e --- /dev/null +++ b/calendar/templates/month_detailed.mustache @@ -0,0 +1,66 @@ + + {{> core_calendar/month_header }} + {{> core_calendar/month_navigation }} + + + + {{# daynames }} + + {{/ daynames }} + + + + {{#weeks}} + + {{#prepadding}} + + {{/prepadding}} + {{#days}} + + {{/days}} + {{#postpadding}} + + {{/postpadding}} + + {{/weeks}} + +
    + {{shortname}} +
      +
    + {{#events.0}} + {{mday}} + {{/events.0}} + {{^events.0}} + {{mday}} + {{/events.0}} + {{#events.0}} +
      + {{#events}} + {{#underway}} +
    • [{{name}}]
    • + {{/underway}} + {{^underway}} +
    • + {{name}} +
    • + {{/underway}} + {{/events}} +
    + {{/events.0}} +
    +
    + {{#events.0}} + {{mday}} + {{/events.0}} + {{^events.0}} + {{mday}} + {{/events.0}} +
    +
     
    +
    diff --git a/calendar/templates/month_header.mustache b/calendar/templates/month_header.mustache new file mode 100644 index 00000000000..d5ce533a85f --- /dev/null +++ b/calendar/templates/month_header.mustache @@ -0,0 +1,5 @@ +{{#filter_selector}} +
    + {{{filter_selector}}} +
    +{{/filter_selector}} diff --git a/calendar/templates/month_navigation.mustache b/calendar/templates/month_navigation.mustache new file mode 100644 index 00000000000..481021eeee3 --- /dev/null +++ b/calendar/templates/month_navigation.mustache @@ -0,0 +1,5 @@ +{{#navigation}} +
    + {{{navigation}}} +
    +{{/navigation}} diff --git a/calendar/view.php b/calendar/view.php index f974ba5bb84..fdf3cb05ff8 100644 --- a/calendar/view.php +++ b/calendar/view.php @@ -132,48 +132,51 @@ echo $renderer->start_layout(); echo html_writer::start_tag('div', array('class'=>'heightcontainer')); echo $OUTPUT->heading(get_string('calendar', 'calendar')); -switch($view) { - case 'day': - echo $renderer->show_day($calendar); - break; - case 'month': - echo $renderer->show_month_detailed($calendar, $url); - break; - case 'upcoming': - $defaultlookahead = CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD; - if (isset($CFG->calendar_lookahead)) { - $defaultlookahead = intval($CFG->calendar_lookahead); - } - $lookahead = get_user_preferences('calendar_lookahead', $defaultlookahead); +if ($view == 'day' || $view == 'upcoming') { + switch($view) { + case 'day': + echo $renderer->show_day($calendar); + break; + case 'upcoming': + $defaultlookahead = CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD; + if (isset($CFG->calendar_lookahead)) { + $defaultlookahead = intval($CFG->calendar_lookahead); + } + $lookahead = get_user_preferences('calendar_lookahead', $defaultlookahead); - $defaultmaxevents = CALENDAR_DEFAULT_UPCOMING_MAXEVENTS; - if (isset($CFG->calendar_maxevents)) { - $defaultmaxevents = intval($CFG->calendar_maxevents); - } - $maxevents = get_user_preferences('calendar_maxevents', $defaultmaxevents); - echo $renderer->show_upcoming_events($calendar, $lookahead, $maxevents); - break; -} - -//Link to calendar export page. -echo $OUTPUT->container_start('bottom'); -if (!empty($CFG->enablecalendarexport)) { - echo $OUTPUT->single_button(new moodle_url('export.php', array('course'=>$courseid)), get_string('exportcalendar', 'calendar')); - if (calendar_user_can_add_event($course)) { - echo $OUTPUT->single_button(new moodle_url('/calendar/managesubscriptions.php', array('course'=>$courseid)), get_string('managesubscriptions', 'calendar')); + $defaultmaxevents = CALENDAR_DEFAULT_UPCOMING_MAXEVENTS; + if (isset($CFG->calendar_maxevents)) { + $defaultmaxevents = intval($CFG->calendar_maxevents); + } + $maxevents = get_user_preferences('calendar_maxevents', $defaultmaxevents); + echo $renderer->show_upcoming_events($calendar, $lookahead, $maxevents); + break; } - if (isloggedin()) { - $authtoken = sha1($USER->id . $DB->get_field('user', 'password', array('id' => $USER->id)) . $CFG->calendar_exportsalt); - $link = new moodle_url( - '/calendar/export_execute.php', - array('preset_what'=>'all', 'preset_time' => 'recentupcoming', 'userid' => $USER->id, 'authtoken'=>$authtoken) - ); - echo html_writer::tag('a', 'iCal', - array('href' => $link, 'title' => get_string('quickdownloadcalendar', 'calendar'), 'class' => 'ical-link m-l-1')); - } -} -echo $OUTPUT->container_end(); + //Link to calendar export page. + echo $OUTPUT->container_start('bottom'); + if (!empty($CFG->enablecalendarexport)) { + echo $OUTPUT->single_button(new moodle_url('export.php', array('course'=>$courseid)), get_string('exportcalendar', 'calendar')); + if (calendar_user_can_add_event($course)) { + $manageurl = new moodle_url('/calendar/managesubscriptions.php', ['course' => $courseid]); + echo $OUTPUT->single_button($manageurl, get_string('managesubscriptions', 'calendar')); + } + if (isloggedin()) { + $authtoken = sha1($USER->id . $DB->get_field('user', 'password', array('id' => $USER->id)) . $CFG->calendar_exportsalt); + $link = new moodle_url( + '/calendar/export_execute.php', + array('preset_what'=>'all', 'preset_time' => 'recentupcoming', 'userid' => $USER->id, 'authtoken'=>$authtoken) + ); + echo html_writer::tag('a', 'iCal', + array('href' => $link, 'title' => get_string('quickdownloadcalendar', 'calendar'), 'class' => 'ical-link m-l-1')); + } + } + + echo $OUTPUT->container_end(); +} else if ($view == 'month') { + list($data, $template) = calendar_get_view($calendar, $view); + echo $renderer->render_from_template($template, $data); +} echo html_writer::end_tag('div'); echo $renderer->complete_layout(); $PAGE->requires->js_call_amd('core_calendar/calendar', 'init'); diff --git a/theme/boost/scss/moodle/calendar.scss b/theme/boost/scss/moodle/calendar.scss index 3a3de0798a0..47632cd7803 100644 --- a/theme/boost/scss/moodle/calendar.scss +++ b/theme/boost/scss/moodle/calendar.scss @@ -11,7 +11,7 @@ $calendarEventUserColor: #dce7ec !default; // Pale blue. background-color: $calendarEventCourseColor; } -.calendar_event_global { +.calendar_event_site { background-color: $calendarEventGlobalColor; } @@ -119,7 +119,7 @@ $calendarEventUserColor: #dce7ec !default; // Pale blue. } .calendar_event_course, - .calendar_event_global, + .calendar_event_site, .calendar_event_group, .calendar_event_user { border-width: 1px 1px 1px 12px; @@ -130,7 +130,7 @@ $calendarEventUserColor: #dce7ec !default; // Pale blue. border-color: $calendarEventCourseColor; } - .calendar_event_global { + .calendar_event_site { border-color: $calendarEventGlobalColor; } diff --git a/theme/bootstrapbase/less/moodle/calendar.less b/theme/bootstrapbase/less/moodle/calendar.less index cbb29dc26fe..447fef65d3c 100644 --- a/theme/bootstrapbase/less/moodle/calendar.less +++ b/theme/bootstrapbase/less/moodle/calendar.less @@ -10,7 +10,7 @@ .calendar_event_course { background-color: @calendarEventCourseColor; } -.calendar_event_global { +.calendar_event_site { background-color: @calendarEventGlobalColor; } .calendar_event_group { @@ -109,7 +109,7 @@ margin: 10px auto; } .calendar_event_course, - .calendar_event_global, + .calendar_event_site, .calendar_event_group, .calendar_event_user { border-width: 1px 1px 1px 12px; @@ -118,7 +118,7 @@ .calendar_event_course { border-color: @calendarEventCourseColor; } - .calendar_event_global { + .calendar_event_site { border-color: @calendarEventGlobalColor; } .calendar_event_group { diff --git a/theme/bootstrapbase/style/moodle.css b/theme/bootstrapbase/style/moodle.css index 066dc473b30..6407793764d 100644 --- a/theme/bootstrapbase/style/moodle.css +++ b/theme/bootstrapbase/style/moodle.css @@ -5542,7 +5542,7 @@ img.iconsmall { .calendar_event_course { background-color: #ffd3bd; } -.calendar_event_global { +.calendar_event_site { background-color: #d6f8cd; } .calendar_event_group { @@ -5630,7 +5630,7 @@ img.iconsmall { margin: 10px auto; } .path-calendar .maincalendar .calendar_event_course, -.path-calendar .maincalendar .calendar_event_global, +.path-calendar .maincalendar .calendar_event_site, .path-calendar .maincalendar .calendar_event_group, .path-calendar .maincalendar .calendar_event_user { border-width: 1px 1px 1px 12px; @@ -5639,7 +5639,7 @@ img.iconsmall { .path-calendar .maincalendar .calendar_event_course { border-color: #ffd3bd; } -.path-calendar .maincalendar .calendar_event_global { +.path-calendar .maincalendar .calendar_event_site { border-color: #d6f8cd; } .path-calendar .maincalendar .calendar_event_group { From 695c5726b8ad4c7c89a7fa04852173abd60154b9 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 19 Jul 2017 09:39:34 +0800 Subject: [PATCH 08/17] MDL-59388 calendar: Add AJAX Monthly navigation --- calendar/amd/build/calendar.min.js | 6 +- calendar/amd/build/repository.min.js | 2 +- calendar/amd/build/view_manager.min.js | 1 + calendar/amd/src/calendar.js | 7 ++- calendar/amd/src/repository.js | 23 +++++++- calendar/amd/src/view_manager.js | 81 ++++++++++++++++++++++++++ calendar/externallib.php | 66 ++++++++++++++++++++- lib/db/services.php | 10 ++++ version.php | 3 +- 9 files changed, 192 insertions(+), 7 deletions(-) create mode 100644 calendar/amd/build/view_manager.min.js create mode 100644 calendar/amd/src/view_manager.js diff --git a/calendar/amd/build/calendar.min.js b/calendar/amd/build/calendar.min.js index f21a625b11c..8f2a8220fde 100644 --- a/calendar/amd/build/calendar.min.js +++ b/calendar/amd/build/calendar.min.js @@ -1 +1,5 @@ -define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events"],function(a,b,c,d,e,f,g,h,i,j,k,l){var m={ROOT:"[data-region='calendar']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']"},n=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},o=function(a){return c.get_string("subsource","core_calendar",a).then(function(b){return a.url?''+b+"":b})},p=function(b){k.getEventById(b).then(function(c){if(!c.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+b);var d=c.event,e=n(d.eventtype);if(d.displayeventsource){d.subscription=JSON.parse(d.subscription);var f={url:d.subscription.url,name:d.subscription.name},g=o(f);return a.when(e,g).then(function(a,b){return d.eventtype=a,d.source=b,d})}return e.then(function(a){return d.eventtype=a,d})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete}};return h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},q=function(a){var b=a.find(m.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}},b)},r=function(b,c){var d=a("body");d.on(l.created,function(){window.location.reload()}),d.on(l.deleted,function(){window.location.reload()}),d.on(l.updated,function(){window.location.reload()}),d.on(l.editActionEvent,function(a,b){window.location.assign(b)}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()})})},s=function(){var b=a(m.ROOT);a(m.EVENT_LINK).click(function(b){b.preventDefault();var c=a(this).attr("data-event-id");p(c)});var c=q(b);r(b,c)};return{init:function(){s()}}}); \ No newline at end of file +<<<<<<< d92c4aa6682300fc7cf86db748a9d1826c115e50 +define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events"],function(a,b,c,d,e,f,g,h,i,j,k,l){var m={ROOT:"[data-region='calendar']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']"},n=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},o=function(a){return c.get_string("subsource","core_calendar",a).then(function(b){return a.url?''+b+"":b})},p=function(b){k.getEventById(b).then(function(c){if(!c.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+b);var d=c.event,e=n(d.eventtype);if(d.displayeventsource){d.subscription=JSON.parse(d.subscription);var f={url:d.subscription.url,name:d.subscription.name},g=o(f);return a.when(e,g).then(function(a,b){return d.eventtype=a,d.source=b,d})}return e.then(function(a){return d.eventtype=a,d})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete}};return h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},q=function(a){var b=a.find(m.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}},b)},r=function(b,c){var d=a("body");d.on(l.created,function(){window.location.reload()}),d.on(l.deleted,function(){window.location.reload()}),d.on(l.updated,function(){window.location.reload()}),d.on(l.editActionEvent,function(a,b){window.location.assign(b)}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()})})},s=function(){var b=a(m.ROOT);a(m.EVENT_LINK).click(function(b){b.preventDefault();var c=a(this).attr("data-event-id");p(c)});var c=q(b);r(b,c)};return{init:function(){s()}}}); +======= +define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events","core_calendar/view_manager"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){var n={ROOT:"[data-region='calendar']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']"},o=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},p=function(a){return c.get_string("subsource","core_calendar",a).then(function(b){return a.url?''+b+"":b})},q=function(b){k.getEventById(b).then(function(c){if(!c.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+b);var d=c.event,e=o(d.eventtype);if(d.displayeventsource){d.subscription=JSON.parse(d.subscription);var f={url:d.subscription.url,name:d.subscription.name},g=p(f);return a.when(e,g).then(function(a,b){return d.eventtype=a,d.source=b,d})}return e.then(function(a){return d.eventtype=a,d})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a)};return a.caneditevent||(b.footer=""),h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},r=function(a){var b=a.find(n.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}},b)},s=function(b,c){var d=a("body");d.on(l.created,function(){window.location.reload()}),d.on(l.deleted,function(){window.location.reload()}),d.on(l.updated,function(){window.location.reload()}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()})})},t=function(){var b=a(n.ROOT);a(n.EVENT_LINK).click(function(b){b.preventDefault();var c=a(this).attr("data-event-id");q(c)});var c=r(b);s(b,c)};return{init:function(){m.init(),t()}}}); +>>>>>>> MDL-59388 calendar: Add AJAX Monthly navigation diff --git a/calendar/amd/build/repository.min.js b/calendar/amd/build/repository.min.js index 27c0538ff93..8cc9d4bf65a 100644 --- a/calendar/amd/build/repository.min.js +++ b/calendar/amd/build/repository.min.js @@ -1 +1 @@ -define(["jquery","core/ajax"],function(a,b){var c=function(a){var c={methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:a,repeat:1}]}};return b.call([c])[0]},d=function(a){var c={methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:a}};return b.call([c])[0]},e=function(a){var c={methodname:"core_calendar_submit_create_update_form",args:{formdata:a}};return b.call([c])[0]};return{getEventById:d,deleteEvent:c,submitCreateUpdateForm:e}}); \ No newline at end of file +define(["jquery","core/ajax"],function(a,b){var c=function(a){var c={methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:a,repeat:1}]}};return b.call([c])[0]},d=function(a){var c={methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:a}};return b.call([c])[0]},e=function(a){var c={methodname:"core_calendar_submit_create_update_form",args:{formdata:a}};return b.call([c])[0]},f=function(a,c){var d={methodname:"core_calendar_get_calendar_monthly_view",args:{time:a,courseid:c}};return b.call([d])[0]};return{getEventById:d,deleteEvent:c,submitCreateUpdateForm:e,getCalendarMonthData:f}}); \ No newline at end of file diff --git a/calendar/amd/build/view_manager.min.js b/calendar/amd/build/view_manager.min.js new file mode 100644 index 00000000000..603721779f4 --- /dev/null +++ b/calendar/amd/build/view_manager.min.js @@ -0,0 +1 @@ +define(["jquery","core/templates","core/notification","core_calendar/repository"],function(a,b,c,d){var e={ROOT:"[data-region='calendar']",CALENDAR_NAV_LINK:"span.calendarwrapper .arrow_link",CALENDAR_MONTH_WRAPPER:".calendarwrapper"},f=function(b){b=a(b),a(b).on("click",e.CALENDAR_NAV_LINK,function(c){var d=a(b).find(e.CALENDAR_MONTH_WRAPPER).data("courseid"),f=a(c.currentTarget);g(f.attr("href"),f.data("time"),d),c.preventDefault()})},g=function(f,g,h){d.getCalendarMonthData(g,h).then(function(a){return window.history.pushState({},"Some new title",f),b.render("core_calendar/month_detailed",a)}).then(function(c,d){return a(e.CALENDAR_MONTH_WRAPPER).replaceWith(c),b.runTemplateJS(d)}).then(function(){}).fail(c.exception)};return{init:function(){f(e.ROOT)}}}); \ No newline at end of file diff --git a/calendar/amd/src/calendar.js b/calendar/amd/src/calendar.js index a08f1c3c0cc..c90a04ff390 100644 --- a/calendar/amd/src/calendar.js +++ b/calendar/amd/src/calendar.js @@ -37,7 +37,8 @@ define([ 'core_calendar/modal_event_form', 'core_calendar/summary_modal', 'core_calendar/repository', - 'core_calendar/events' + 'core_calendar/events', + 'core_calendar/view_manager' ], function( $, @@ -51,7 +52,8 @@ define([ ModalEventForm, SummaryModal, CalendarRepository, - CalendarEvents + CalendarEvents, + CalendarViewManager ) { var SELECTORS = { @@ -234,6 +236,7 @@ define([ return { init: function() { + CalendarViewManager.init(); registerEventListeners(); } }; diff --git a/calendar/amd/src/repository.js b/calendar/amd/src/repository.js index 0f96079b24c..9b4e5f5dfb7 100644 --- a/calendar/amd/src/repository.js +++ b/calendar/amd/src/repository.js @@ -83,9 +83,30 @@ define(['jquery', 'core/ajax'], function($, Ajax) { return Ajax.call([request])[0]; }; + /** + * Get calendar data for the month view. + * + * @method getCalendarMonthData + * @param {Number} time Timestamp. + * @param {Number} courseid The course id. + * @return {promise} Resolved with the month view data. + */ + var getCalendarMonthData = function (time, courseid) { + var request = { + methodname: 'core_calendar_get_calendar_monthly_view', + args: { + time: time, + courseid: courseid, + } + }; + + return Ajax.call([request])[0]; + }; + return { getEventById: getEventById, deleteEvent: deleteEvent, - submitCreateUpdateForm: submitCreateUpdateForm + submitCreateUpdateForm: submitCreateUpdateForm, + getCalendarMonthData: getCalendarMonthData }; }); diff --git a/calendar/amd/src/view_manager.js b/calendar/amd/src/view_manager.js new file mode 100644 index 00000000000..d8de28132c8 --- /dev/null +++ b/calendar/amd/src/view_manager.js @@ -0,0 +1,81 @@ +// 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 . + +/** + * A javascript module to handler calendar view changes. + * + * @module core_calendar/view_manager + * @package core_calendar + * @copyright 2017 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define(['jquery', 'core/templates', 'core/notification', 'core_calendar/repository'], + function($, Templates, Notification, CalendarRepository) { + + var SELECTORS = { + ROOT: "[data-region='calendar']", + CALENDAR_NAV_LINK: "span.calendarwrapper .arrow_link", + CALENDAR_MONTH_WRAPPER: ".calendarwrapper", + }; + + /** + * Register event listeners for the module. + * + * @param {object} root The root element. + */ + var registerEventListeners = function(root) { + root = $(root); + + $(root).on('click', SELECTORS.CALENDAR_NAV_LINK, function(e) { + var courseId = $(root).find(SELECTORS.CALENDAR_MONTH_WRAPPER).data('courseid'); + var link = $(e.currentTarget); + changeMonth(link.attr('href'), link.data('time'), courseId); + + e.preventDefault(); + }); + }; + + + /** + * Handle changes to the current calendar view. + * + * @param {Number} time The calendar time to be shown + * @param {Number} courseid The id of the course whose events are shown + */ + var changeMonth = function(url, time, courseid) { + CalendarRepository.getCalendarMonthData(time, courseid) + .then(function(context) { + // TODO Fetch the page title from somewhere..? + window.history.pushState({}, 'Some new title', url); + + return Templates.render('core_calendar/month_detailed', context); + }) + .then(function(html, js) { + $(SELECTORS.CALENDAR_MONTH_WRAPPER).replaceWith(html); + + return Templates.runTemplateJS(js); + }) + .then(function() { + // TODO Fire an event to say the month changed. + }) + .fail(Notification.exception); + }; + + return { + init: function() { + registerEventListeners(SELECTORS.ROOT); + } + }; + }); diff --git a/calendar/externallib.php b/calendar/externallib.php index 2356f9eabf5..e9657dbc91d 100644 --- a/calendar/externallib.php +++ b/calendar/externallib.php @@ -705,6 +705,7 @@ class core_calendar_external extends external_api { ) ); } + /** * Get calendar event by id. * @@ -745,7 +746,7 @@ class core_calendar_external extends external_api { * * @return external_description */ - public static function get_calendar_event_by_id_returns() { + public static function get_calendar_event_by_id_returns() { $eventstructure = event_exporter::get_read_structure(); return new external_single_structure(array( @@ -844,4 +845,67 @@ class core_calendar_external extends external_api { ) ); } + + /** + * Get data for the monthly calendar view. + * + * @param int $time The time to be shown + * @param int $courseid The course to be included + * @return array + */ + public static function get_calendar_monthly_view($time, $courseid) { + global $CFG, $DB, $USER, $PAGE; + require_once($CFG->dirroot."/calendar/lib.php"); + + $warnings = []; + + // Parameter validation. + $params = self::validate_parameters(self::get_calendar_monthly_view_parameters(), [ + 'time' => $time, + 'courseid' => $courseid, + ]); + + if ($courseid != SITEID && !empty($courseid)) { + // Course ID must be valid and existing. + $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); + $courses = [$course->id => $course]; + } else { + $course = get_site(); + $courses = calendar_get_default_courses(); + } + + // TODO: Copy what we do in calendar/view.php. + $context = \context_user::instance($USER->id); + self::validate_context($context); + + $calendar = new calendar_information(0, 0, 0, $time); + $calendar->prepare_for_view($course, $courses); + + list($data, $template) = calendar_get_view($calendar, 'month'); + + return $data; + } + + /** + * Returns description of method parameters. + * + * @return external_function_parameters + */ + public static function get_calendar_monthly_view_parameters() { + return new external_function_parameters( + [ + 'time' => new external_value(PARAM_INT, 'Time to be viewed', VALUE_REQUIRED, '', NULL_NOT_ALLOWED), + 'courseid' => new external_value(PARAM_INT, 'Course being viewed', VALUE_DEFAULT, SITEID, NULL_ALLOWED), + ] + ); + } + + /** + * Returns description of method result value. + * + * @return external_description + */ + public static function get_calendar_monthly_view_returns() { + return \core_calendar\external\month_exporter::get_read_structure(); + } } diff --git a/lib/db/services.php b/lib/db/services.php index 909b7fafccd..e37d54e7747 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -58,6 +58,16 @@ $functions = array( 'capabilities' => 'moodle/badges:viewotherbadges', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), + 'core_calendar_get_calendar_monthly_view' => array( + 'classname' => 'core_calendar_external', + 'methodname' => 'get_calendar_monthly_view', + 'description' => 'Fetch the monthly view data for a calendar', + 'classpath' => 'calendar/externallib.php', + 'type' => 'read', + 'capabilities' => '', + 'ajax' => true, + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), + ), 'core_calendar_create_calendar_events' => array( 'classname' => 'core_calendar_external', 'methodname' => 'create_calendar_events', diff --git a/version.php b/version.php index 945c926e430..d475fe8a5db 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,8 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2017081000.00; // YYYYMMDD = weekly release date of this DEV branch. + +$version = 2017081000.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. From 6124ec1947ce04ffd6d04fdfb883e99a37cfdede Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Wed, 19 Jul 2017 08:58:11 +0800 Subject: [PATCH 09/17] MDL-59388 core_calendar: add calendar_get_footer_options() method --- calendar/lib.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/calendar/lib.php b/calendar/lib.php index 7cb8c821df6..df1f08d692f 100644 --- a/calendar/lib.php +++ b/calendar/lib.php @@ -3625,3 +3625,24 @@ function calendar_get_timestamp($d, $m, $y, $time = 0) { return $time; } + +/** + * Get the calendar footer options. + * + * @param calendar_information $calendar The calendar information object. + * @return array The data for template and template name. + */ +function calendar_get_footer_options($calendar) { + global $CFG, $USER, $DB, $PAGE; + + // Generate hash for iCal link. + $rawhash = $USER->id . $DB->get_field('user', 'password', ['id' => $USER->id]) . $CFG->calendar_exportsalt; + $authtoken = sha1($rawhash); + + $renderer = $PAGE->get_renderer('core_calendar'); + $footer = new \core_calendar\external\footer_options_exporter($calendar, $USER->id, $authtoken); + $data = $footer->export($renderer); + $template = 'core_calendar/footer_options'; + + return [$data, $template]; +} From 86775ced8994658048b13d9ff553d9b5bf0a30be Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Wed, 19 Jul 2017 08:59:11 +0800 Subject: [PATCH 10/17] MDL-59388 core_calendar: new footer options exporter --- .../external/footer_options_exporter.php | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 calendar/classes/external/footer_options_exporter.php diff --git a/calendar/classes/external/footer_options_exporter.php b/calendar/classes/external/footer_options_exporter.php new file mode 100644 index 00000000000..4024576b7ce --- /dev/null +++ b/calendar/classes/external/footer_options_exporter.php @@ -0,0 +1,142 @@ +. + +/** + * Class for exporting calendar footer view options data. + * + * @package core_calendar + * @copyright 2017 Simey Lameze + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_calendar\external; + +defined('MOODLE_INTERNAL') || die(); + +use core\external\exporter; +use renderer_base; +use stdClass; +use moodle_url; + +/** + * Class for exporting calendar footer view options data. + * + * @copyright 2017 Simey Lameze + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class footer_options_exporter extends exporter { + + /** + * @var \calendar_information $calendar The calendar to be rendered. + */ + protected $calendar; + + /** + * @var int $userid The user id. + */ + protected $userid; + + /** + * @var string $token The user sha1 token. + */ + protected $token; + + /** + * Constructor for month_exporter. + * + * @param \calendar_information $calendar The calendar being represented + */ + public function __construct(\calendar_information $calendar, $userid, $token) { + $this->calendar = $calendar; + $this->userid = $userid; + $this->token = $token; + } + + /** + * Get the export calendar button. + * + * @param renderer_base $output + * @return string The export calendar button html. + */ + protected function get_export_calendar_button(renderer_base $output) { + $exportcalendarurl = new moodle_url('/calendar/export.php', ['course' => $this->calendar->course->id]); + return $output->single_button($exportcalendarurl->out(false), get_string('exportcalendar', 'calendar')); + } + + /** + * Get the iCal url. + * + * @return string The iCal url. + */ + protected function get_ical_url() { + return new moodle_url('/calendar/export_execute.php', ['preset_what' => 'all', + 'preset_time' => 'recentupcoming', 'userid' => $this->userid, 'authtoken'=> $this->token]); + + } + + /** + * Get manage subscription button. + * + * @param renderer_base $output + * @return string The manage subscription button html. + */ + protected function get_manage_subscriptions_button(renderer_base $output) { + if (calendar_user_can_add_event($this->calendar->course)) { + $managesubscriptionurl = new moodle_url('/calendar/managesubscriptions.php', + ['course' => $this->calendar->course->id]); + return $output->single_button($managesubscriptionurl->out(false), + get_string('managesubscriptions', 'calendar')); + } + } + + /** + * Get the additional values to inject while exporting. + * + * @param renderer_base $output The renderer. + * @return array Keys are the property names, values are their values. + */ + protected function get_other_values(renderer_base $output) { + global $CFG; + + $values = new stdClass(); + + if (!empty($CFG->enablecalendarexport)) { + $values->exportcalendarbutton = $this->get_export_calendar_button($output); + $values->managesubscriptionbutton = $this->get_manage_subscriptions_button($output); + $values->icalurl = $this->get_ical_url($output)->out(false); + } + + return (array) $values; + } + + /** + * Return the list of additional properties. + * + * @return array + */ + public static function define_other_properties() { + return array( + 'exportcalendarbutton' => [ + 'type' => PARAM_RAW, + ], + 'managesubscriptionbutton' => [ + 'type' => PARAM_RAW, + ], + 'icalurl' => [ + 'type' => PARAM_URL, + ], + ); + } +} From 644b2e982e77a3be99c74038d63a260fcf35a4f5 Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Wed, 19 Jul 2017 09:04:19 +0800 Subject: [PATCH 11/17] MDL-59388 core_calendar: display calendar footer options --- calendar/templates/footer_options.mustache | 33 ++++++++++++++++++++++ calendar/view.php | 26 +++-------------- 2 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 calendar/templates/footer_options.mustache diff --git a/calendar/templates/footer_options.mustache b/calendar/templates/footer_options.mustache new file mode 100644 index 00000000000..d5371505caf --- /dev/null +++ b/calendar/templates/footer_options.mustache @@ -0,0 +1,33 @@ +{{! + 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 . +}} +{{! + @template core_calendar/footer_options + + Displays export options on the calendar footer. + + Example context (json): + { + "exportcalendarbutton": "", + "managesubscriptionbutton": "", + "icalurl": "http://example.com/" + } +}} +
    + {{{exportcalendarbutton}}} + {{{managesubscriptionbutton}}} + iCal +
    diff --git a/calendar/view.php b/calendar/view.php index fdf3cb05ff8..ba0bc947c6d 100644 --- a/calendar/view.php +++ b/calendar/view.php @@ -152,32 +152,14 @@ if ($view == 'day' || $view == 'upcoming') { echo $renderer->show_upcoming_events($calendar, $lookahead, $maxevents); break; } - - //Link to calendar export page. - echo $OUTPUT->container_start('bottom'); - if (!empty($CFG->enablecalendarexport)) { - echo $OUTPUT->single_button(new moodle_url('export.php', array('course'=>$courseid)), get_string('exportcalendar', 'calendar')); - if (calendar_user_can_add_event($course)) { - $manageurl = new moodle_url('/calendar/managesubscriptions.php', ['course' => $courseid]); - echo $OUTPUT->single_button($manageurl, get_string('managesubscriptions', 'calendar')); - } - if (isloggedin()) { - $authtoken = sha1($USER->id . $DB->get_field('user', 'password', array('id' => $USER->id)) . $CFG->calendar_exportsalt); - $link = new moodle_url( - '/calendar/export_execute.php', - array('preset_what'=>'all', 'preset_time' => 'recentupcoming', 'userid' => $USER->id, 'authtoken'=>$authtoken) - ); - echo html_writer::tag('a', 'iCal', - array('href' => $link, 'title' => get_string('quickdownloadcalendar', 'calendar'), 'class' => 'ical-link m-l-1')); - } - } - - echo $OUTPUT->container_end(); } else if ($view == 'month') { list($data, $template) = calendar_get_view($calendar, $view); echo $renderer->render_from_template($template, $data); } echo html_writer::end_tag('div'); -echo $renderer->complete_layout(); + +list($data, $template) = calendar_get_footer_options($calendar); +echo $renderer->render_from_template($template, $data); + $PAGE->requires->js_call_amd('core_calendar/calendar', 'init'); echo $OUTPUT->footer(); From a4af4c9633d877058898d61d129222b267b65166 Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Tue, 8 Aug 2017 08:11:31 +0800 Subject: [PATCH 12/17] MDL-59388 core_calendar: fix cibot complains --- calendar/amd/build/calendar.min.js | 6 +- calendar/amd/build/events.min.js | 2 +- calendar/amd/build/view_manager.min.js | 2 +- calendar/amd/src/event_form.js | 2 +- calendar/amd/src/events.js | 3 +- calendar/amd/src/repository.js | 4 +- calendar/amd/src/view_manager.js | 13 ++-- calendar/classes/external/day_exporter.php | 68 ++++++++++++---- .../classes/external/day_name_exporter.php | 42 ++++++++++ .../external/footer_options_exporter.php | 4 +- calendar/classes/external/month_exporter.php | 77 ++++++++++++++++--- calendar/classes/external/week_exporter.php | 64 +++++++++++++-- calendar/lib.php | 13 ++-- calendar/templates/month_detailed.mustache | 33 ++++++++ calendar/templates/month_header.mustache | 33 ++++++++ calendar/templates/month_navigation.mustache | 33 ++++++++ 16 files changed, 343 insertions(+), 56 deletions(-) diff --git a/calendar/amd/build/calendar.min.js b/calendar/amd/build/calendar.min.js index 8f2a8220fde..82a01d50d67 100644 --- a/calendar/amd/build/calendar.min.js +++ b/calendar/amd/build/calendar.min.js @@ -1,5 +1 @@ -<<<<<<< d92c4aa6682300fc7cf86db748a9d1826c115e50 -define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events"],function(a,b,c,d,e,f,g,h,i,j,k,l){var m={ROOT:"[data-region='calendar']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']"},n=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},o=function(a){return c.get_string("subsource","core_calendar",a).then(function(b){return a.url?''+b+"":b})},p=function(b){k.getEventById(b).then(function(c){if(!c.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+b);var d=c.event,e=n(d.eventtype);if(d.displayeventsource){d.subscription=JSON.parse(d.subscription);var f={url:d.subscription.url,name:d.subscription.name},g=o(f);return a.when(e,g).then(function(a,b){return d.eventtype=a,d.source=b,d})}return e.then(function(a){return d.eventtype=a,d})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete}};return h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},q=function(a){var b=a.find(m.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}},b)},r=function(b,c){var d=a("body");d.on(l.created,function(){window.location.reload()}),d.on(l.deleted,function(){window.location.reload()}),d.on(l.updated,function(){window.location.reload()}),d.on(l.editActionEvent,function(a,b){window.location.assign(b)}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()})})},s=function(){var b=a(m.ROOT);a(m.EVENT_LINK).click(function(b){b.preventDefault();var c=a(this).attr("data-event-id");p(c)});var c=q(b);r(b,c)};return{init:function(){s()}}}); -======= -define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events","core_calendar/view_manager"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){var n={ROOT:"[data-region='calendar']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']"},o=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},p=function(a){return c.get_string("subsource","core_calendar",a).then(function(b){return a.url?''+b+"":b})},q=function(b){k.getEventById(b).then(function(c){if(!c.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+b);var d=c.event,e=o(d.eventtype);if(d.displayeventsource){d.subscription=JSON.parse(d.subscription);var f={url:d.subscription.url,name:d.subscription.name},g=p(f);return a.when(e,g).then(function(a,b){return d.eventtype=a,d.source=b,d})}return e.then(function(a){return d.eventtype=a,d})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a)};return a.caneditevent||(b.footer=""),h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},r=function(a){var b=a.find(n.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}},b)},s=function(b,c){var d=a("body");d.on(l.created,function(){window.location.reload()}),d.on(l.deleted,function(){window.location.reload()}),d.on(l.updated,function(){window.location.reload()}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()})})},t=function(){var b=a(n.ROOT);a(n.EVENT_LINK).click(function(b){b.preventDefault();var c=a(this).attr("data-event-id");q(c)});var c=r(b);s(b,c)};return{init:function(){m.init(),t()}}}); ->>>>>>> MDL-59388 calendar: Add AJAX Monthly navigation +define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events","core_calendar/view_manager"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){var n={ROOT:"[data-region='calendar']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']"},o=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},p=function(a){return c.get_string("subsource","core_calendar",a).then(function(b){return a.url?''+b+"":b})},q=function(b){k.getEventById(b).then(function(c){if(!c.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+b);var d=c.event,e=o(d.eventtype);if(d.displayeventsource){d.subscription=JSON.parse(d.subscription);var f={url:d.subscription.url,name:d.subscription.name},g=p(f);return a.when(e,g).then(function(a,b){return d.eventtype=a,d.source=b,d})}return e.then(function(a){return d.eventtype=a,d})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete}};return h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},r=function(a){var b=a.find(n.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}},b)},s=function(b,c){var d=a("body");d.on(l.created,function(){window.location.reload()}),d.on(l.deleted,function(){window.location.reload()}),d.on(l.updated,function(){window.location.reload()}),d.on(l.editActionEvent,function(a,b){window.location.assign(b)}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()})})},t=function(){var b=a(n.ROOT);a(n.EVENT_LINK).click(function(b){b.preventDefault();var c=a(this).attr("data-event-id");q(c)});var c=r(b);s(b,c)};return{init:function(){m.init(),t()}}}); \ No newline at end of file diff --git a/calendar/amd/build/events.min.js b/calendar/amd/build/events.min.js index 0c310d9f996..ca56cb1ec42 100644 --- a/calendar/amd/build/events.min.js +++ b/calendar/amd/build/events.min.js @@ -1 +1 @@ -define([],function(){return{created:"calendar-events:created",deleted:"calendar-events:deleted",updated:"calendar-events:updated",editEvent:"calendar-events:edit_event",editActionEvent:"calendar-events:edit_action_event"}}); \ No newline at end of file +define([],function(){return{created:"calendar-events:created",deleted:"calendar-events:deleted",updated:"calendar-events:updated",editEvent:"calendar-events:edit_event",editActionEvent:"calendar-events:edit_action_event",monthChanged:"calendar-events:month_changed"}}); \ No newline at end of file diff --git a/calendar/amd/build/view_manager.min.js b/calendar/amd/build/view_manager.min.js index 603721779f4..2942c8961f0 100644 --- a/calendar/amd/build/view_manager.min.js +++ b/calendar/amd/build/view_manager.min.js @@ -1 +1 @@ -define(["jquery","core/templates","core/notification","core_calendar/repository"],function(a,b,c,d){var e={ROOT:"[data-region='calendar']",CALENDAR_NAV_LINK:"span.calendarwrapper .arrow_link",CALENDAR_MONTH_WRAPPER:".calendarwrapper"},f=function(b){b=a(b),a(b).on("click",e.CALENDAR_NAV_LINK,function(c){var d=a(b).find(e.CALENDAR_MONTH_WRAPPER).data("courseid"),f=a(c.currentTarget);g(f.attr("href"),f.data("time"),d),c.preventDefault()})},g=function(f,g,h){d.getCalendarMonthData(g,h).then(function(a){return window.history.pushState({},"Some new title",f),b.render("core_calendar/month_detailed",a)}).then(function(c,d){return a(e.CALENDAR_MONTH_WRAPPER).replaceWith(c),b.runTemplateJS(d)}).then(function(){}).fail(c.exception)};return{init:function(){f(e.ROOT)}}}); \ No newline at end of file +define(["jquery","core/templates","core/notification","core_calendar/repository","core_calendar/events"],function(a,b,c,d,e){var f={ROOT:"[data-region='calendar']",CALENDAR_NAV_LINK:"span.calendarwrapper .arrow_link",CALENDAR_MONTH_WRAPPER:".calendarwrapper"},g=function(b){b=a(b),a(b).on("click",f.CALENDAR_NAV_LINK,function(c){var d=a(b).find(f.CALENDAR_MONTH_WRAPPER).data("courseid"),e=a(c.currentTarget);h(e.attr("href"),e.data("time"),d),c.preventDefault()})},h=function(g,h,i){d.getCalendarMonthData(h,i).then(function(a){return window.history.pushState({},"",g),b.render("core_calendar/month_detailed",a)}).then(function(c,d){return a(f.CALENDAR_MONTH_WRAPPER).replaceWith(c),b.runTemplateJS(d)}).done(function(){a("body").trigger(e.monthChanged,[])}).fail(c.exception)};return{init:function(){g(f.ROOT)}}}); \ No newline at end of file diff --git a/calendar/amd/src/event_form.js b/calendar/amd/src/event_form.js index e62ee638950..0a4e33e6727 100644 --- a/calendar/amd/src/event_form.js +++ b/calendar/amd/src/event_form.js @@ -249,7 +249,7 @@ define(['jquery', 'core/templates'], function($, Templates) { }; /** - * Initialise all of the form enhancementds. + * Initialise all of the form enhancements. * * @method init * @param {string} formId The value of the form's id attribute diff --git a/calendar/amd/src/events.js b/calendar/amd/src/events.js index 47aac12ec26..465e3370f0e 100644 --- a/calendar/amd/src/events.js +++ b/calendar/amd/src/events.js @@ -28,6 +28,7 @@ define([], function() { deleted: 'calendar-events:deleted', updated: 'calendar-events:updated', editEvent: 'calendar-events:edit_event', - editActionEvent: 'calendar-events:edit_action_event' + editActionEvent: 'calendar-events:edit_action_event', + monthChanged: 'calendar-events:month_changed' }; }); diff --git a/calendar/amd/src/repository.js b/calendar/amd/src/repository.js index 9b4e5f5dfb7..5c95483886a 100644 --- a/calendar/amd/src/repository.js +++ b/calendar/amd/src/repository.js @@ -91,12 +91,12 @@ define(['jquery', 'core/ajax'], function($, Ajax) { * @param {Number} courseid The course id. * @return {promise} Resolved with the month view data. */ - var getCalendarMonthData = function (time, courseid) { + var getCalendarMonthData = function(time, courseid) { var request = { methodname: 'core_calendar_get_calendar_monthly_view', args: { time: time, - courseid: courseid, + courseid: courseid } }; diff --git a/calendar/amd/src/view_manager.js b/calendar/amd/src/view_manager.js index d8de28132c8..8a243cf76da 100644 --- a/calendar/amd/src/view_manager.js +++ b/calendar/amd/src/view_manager.js @@ -21,8 +21,8 @@ * @copyright 2017 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery', 'core/templates', 'core/notification', 'core_calendar/repository'], - function($, Templates, Notification, CalendarRepository) { +define(['jquery', 'core/templates', 'core/notification', 'core_calendar/repository', 'core_calendar/events'], + function($, Templates, Notification, CalendarRepository, CalendarEvents) { var SELECTORS = { ROOT: "[data-region='calendar']", @@ -47,18 +47,17 @@ define(['jquery', 'core/templates', 'core/notification', 'core_calendar/reposito }); }; - /** * Handle changes to the current calendar view. * + * @param {String} url The calendar url to be shown * @param {Number} time The calendar time to be shown * @param {Number} courseid The id of the course whose events are shown */ var changeMonth = function(url, time, courseid) { CalendarRepository.getCalendarMonthData(time, courseid) .then(function(context) { - // TODO Fetch the page title from somewhere..? - window.history.pushState({}, 'Some new title', url); + window.history.pushState({}, '', url); return Templates.render('core_calendar/month_detailed', context); }) @@ -67,8 +66,8 @@ define(['jquery', 'core/templates', 'core/notification', 'core_calendar/reposito return Templates.runTemplateJS(js); }) - .then(function() { - // TODO Fire an event to say the month changed. + .done(function() { + $('body').trigger(CalendarEvents.monthChanged, []); }) .fail(Notification.exception); }; diff --git a/calendar/classes/external/day_exporter.php b/calendar/classes/external/day_exporter.php index 827b3025d3c..2f4efc0d65d 100644 --- a/calendar/classes/external/day_exporter.php +++ b/calendar/classes/external/day_exporter.php @@ -1,13 +1,49 @@ . + +/** + * Contains event class for displaying the day view. + * + * @package core_calendar + * @copyright 2017 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ namespace core_calendar\external; +defined('MOODLE_INTERNAL') || die(); + use core\external\exporter; use renderer_base; use moodle_url; +/** + * Class for displaying the day view. + * + * @package core_calendar + * @copyright 2017 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class day_exporter extends exporter { + /** + * Return the list of properties. + * + * @return array + */ protected static function define_properties() { // These are the default properties as returned by getuserdate() // but without the formatted month and week names. @@ -36,6 +72,11 @@ class day_exporter extends exporter { ]; } + /** + * Return the list of additional properties. + * + * @return array + */ protected static function define_other_properties() { return [ 'timestamp' => [ @@ -56,24 +97,17 @@ class day_exporter extends exporter { 'events' => [ 'type' => event_exporter::read_properties_definition(), 'multiple' => true, - ], - 'viewdaylink' => [ - 'type' => PARAM_URL, - ], - - //'viewdaylink' => $this->viewdaylink->out(false), - //'createeventlink' => $this->createeventlink, - //'viewdaylinktitle' => $this->get_title($renderer), - //'events' => $this->get_events($renderer), - //'hasevents' => !empty($this->events), - //'eventtypes' => array_unique($this->eventtypes), - //'eventcount' => count($this->events), - //'durationevents' => array_unique($this->durationevents), + ] ]; } + /** + * Get the additional values to inject while exporting. + * + * @param renderer_base $output The renderer. + * @return array Keys are the property names, values are their values. + */ protected function get_other_values(renderer_base $output) { - //$events = new events_exporter($this->related['events'], $this->related); $return = [ 'timestamp' => $this->data[0], ]; @@ -84,7 +118,6 @@ class day_exporter extends exporter { ]); $return['viewdaylink'] = $url->out(false); - $cache = $this->related['cache']; $return['events'] = array_map(function($event) use ($cache, $output, $url) { $context = $cache->get_context($event); @@ -101,6 +134,11 @@ class day_exporter extends exporter { return $return; } + /** + * Returns a list of objects that are related. + * + * @return array + */ protected static function define_related() { return [ 'events' => '\core_calendar\local\event\entities\event_interface[]', diff --git a/calendar/classes/external/day_name_exporter.php b/calendar/classes/external/day_name_exporter.php index afa17c78f43..665cc55df50 100644 --- a/calendar/classes/external/day_name_exporter.php +++ b/calendar/classes/external/day_name_exporter.php @@ -1,11 +1,42 @@ . + +/** + * Contains event class for displaying the day name. + * + * @package core_calendar + * @copyright 2017 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ namespace core_calendar\external; +defined('MOODLE_INTERNAL') || die(); + use core\external\exporter; use renderer_base; use moodle_url; +/** + * Class for displaying the day view. + * + * @package core_calendar + * @copyright 2017 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class day_name_exporter extends exporter { /** @@ -23,12 +54,23 @@ class day_name_exporter extends exporter { */ protected $fullname; + /** + * Constructor. + * + * @param int $dayno The day number. + * @param array $names The list of names. + */ public function __construct($dayno, $names) { $data = $names + ['dayno' => $dayno]; parent::__construct($data, []); } + /** + * Return the list of properties. + * + * @return array + */ protected static function define_properties() { return [ 'dayno' => [ diff --git a/calendar/classes/external/footer_options_exporter.php b/calendar/classes/external/footer_options_exporter.php index 4024576b7ce..cc9ae2276cc 100644 --- a/calendar/classes/external/footer_options_exporter.php +++ b/calendar/classes/external/footer_options_exporter.php @@ -57,6 +57,8 @@ class footer_options_exporter extends exporter { * Constructor for month_exporter. * * @param \calendar_information $calendar The calendar being represented + * @param int $userid The user id + * @param string $token The user sha1 token. */ public function __construct(\calendar_information $calendar, $userid, $token) { $this->calendar = $calendar; @@ -82,7 +84,7 @@ class footer_options_exporter extends exporter { */ protected function get_ical_url() { return new moodle_url('/calendar/export_execute.php', ['preset_what' => 'all', - 'preset_time' => 'recentupcoming', 'userid' => $this->userid, 'authtoken'=> $this->token]); + 'preset_time' => 'recentupcoming', 'userid' => $this->userid, 'authtoken' => $this->token]); } diff --git a/calendar/classes/external/month_exporter.php b/calendar/classes/external/month_exporter.php index 701a2fd1e16..cf1d1bd69fb 100644 --- a/calendar/classes/external/month_exporter.php +++ b/calendar/classes/external/month_exporter.php @@ -1,11 +1,42 @@ . + +/** + * Contains event class for displaying the month view. + * + * @package core_calendar + * @copyright 2017 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ namespace core_calendar\external; +defined('MOODLE_INTERNAL') || die(); + use core\external\exporter; use renderer_base; use moodle_url; +/** + * Class for displaying the month view. + * + * @package core_calendar + * @copyright 2017 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class month_exporter extends exporter { /** @@ -48,6 +79,11 @@ class month_exporter extends exporter { parent::__construct([], $related); } + /** + * Return the list of additional properties. + * + * @return array + */ protected static function define_other_properties() { return [ 'courseid' => [ @@ -79,6 +115,12 @@ class month_exporter extends exporter { ]; } + /** + * Get the additional values to inject while exporting. + * + * @param renderer_base $output The renderer. + * @return array Keys are the property names, values are their values. + */ protected function get_other_values(renderer_base $output) { return [ 'courseid' => $this->calendar->courseid, @@ -94,10 +136,9 @@ class month_exporter extends exporter { /** * Get the course filter selector. - * TODO Convert to new exporter? * - * @param renderer_base $output - * return string + * @param renderer_base $output + * @return string The html code for the course filter selector. */ protected function get_course_filter_selector(renderer_base $output) { $content = ''; @@ -110,11 +151,10 @@ class month_exporter extends exporter { } /** - * Get the course filter selector. - * TODO Convert to new exporter? + * Get the calendar navigation controls. * - * @param renderer_base $output - * return string + * @param renderer_base $output + * @return string The html code to the calendar top navigation. */ protected function get_navigation(renderer_base $output) { return calendar_top_controls('month', [ @@ -149,8 +189,8 @@ class month_exporter extends exporter { * Get the list of week days, ordered into weeks and padded according * to the value of the first day of the week. * - * @param renderer_base $output - * @return array + * @param renderer_base $output + * @return array The list of weeks. */ protected function get_weeks(renderer_base $output) { $weeks = []; @@ -185,7 +225,7 @@ class month_exporter extends exporter { /** * Get the list of days with the matching date array. * - * @return array + * @return array */ protected function get_days() { $date = $this->related['type']->timestamp_to_date_array($this->calendar->time); @@ -202,6 +242,11 @@ class month_exporter extends exporter { return $days; } + /** + * Returns a list of objects that are related. + * + * @return array + */ protected static function define_related() { return [ 'events' => '\core_calendar\local\event\entities\event_interface[]', @@ -210,17 +255,29 @@ class month_exporter extends exporter { ]; } + /** + * Get the previous month timestamp. + * + * @return int The previous month timestamp. + */ protected function get_previous_month_timestamp() { $date = $this->related['type']->timestamp_to_date_array($this->calendar->time); $month = calendar_sub_month($date['mon'], $date['year']); $monthtime = $this->related['type']->convert_to_gregorian($month[1], $month[0], 1); + return make_timestamp($monthtime['year'], $monthtime['month'], $monthtime['day'], $monthtime['hour'], $monthtime['minute']); } + /** + * Get the next month timestamp. + * + * @return int The next month timestamp. + */ protected function get_next_month_timestamp() { $date = $this->related['type']->timestamp_to_date_array($this->calendar->time); $month = calendar_sub_month($date['mon'], $date['year']); $monthtime = $this->related['type']->convert_to_gregorian($month[1], $month[0], 1); + return make_timestamp($monthtime['year'], $monthtime['month'], $monthtime['day'], $monthtime['hour'], $monthtime['minute']); } } diff --git a/calendar/classes/external/week_exporter.php b/calendar/classes/external/week_exporter.php index c8427cece56..c7412800d3d 100644 --- a/calendar/classes/external/week_exporter.php +++ b/calendar/classes/external/week_exporter.php @@ -1,11 +1,42 @@ . + +/** + * Contains event class for displaying the week view. + * + * @package core_calendar + * @copyright 2017 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ namespace core_calendar\external; +defined('MOODLE_INTERNAL') || die(); + use core\external\exporter; use renderer_base; use moodle_url; +/** + * Class for displaying the week view. + * + * @package core_calendar + * @copyright 2017 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class week_exporter extends exporter { /** @@ -14,17 +45,23 @@ class week_exporter extends exporter { protected $days = []; /** - * @var int $prepadding The number of pre-padding days at the start of - * the week. + * @var int $prepadding The number of pre-padding days at the start of the week. */ protected $prepadding = 0; /** - * @var int $postpadding The number of post-padding days at the start of - * the week. + * @var int $postpadding The number of post-padding days at the start of the week. */ protected $postpadding = 0; + /** + * Constructor. + * + * @param mixed $days An array of day_exporter objects. + * @param int $prepadding The number of pre-padding days at the start of the week. + * @param int $postpadding The number of post-padding days at the start of the week. + * @param array $related Related objects. + */ public function __construct($days, $prepadding, $postpadding, $related) { $this->days = $days; $this->prepadding = $prepadding; @@ -33,6 +70,11 @@ class week_exporter extends exporter { parent::__construct([], $related); } + /** + * Return the list of additional properties. + * + * @return array + */ protected static function define_other_properties() { return [ 'prepadding' => [ @@ -50,6 +92,12 @@ class week_exporter extends exporter { ]; } + /** + * Get the additional values to inject while exporting. + * + * @param renderer_base $output The renderer. + * @return array Keys are the property names, values are their values. + */ protected function get_other_values(renderer_base $output) { $return = [ 'prepadding' => [], @@ -82,22 +130,26 @@ class week_exporter extends exporter { // Ends before today. continue; } - $events[] = $event; } - $day = new day_exporter($daydata, [ 'events' => $events, 'cache' => $this->related['cache'], 'type' => $this->related['type'], ]); + $return['days'][] = $day->export($output); } return $return; } + /** + * Returns a list of objects that are related. + * + * @return array + */ protected static function define_related() { return [ 'events' => '\core_calendar\local\event\entities\event_interface[]', diff --git a/calendar/lib.php b/calendar/lib.php index df1f08d692f..56b44d107d5 100644 --- a/calendar/lib.php +++ b/calendar/lib.php @@ -3431,7 +3431,7 @@ function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses, $ * @return array[array, string] */ function calendar_get_view(\calendar_information $calendar, $view) { - global $PAGE, $DB, $OUTPUT; + global $PAGE, $CFG; $renderer = $PAGE->get_renderer('core_calendar'); $type = \core_calendar\type_factory::get_calendar_instance(); @@ -3444,7 +3444,11 @@ function calendar_get_view(\calendar_information $calendar, $view) { $tend = $tstart + DAYSECS - 1; $selectortitle = get_string('dayviewfor', 'calendar'); } else if ($view === 'upcoming') { - $defaultlookahead = isset($CFG->calendar_lookahead) ? intval($CFG->calendar_lookahead) : CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD; + if (isset($CFG->calendar_lookahead)) { + $defaultlookahead = intval($CFG->calendar_lookahead); + } else { + $defaultlookahead = CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD; + } $tend = $tstart + get_user_preferences('calendar_lookahead', $defaultlookahead); $selectortitle = get_string('upcomingeventsfor', 'calendar'); } else { @@ -3503,9 +3507,7 @@ function calendar_get_view(\calendar_information $calendar, $view) { 'cache' => new \core_calendar\external\events_related_objects_cache($events), ]; - if ($view === 'day') { - // TODO: Export the days events in a new exporter. - } else if ($view === 'upcoming') { + if ($view === 'upcoming') { $defaultlookahead = CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD; if (isset($CFG->calendar_lookahead)) { $defaultlookahead = intval($CFG->calendar_lookahead); @@ -3517,7 +3519,6 @@ function calendar_get_view(\calendar_information $calendar, $view) { $defaultmaxevents = intval($CFG->calendar_maxevents); } $maxevents = get_user_preferences('calendar_maxevents', $defaultmaxevents); - // TODO: Export the upcoming events in a new exporter. } else { $month = new \core_calendar\external\month_exporter($calendar, $type, $related); $data = $month->export($renderer); diff --git a/calendar/templates/month_detailed.mustache b/calendar/templates/month_detailed.mustache index de10bc2827e..6ad4b29ab3e 100644 --- a/calendar/templates/month_detailed.mustache +++ b/calendar/templates/month_detailed.mustache @@ -1,3 +1,36 @@ +{{! + 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 . +}} +{{! + @template calendar/month_detailed + + Calendar month view. + + The purpose of this template is to render the month view. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Example context (json): + { + } +}} {{> core_calendar/month_header }} {{> core_calendar/month_navigation }} diff --git a/calendar/templates/month_header.mustache b/calendar/templates/month_header.mustache index d5ce533a85f..56d6e4934e4 100644 --- a/calendar/templates/month_header.mustache +++ b/calendar/templates/month_header.mustache @@ -1,3 +1,36 @@ +{{! + 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 . +}} +{{! + @template calendar/month_header + + Calendar month header. + + The purpose of this template is to render the month header. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Example context (json): + { + } +}} {{#filter_selector}}
    {{{filter_selector}}} diff --git a/calendar/templates/month_navigation.mustache b/calendar/templates/month_navigation.mustache index 481021eeee3..d7d350955a4 100644 --- a/calendar/templates/month_navigation.mustache +++ b/calendar/templates/month_navigation.mustache @@ -1,3 +1,36 @@ +{{! + 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 . +}} +{{! + @template calendar/month_navigation + + Calendar month navigation. + + The purpose of this template is to render the navigation to switch to previous and next months. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Example context (json): + { + } +}} {{#navigation}}
    {{{navigation}}} From 836aa3f64a604e9289dbfc68a143ac8809c3f3c0 Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Tue, 8 Aug 2017 13:11:42 +0800 Subject: [PATCH 13/17] MDL-59388 core_calendar: fix month view on clean theme --- calendar/amd/build/view_manager.min.js | 2 +- calendar/amd/src/view_manager.js | 9 +++------ calendar/classes/external/day_exporter.php | 2 +- calendar/externallib.php | 2 -- calendar/lib.php | 22 ++++------------------ calendar/templates/month_detailed.mustache | 4 ++-- 6 files changed, 11 insertions(+), 30 deletions(-) diff --git a/calendar/amd/build/view_manager.min.js b/calendar/amd/build/view_manager.min.js index 2942c8961f0..384490f57d8 100644 --- a/calendar/amd/build/view_manager.min.js +++ b/calendar/amd/build/view_manager.min.js @@ -1 +1 @@ -define(["jquery","core/templates","core/notification","core_calendar/repository","core_calendar/events"],function(a,b,c,d,e){var f={ROOT:"[data-region='calendar']",CALENDAR_NAV_LINK:"span.calendarwrapper .arrow_link",CALENDAR_MONTH_WRAPPER:".calendarwrapper"},g=function(b){b=a(b),a(b).on("click",f.CALENDAR_NAV_LINK,function(c){var d=a(b).find(f.CALENDAR_MONTH_WRAPPER).data("courseid"),e=a(c.currentTarget);h(e.attr("href"),e.data("time"),d),c.preventDefault()})},h=function(g,h,i){d.getCalendarMonthData(h,i).then(function(a){return window.history.pushState({},"",g),b.render("core_calendar/month_detailed",a)}).then(function(c,d){return a(f.CALENDAR_MONTH_WRAPPER).replaceWith(c),b.runTemplateJS(d)}).done(function(){a("body").trigger(e.monthChanged,[])}).fail(c.exception)};return{init:function(){g(f.ROOT)}}}); \ No newline at end of file +define(["jquery","core/templates","core/notification","core_calendar/repository","core_calendar/events"],function(a,b,c,d,e){var f={ROOT:"[data-region='calendar']",CALENDAR_NAV_LINK:"span.calendarwrapper .arrow_link",CALENDAR_MONTH_WRAPPER:".calendarwrapper"},g=function(b){b=a(b),b.on("click",f.CALENDAR_NAV_LINK,function(c){var d=a(b).find(f.CALENDAR_MONTH_WRAPPER).data("courseid"),e=a(c.currentTarget);h(e.attr("href"),e.data("time"),d),c.preventDefault()})},h=function(g,h,i){d.getCalendarMonthData(h,i).then(function(a){return window.history.pushState({},"",g),b.render("core_calendar/month_detailed",a)}).then(function(a,c){return b.replaceNodeContents(f.CALENDAR_MONTH_WRAPPER,a,c)}).done(function(){a("body").trigger(e.monthChanged,[h,i])}).fail(c.exception)};return{init:function(){g(f.ROOT)}}}); \ No newline at end of file diff --git a/calendar/amd/src/view_manager.js b/calendar/amd/src/view_manager.js index 8a243cf76da..6eb6269fbf8 100644 --- a/calendar/amd/src/view_manager.js +++ b/calendar/amd/src/view_manager.js @@ -38,7 +38,7 @@ define(['jquery', 'core/templates', 'core/notification', 'core_calendar/reposito var registerEventListeners = function(root) { root = $(root); - $(root).on('click', SELECTORS.CALENDAR_NAV_LINK, function(e) { + root.on('click', SELECTORS.CALENDAR_NAV_LINK, function(e) { var courseId = $(root).find(SELECTORS.CALENDAR_MONTH_WRAPPER).data('courseid'); var link = $(e.currentTarget); changeMonth(link.attr('href'), link.data('time'), courseId); @@ -58,16 +58,13 @@ define(['jquery', 'core/templates', 'core/notification', 'core_calendar/reposito CalendarRepository.getCalendarMonthData(time, courseid) .then(function(context) { window.history.pushState({}, '', url); - return Templates.render('core_calendar/month_detailed', context); }) .then(function(html, js) { - $(SELECTORS.CALENDAR_MONTH_WRAPPER).replaceWith(html); - - return Templates.runTemplateJS(js); + return Templates.replaceNodeContents(SELECTORS.CALENDAR_MONTH_WRAPPER, html, js); }) .done(function() { - $('body').trigger(CalendarEvents.monthChanged, []); + $('body').trigger(CalendarEvents.monthChanged, [time, courseid]); }) .fail(Notification.exception); }; diff --git a/calendar/classes/external/day_exporter.php b/calendar/classes/external/day_exporter.php index 2f4efc0d65d..5cac152723a 100644 --- a/calendar/classes/external/day_exporter.php +++ b/calendar/classes/external/day_exporter.php @@ -95,7 +95,7 @@ class day_exporter extends exporter { 'optional' => true, ], 'events' => [ - 'type' => event_exporter::read_properties_definition(), + 'type' => calendar_event_exporter::read_properties_definition(), 'multiple' => true, ] ]; diff --git a/calendar/externallib.php b/calendar/externallib.php index e9657dbc91d..b8ba417c083 100644 --- a/calendar/externallib.php +++ b/calendar/externallib.php @@ -857,8 +857,6 @@ class core_calendar_external extends external_api { global $CFG, $DB, $USER, $PAGE; require_once($CFG->dirroot."/calendar/lib.php"); - $warnings = []; - // Parameter validation. $params = self::validate_parameters(self::get_calendar_monthly_view_parameters(), [ 'time' => $time, diff --git a/calendar/lib.php b/calendar/lib.php index 56b44d107d5..bb61015ecc6 100644 --- a/calendar/lib.php +++ b/calendar/lib.php @@ -3424,7 +3424,7 @@ function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses, $ /** - * Get theh calendar view output. + * Get the calendar view output. * * @param \calendar_information $calendar The calendar being represented * @param string $view The type of calendar to have displayed @@ -3507,23 +3507,9 @@ function calendar_get_view(\calendar_information $calendar, $view) { 'cache' => new \core_calendar\external\events_related_objects_cache($events), ]; - if ($view === 'upcoming') { - $defaultlookahead = CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD; - if (isset($CFG->calendar_lookahead)) { - $defaultlookahead = intval($CFG->calendar_lookahead); - } - $lookahead = get_user_preferences('calendar_lookahead', $defaultlookahead); - - $defaultmaxevents = CALENDAR_DEFAULT_UPCOMING_MAXEVENTS; - if (isset($CFG->calendar_maxevents)) { - $defaultmaxevents = intval($CFG->calendar_maxevents); - } - $maxevents = get_user_preferences('calendar_maxevents', $defaultmaxevents); - } else { - $month = new \core_calendar\external\month_exporter($calendar, $type, $related); - $data = $month->export($renderer); - $template = 'core_calendar/month_detailed'; - } + $month = new \core_calendar\external\month_exporter($calendar, $type, $related); + $data = $month->export($renderer); + $template = 'core_calendar/month_detailed'; return [$data, $template]; } diff --git a/calendar/templates/month_detailed.mustache b/calendar/templates/month_detailed.mustache index 6ad4b29ab3e..106d81b91bc 100644 --- a/calendar/templates/month_detailed.mustache +++ b/calendar/templates/month_detailed.mustache @@ -34,7 +34,7 @@ {{> core_calendar/month_header }} {{> core_calendar/month_navigation }} - +
    {{# daynames }} @@ -79,7 +79,7 @@ {{/events.0}} -
    +
    {{#events.0}} {{mday}} {{/events.0}} From 6a7ad2cb211d5a172557120a8c00c20b418e6132 Mon Sep 17 00:00:00 2001 From: Ryan Wyllie Date: Mon, 14 Aug 2017 09:17:59 +0800 Subject: [PATCH 14/17] MDL-59388 core_calendar: setUpTrigger needs to handle arrays --- calendar/amd/build/calendar.min.js | 2 +- calendar/amd/src/calendar.js | 4 ++-- lib/amd/build/modal_factory.min.js | 2 +- lib/amd/src/modal_factory.js | 21 ++++++++++++++++----- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/calendar/amd/build/calendar.min.js b/calendar/amd/build/calendar.min.js index 82a01d50d67..24a58102010 100644 --- a/calendar/amd/build/calendar.min.js +++ b/calendar/amd/build/calendar.min.js @@ -1 +1 @@ -define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events","core_calendar/view_manager"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){var n={ROOT:"[data-region='calendar']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']"},o=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},p=function(a){return c.get_string("subsource","core_calendar",a).then(function(b){return a.url?''+b+"":b})},q=function(b){k.getEventById(b).then(function(c){if(!c.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+b);var d=c.event,e=o(d.eventtype);if(d.displayeventsource){d.subscription=JSON.parse(d.subscription);var f={url:d.subscription.url,name:d.subscription.name},g=p(f);return a.when(e,g).then(function(a,b){return d.eventtype=a,d.source=b,d})}return e.then(function(a){return d.eventtype=a,d})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete}};return h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},r=function(a){var b=a.find(n.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}},b)},s=function(b,c){var d=a("body");d.on(l.created,function(){window.location.reload()}),d.on(l.deleted,function(){window.location.reload()}),d.on(l.updated,function(){window.location.reload()}),d.on(l.editActionEvent,function(a,b){window.location.assign(b)}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()})})},t=function(){var b=a(n.ROOT);a(n.EVENT_LINK).click(function(b){b.preventDefault();var c=a(this).attr("data-event-id");q(c)});var c=r(b);s(b,c)};return{init:function(){m.init(),t()}}}); \ No newline at end of file +define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events","core_calendar/view_manager"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){var n={ROOT:"[data-region='calendar']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']"},o=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},p=function(a){return c.get_string("subsource","core_calendar",a).then(function(b){return a.url?''+b+"":b})},q=function(b){k.getEventById(b).then(function(c){if(!c.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+b);var d=c.event,e=o(d.eventtype);if(d.displayeventsource){d.subscription=JSON.parse(d.subscription);var f={url:d.subscription.url,name:d.subscription.name},g=p(f);return a.when(e,g).then(function(a,b){return d.eventtype=a,d.source=b,d})}return e.then(function(a){return d.eventtype=a,d})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete}};return h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},r=function(a){var b=a.find(n.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}},[a,n.NEW_EVENT_BUTTON])},s=function(b,c){var d=a("body");d.on(l.created,function(){window.location.reload()}),d.on(l.deleted,function(){window.location.reload()}),d.on(l.updated,function(){window.location.reload()}),d.on(l.editActionEvent,function(a,b){window.location.assign(b)}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()})})},t=function(){var b=a(n.ROOT);b.on("click",n.EVENT_LINK,function(b){b.preventDefault();var c=a(this).attr("data-event-id");q(c)});var c=r(b);s(b,c)};return{init:function(){m.init(),t()}}}); \ No newline at end of file diff --git a/calendar/amd/src/calendar.js b/calendar/amd/src/calendar.js index c90a04ff390..570c8c62a3e 100644 --- a/calendar/amd/src/calendar.js +++ b/calendar/amd/src/calendar.js @@ -175,7 +175,7 @@ define([ contextid: contextId } }, - newEventButton + [root, SELECTORS.NEW_EVENT_BUTTON] ); }; @@ -224,7 +224,7 @@ define([ var root = $(SELECTORS.ROOT); // Bind click events to event links. - $(SELECTORS.EVENT_LINK).click(function(e) { + root.on('click', SELECTORS.EVENT_LINK, function(e) { e.preventDefault(); var eventId = $(this).attr('data-event-id'); renderEventSummaryModal(eventId); diff --git a/lib/amd/build/modal_factory.min.js b/lib/amd/build/modal_factory.min.js index 06f8a5f41d9..efa71d9cba8 100644 --- a/lib/amd/build/modal_factory.min.js +++ b/lib/amd/build/modal_factory.min.js @@ -1 +1 @@ -define(["jquery","core/modal_events","core/modal_registry","core/modal","core/modal_save_cancel","core/modal_confirm","core/modal_cancel","core/templates","core/notification","core/custom_interaction_events"],function(a,b,c,d,e,f,g,h,i,j){var k={DEFAULT:"core/modal",SAVE_CANCEL:"core/modal_save_cancel",CONFIRM:"core/modal_confirm",CANCEL:"core/modal_cancel"},l={DEFAULT:"DEFAULT",SAVE_CANCEL:"SAVE_CANCEL",CONFIRM:"CONFIRM",CANCEL:"CANCEL"};c.register(l.DEFAULT,d,k.DEFAULT),c.register(l.SAVE_CANCEL,e,k.SAVE_CANCEL),c.register(l.CONFIRM,f,k.CONFIRM),c.register(l.CANCEL,g,k.CANCEL);var m=function(a,c){"undefined"!=typeof c&&(j.define(c,[j.events.activate]),c.on(j.events.activate,function(b,c){a.show(),c.originalEvent.preventDefault()}),a.getRoot().on(b.hidden,function(){c.focus()}))},n=function(b,c,d){c=a(c);var e=b.module,f=new e(c);return m(f,d),f},o=function(b,c,d){var e=b.template;return h.render(e,c).then(function(c){var e=a(c);return n(b,e,d)}).fail(i.exception)},p=function(a,b){var d=a.type||l.DEFAULT,e=!!a.large,f=null,g={};return f=c.get(d),f||i.exception({message:"Unable to find modal of type: "+d}),"undefined"!=typeof a.templateContext&&(g=a.templateContext),o(f,g,b).then(function(b){return"undefined"!=typeof a.title&&b.setTitle(a.title),"undefined"!=typeof a.body&&b.setBody(a.body),"undefined"!=typeof a.footer&&b.setFooter(a.footer),e&&b.setLarge(),b})};return{create:p,types:l}}); \ No newline at end of file +define(["jquery","core/modal_events","core/modal_registry","core/modal","core/modal_save_cancel","core/modal_confirm","core/modal_cancel","core/templates","core/notification","core/custom_interaction_events"],function(a,b,c,d,e,f,g,h,i,j){var k={DEFAULT:"core/modal",SAVE_CANCEL:"core/modal_save_cancel",CONFIRM:"core/modal_confirm",CANCEL:"core/modal_cancel"},l={DEFAULT:"DEFAULT",SAVE_CANCEL:"SAVE_CANCEL",CONFIRM:"CONFIRM",CANCEL:"CANCEL"};c.register(l.DEFAULT,d,k.DEFAULT),c.register(l.SAVE_CANCEL,e,k.SAVE_CANCEL),c.register(l.CONFIRM,f,k.CONFIRM),c.register(l.CANCEL,g,k.CANCEL);var m=function(a,c){if("undefined"!=typeof c){if(Array.isArray(c)){var d=c[1];c=c[0],j.define(c,[j.events.activate]),c.on(j.events.activate,d,function(b,c){a.show(),c.originalEvent.preventDefault()})}else j.define(c,[j.events.activate]),c.on(j.events.activate,function(b,c){a.show(),c.originalEvent.preventDefault()});a.getRoot().on(b.hidden,function(){c.focus()})}},n=function(b,c,d){c=a(c);var e=b.module,f=new e(c);return m(f,d),f},o=function(b,c,d){var e=b.template;return h.render(e,c).then(function(c){var e=a(c);return n(b,e,d)}).fail(i.exception)},p=function(a,b){var d=a.type||l.DEFAULT,e=!!a.large,f=null,g={};return f=c.get(d),f||i.exception({message:"Unable to find modal of type: "+d}),"undefined"!=typeof a.templateContext&&(g=a.templateContext),o(f,g,b).then(function(b){return"undefined"!=typeof a.title&&b.setTitle(a.title),"undefined"!=typeof a.body&&b.setBody(a.body),"undefined"!=typeof a.footer&&b.setFooter(a.footer),e&&b.setLarge(),b})};return{create:p,types:l}}); \ No newline at end of file diff --git a/lib/amd/src/modal_factory.js b/lib/amd/src/modal_factory.js index 52839b138c3..678bad88d33 100644 --- a/lib/amd/src/modal_factory.js +++ b/lib/amd/src/modal_factory.js @@ -60,11 +60,22 @@ define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal', */ var setUpTrigger = function(modal, triggerElement) { if (typeof triggerElement != 'undefined') { - CustomEvents.define(triggerElement, [CustomEvents.events.activate]); - triggerElement.on(CustomEvents.events.activate, function(e, data) { - modal.show(); - data.originalEvent.preventDefault(); - }); + if (Array.isArray(triggerElement)) { + var selector = triggerElement[1]; + triggerElement = triggerElement[0]; + + CustomEvents.define(triggerElement, [CustomEvents.events.activate]); + triggerElement.on(CustomEvents.events.activate, selector, function(e, data) { + modal.show(); + data.originalEvent.preventDefault(); + }); + } else { + CustomEvents.define(triggerElement, [CustomEvents.events.activate]); + triggerElement.on(CustomEvents.events.activate, function(e, data) { + modal.show(); + data.originalEvent.preventDefault(); + }); + } modal.getRoot().on(ModalEvents.hidden, function() { triggerElement.focus(); From 12eef11c8fdbe059df777be293efbedff9ccbe3a Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Tue, 15 Aug 2017 17:08:31 +0800 Subject: [PATCH 15/17] MDL-59388 calendar: footer_options fixes --- .../external/footer_options_exporter.php | 10 +-- calendar/templates/footer_options.mustache | 4 +- lib/templates/single_button.mustache | 61 +++++++++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 lib/templates/single_button.mustache diff --git a/calendar/classes/external/footer_options_exporter.php b/calendar/classes/external/footer_options_exporter.php index cc9ae2276cc..a5e6e4df798 100644 --- a/calendar/classes/external/footer_options_exporter.php +++ b/calendar/classes/external/footer_options_exporter.php @@ -69,12 +69,11 @@ class footer_options_exporter extends exporter { /** * Get the export calendar button. * - * @param renderer_base $output - * @return string The export calendar button html. + * @return \single_button The export calendar button html. */ - protected function get_export_calendar_button(renderer_base $output) { + protected function get_export_calendar_button() { $exportcalendarurl = new moodle_url('/calendar/export.php', ['course' => $this->calendar->course->id]); - return $output->single_button($exportcalendarurl->out(false), get_string('exportcalendar', 'calendar')); + return new \single_button($exportcalendarurl, get_string('exportcalendar', 'calendar')); } /** @@ -115,7 +114,8 @@ class footer_options_exporter extends exporter { $values = new stdClass(); if (!empty($CFG->enablecalendarexport)) { - $values->exportcalendarbutton = $this->get_export_calendar_button($output); + $exportbutton = $this->get_export_calendar_button(); + $values->exportcalendarbutton = $exportbutton->export_for_template($output); $values->managesubscriptionbutton = $this->get_manage_subscriptions_button($output); $values->icalurl = $this->get_ical_url($output)->out(false); } diff --git a/calendar/templates/footer_options.mustache b/calendar/templates/footer_options.mustache index d5371505caf..67e302e84d7 100644 --- a/calendar/templates/footer_options.mustache +++ b/calendar/templates/footer_options.mustache @@ -27,7 +27,9 @@ } }}
    - {{{exportcalendarbutton}}} + {{#exportcalendarbutton}} + {{> core/single_button }} + {{/exportcalendarbutton}} {{{managesubscriptionbutton}}} iCal
    diff --git a/lib/templates/single_button.mustache b/lib/templates/single_button.mustache new file mode 100644 index 00000000000..9eca305943e --- /dev/null +++ b/lib/templates/single_button.mustache @@ -0,0 +1,61 @@ +{{! + 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 . +}} +{{! + @template core/single_button + + Moodle template for a single button submit form. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * classes - a list of classes to wrap the form. + * method - get or post + * url - the action url to submit to + * formid - optional id value for the form + * params - array of params with name and value attributes + * primary - true if this is a primary action button + * id - id for the element + * tooltip - tooltip text for the button + * disabled - true if this element is disabled + * label - text to show on the button + + Example context (json): + { "method" : "get", + "url" : "#", + "primary" : true, + "tooltip" : "This is a tooltip", + "label" : "This is a the button text" + } +}} +
    +
    + {{#params}} + + {{/params}} + + +
    +{{#hasactions}} + {{> core/actions }} +{{/hasactions}} From bd33760372331728d66b552ae0d362d813ac9ebf Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Wed, 16 Aug 2017 09:44:27 +0800 Subject: [PATCH 16/17] MDL-59388 core_calendar: remove empty lines and unused imports --- calendar/classes/external/calendar_event_exporter.php | 3 --- calendar/classes/external/day_name_exporter.php | 2 -- calendar/classes/external/event_exporter.php | 3 --- calendar/classes/external/footer_options_exporter.php | 2 +- calendar/classes/external/week_exporter.php | 1 - version.php | 1 - 6 files changed, 1 insertion(+), 11 deletions(-) diff --git a/calendar/classes/external/calendar_event_exporter.php b/calendar/classes/external/calendar_event_exporter.php index e3b53a9afb1..31863901b8a 100644 --- a/calendar/classes/external/calendar_event_exporter.php +++ b/calendar/classes/external/calendar_event_exporter.php @@ -26,9 +26,6 @@ namespace core_calendar\external; defined('MOODLE_INTERNAL') || die(); -use \core\external\exporter; -use \core_calendar\local\event\entities\event_interface; -use \core_calendar\local\event\entities\action_event_interface; use \core_course\external\course_summary_exporter; use \renderer_base; diff --git a/calendar/classes/external/day_name_exporter.php b/calendar/classes/external/day_name_exporter.php index 665cc55df50..41c657f4f35 100644 --- a/calendar/classes/external/day_name_exporter.php +++ b/calendar/classes/external/day_name_exporter.php @@ -27,8 +27,6 @@ namespace core_calendar\external; defined('MOODLE_INTERNAL') || die(); use core\external\exporter; -use renderer_base; -use moodle_url; /** * Class for displaying the day view. diff --git a/calendar/classes/external/event_exporter.php b/calendar/classes/external/event_exporter.php index e20b43e2210..7e4bb7c531d 100644 --- a/calendar/classes/external/event_exporter.php +++ b/calendar/classes/external/event_exporter.php @@ -26,11 +26,8 @@ namespace core_calendar\external; defined('MOODLE_INTERNAL') || die(); - require_once($CFG->dirroot . "/calendar/lib.php"); -use \core\external\exporter; -use \core_calendar\local\event\entities\event_interface; use \core_calendar\local\event\entities\action_event_interface; use \core_calendar\local\event\container; use \core_course\external\course_summary_exporter; diff --git a/calendar/classes/external/footer_options_exporter.php b/calendar/classes/external/footer_options_exporter.php index a5e6e4df798..018970a195c 100644 --- a/calendar/classes/external/footer_options_exporter.php +++ b/calendar/classes/external/footer_options_exporter.php @@ -117,7 +117,7 @@ class footer_options_exporter extends exporter { $exportbutton = $this->get_export_calendar_button(); $values->exportcalendarbutton = $exportbutton->export_for_template($output); $values->managesubscriptionbutton = $this->get_manage_subscriptions_button($output); - $values->icalurl = $this->get_ical_url($output)->out(false); + $values->icalurl = $this->get_ical_url()->out(false); } return (array) $values; diff --git a/calendar/classes/external/week_exporter.php b/calendar/classes/external/week_exporter.php index c7412800d3d..afc92b2f012 100644 --- a/calendar/classes/external/week_exporter.php +++ b/calendar/classes/external/week_exporter.php @@ -28,7 +28,6 @@ defined('MOODLE_INTERNAL') || die(); use core\external\exporter; use renderer_base; -use moodle_url; /** * Class for displaying the week view. diff --git a/version.php b/version.php index d475fe8a5db..fa054589d5e 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,6 @@ defined('MOODLE_INTERNAL') || die(); - $version = 2017081000.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. From 1a1e8ab029c3e555f6f2a361a6f6efceb60a5480 Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Wed, 16 Aug 2017 10:10:48 +0800 Subject: [PATCH 17/17] MDL-59388 core_calendar: convert manage subscription button --- calendar/classes/external/footer_options_exporter.php | 8 ++++---- calendar/templates/footer_options.mustache | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/calendar/classes/external/footer_options_exporter.php b/calendar/classes/external/footer_options_exporter.php index 018970a195c..23bb42f0e42 100644 --- a/calendar/classes/external/footer_options_exporter.php +++ b/calendar/classes/external/footer_options_exporter.php @@ -90,14 +90,13 @@ class footer_options_exporter extends exporter { /** * Get manage subscription button. * - * @param renderer_base $output * @return string The manage subscription button html. */ - protected function get_manage_subscriptions_button(renderer_base $output) { + protected function get_manage_subscriptions_button() { if (calendar_user_can_add_event($this->calendar->course)) { $managesubscriptionurl = new moodle_url('/calendar/managesubscriptions.php', ['course' => $this->calendar->course->id]); - return $output->single_button($managesubscriptionurl->out(false), + return new \single_button($managesubscriptionurl, get_string('managesubscriptions', 'calendar')); } } @@ -115,8 +114,9 @@ class footer_options_exporter extends exporter { if (!empty($CFG->enablecalendarexport)) { $exportbutton = $this->get_export_calendar_button(); + $managesubscriptionbutton = $this->get_manage_subscriptions_button(); $values->exportcalendarbutton = $exportbutton->export_for_template($output); - $values->managesubscriptionbutton = $this->get_manage_subscriptions_button($output); + $values->managesubscriptionbutton = $managesubscriptionbutton->export_for_template($output); $values->icalurl = $this->get_ical_url()->out(false); } diff --git a/calendar/templates/footer_options.mustache b/calendar/templates/footer_options.mustache index 67e302e84d7..05c2941933b 100644 --- a/calendar/templates/footer_options.mustache +++ b/calendar/templates/footer_options.mustache @@ -30,6 +30,8 @@ {{#exportcalendarbutton}} {{> core/single_button }} {{/exportcalendarbutton}} - {{{managesubscriptionbutton}}} + {{#managesubscriptionbutton}} + {{> core/single_button }} + {{/managesubscriptionbutton}} iCal