From 9e95193e9fd0ac9a69ce6c7573c97017e6565034 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 4 Oct 2018 15:57:04 +0800 Subject: [PATCH] MDL-63352 block_timeline: Persist user preference on timeline * Persist sort preference to the user table * Persist filter preferences to the user table * Add behat tests to test persistence * Add the gdpr exports for the user preferences as well as unit tests for the same * Updated view_nav.min --- blocks/timeline/amd/build/view_nav.min.js | Bin 923 -> 1385 bytes blocks/timeline/amd/src/view_nav.js | 48 +++++++- blocks/timeline/block_timeline.php | 5 +- blocks/timeline/classes/output/main.php | 94 ++++++++++++++- blocks/timeline/classes/privacy/provider.php | 38 +++++- blocks/timeline/lang/en/block_timeline.php | 4 +- blocks/timeline/lib.php | 70 +++++++++++ .../templates/nav-day-filter.mustache | 18 ++- .../templates/nav-view-selector.mustache | 12 +- blocks/timeline/templates/view.mustache | 29 ++++- .../behat/block_timeline_courses.feature | 19 +++ .../tests/behat/block_timeline_dates.feature | 33 ++++++ blocks/timeline/tests/privacy_test.php | 110 ++++++++++++++++++ .../block_timeline/nav-day-filter.mustache | 12 +- .../block_timeline/nav-view-selector.mustache | 4 +- .../templates/block_timeline/view.mustache | 29 ++++- 16 files changed, 487 insertions(+), 38 deletions(-) create mode 100644 blocks/timeline/lib.php create mode 100644 blocks/timeline/tests/privacy_test.php diff --git a/blocks/timeline/amd/build/view_nav.min.js b/blocks/timeline/amd/build/view_nav.min.js index c66ad2fab555f6bf493ea355051bfc561bce70bf..09fbfd9400446f7e8679dc722bf7bb1ce2575aaa 100644 GIT binary patch literal 1385 zcmbW1QE!_t5XZkt%S*82xP1npDQc+{DQ%`T+B8KGGUfZ#AM95|a7%#dQ7@czs@OO}X-hz@O9jSMnUs;=9HC zQ}MQgaRmy5GhO}r$zsxqc~0gJ56|)9R25ZJW0U6qLAqNXu0D8{tx(W_wN5S&oUUv|1y638fAZ=Dn z6J|_srTnE>k>%}*h0 zDE26JMQFi{_CH{qap$a$ zXdA~KCn4T*V>hmIJczIM2x;HYry1kCdZ=b_Dz&u)#Kr;)a*~4T| z(-rC?2o-pb!1~Zl%F4CWT*d7&Gjywc-b2o7<$TgKh=}|mBJWm;gdGK5RhSoj)%~)x zVveWYFl%xUth+)j4Wa+Z)p5g}S+5mDBmBe&m+@`O($kK84X5lfPL;9l156nesJcM_ hhi~E0A#K@0Z+-k*BUE;eCbZycs}E937i`ei@E@84-sk`T delta 387 zcmZWl%}T>S7{sPU5IlLXMX*08X3N*LXG@Skt>Q(by@-^u$^K-UHi2XtkP;A|K-r^@ z<9m4b;u{p)T5J`M-^?)c&3x>ZyQx53`I#D#gUOWJIwc0)dU)KLV&;6b_o3Ko7*qHE@WLE0PGN5cVf= z%2H(_7KQp&psQBpN;&~1N+ouK5=B}(iKh(&U;r!rhsMu diff --git a/blocks/timeline/amd/src/view_nav.js b/blocks/timeline/amd/src/view_nav.js index 092dcbd93fe..6fd32a6f7e4 100644 --- a/blocks/timeline/amd/src/view_nav.js +++ b/blocks/timeline/amd/src/view_nav.js @@ -25,12 +25,16 @@ define( [ 'jquery', 'core/custom_interaction_events', - 'block_timeline/view' + 'block_timeline/view', + 'core/ajax', + 'core/notification' ], function( $, CustomEvents, - View + View, + Ajax, + Notification ) { var SELECTORS = { @@ -41,6 +45,29 @@ function( DATA_DAYS_LIMIT: '[data-days-limit]', }; + /** + * Generic handler to persist user preferences + * + * @param {string} type The name of the attribute you're updating + * @param {string} value The value of the attribute you're updating + */ + var updateUserPreferences = function(type, value) { + var request = { + methodname: 'core_user_update_user_preferences', + args: { + preferences: [ + { + type: type, + value: value + } + ] + } + }; + + Ajax.call([request])[0] + .fail(Notification.exception); + }; + /** * Event listener for the day selector ("Next 7 days", "Next 30 days", etc). * @@ -55,6 +82,11 @@ function( CustomEvents.events.activate, SELECTORS.TIMELINE_DAY_FILTER_OPTION, function(e, data) { + // Update the user preference + var filtername = $(e.currentTarget).data('filtername'); + var type = 'block_timeline_user_filter_preference'; + updateUserPreferences(type, filtername); + var option = $(e.target).closest(SELECTORS.TIMELINE_DAY_FILTER_OPTION); if (option.hasClass('active')) { @@ -94,11 +126,21 @@ function( * @param {object} timelineViewRoot The root element for the timeline view */ var registerViewSelector = function(root, timelineViewRoot) { + var viewSelector = root.find(SELECTORS.TIMELINE_VIEW_SELECTOR); + // Listen for when the user changes tab so that we can show the first set of courses // and load their events when they request the sort by courses view for the first time. - root.find(SELECTORS.TIMELINE_VIEW_SELECTOR).on('shown shown.bs.tab', function() { + viewSelector.on('shown shown.bs.tab', function() { View.shown(timelineViewRoot); }); + + // Event selector for user_sort + CustomEvents.define(viewSelector, [CustomEvents.events.activate]); + viewSelector.on(CustomEvents.events.activate, "[data-toggle='tab']", function(e) { + var filtername = $(e.currentTarget).data('filtername'); + var type = 'block_timeline_user_sort_preference'; + updateUserPreferences(type, filtername); + }); }; /** diff --git a/blocks/timeline/block_timeline.php b/blocks/timeline/block_timeline.php index 5a24f9ca98f..03e72303328 100644 --- a/blocks/timeline/block_timeline.php +++ b/blocks/timeline/block_timeline.php @@ -50,7 +50,10 @@ class block_timeline extends block_base { return $this->content; } - $renderable = new \block_timeline\output\main(); + $sort = get_user_preferences('block_timeline_user_sort_preference'); + $filter = get_user_preferences('block_timeline_user_filter_preference'); + + $renderable = new \block_timeline\output\main($sort, $filter); $renderer = $this->page->get_renderer('block_timeline'); $this->content = (object) [ diff --git a/blocks/timeline/classes/output/main.php b/blocks/timeline/classes/output/main.php index b5e96097b22..218eb46f194 100644 --- a/blocks/timeline/classes/output/main.php +++ b/blocks/timeline/classes/output/main.php @@ -30,6 +30,7 @@ use templatable; use core_course\external\course_summary_exporter; require_once($CFG->dirroot . '/course/lib.php'); +require_once($CFG->dirroot . '/blocks/timeline/lib.php'); require_once($CFG->libdir . '/completionlib.php'); /** @@ -43,6 +44,86 @@ class main implements renderable, templatable { /** Number of courses to load per page */ const COURSES_PER_PAGE = 2; + /** + * @var string The current filter preference + */ + public $filter; + + /** + * @var string The current sort/order preference + */ + public $order; + + /** + * main constructor. + * + * @param string $order Constant sort value from ../timeline/lib.php + * @param string $filter Constant sort value from ../timeline/lib.php + */ + public function __construct($order, $filter) { + $this->order = $order ? $order : BLOCK_TIMELINE_SORT_BY_DATES; + $this->filter = $filter ? $filter : BLOCK_TIMELINE_FILTER_BY_7_DAYS; + } + + /** + * Test the available filters with the current user preference and return an array with + * bool flags corresponding to which is active + * + * @return array + */ + protected function get_filters_as_booleans() { + $filters = [ + BLOCK_TIMELINE_FILTER_BY_NONE => false, + BLOCK_TIMELINE_FILTER_BY_OVERDUE => false, + BLOCK_TIMELINE_FILTER_BY_7_DAYS => false, + BLOCK_TIMELINE_FILTER_BY_30_DAYS => false, + BLOCK_TIMELINE_FILTER_BY_3_MONTHS => false, + BLOCK_TIMELINE_FILTER_BY_6_MONTHS => false + ]; + + // Set the selected filter to true. + $filters[$this->filter] = true; + + return $filters; + } + + /** + * Get the offset/limit values corresponding to $this->filter + * which are used to send through to the context as default values + * + * @return array + */ + private function get_filter_offsets() { + + $limit = false; + if (in_array($this->filter, [BLOCK_TIMELINE_FILTER_BY_NONE, BLOCK_TIMELINE_FILTER_BY_OVERDUE])) { + $offset = -14; + if ($this->filter == BLOCK_TIMELINE_FILTER_BY_OVERDUE) { + $limit = 0; + } + } else { + $offset = 0; + $limit = 7; + + switch($this->filter) { + case BLOCK_TIMELINE_FILTER_BY_30_DAYS: + $limit = 30; + break; + case BLOCK_TIMELINE_FILTER_BY_3_MONTHS: + $limit = 90; + break; + case BLOCK_TIMELINE_FILTER_BY_6_MONTHS: + $limit = 180; + break; + } + } + + return [ + 'daysoffset' => $offset, + 'dayslimit' => $limit + ]; + } + /** * Export this data so it can be used as the context for a mustache template. * @@ -69,13 +150,22 @@ class main implements renderable, templatable { return $exporter->export($output); }, $inprogresscourses); - return [ + $filters = $this->get_filters_as_booleans(); + $offsets = $this->get_filter_offsets(); + $contextvariables = [ 'midnight' => usergetmidnight(time()), 'coursepages' => [$formattedcourses], 'urls' => [ 'nocourses' => $nocoursesurl, 'noevents' => $noeventsurl - ] + ], + 'sorttimelinedates' => $this->order == BLOCK_TIMELINE_SORT_BY_DATES, + 'sorttimelinecourses' => $this->order == BLOCK_TIMELINE_SORT_BY_COURSES, + 'selectedfilter' => $this->filter, + 'hasdaysoffset' => true, + 'hasdayslimit' => $offsets['dayslimit'] !== false , + 'nodayslimit' => $offsets['dayslimit'] === false , ]; + return array_merge($contextvariables, $filters, $offsets); } } diff --git a/blocks/timeline/classes/privacy/provider.php b/blocks/timeline/classes/privacy/provider.php index 02ae526ba74..3be099871b1 100644 --- a/blocks/timeline/classes/privacy/provider.php +++ b/blocks/timeline/classes/privacy/provider.php @@ -25,6 +25,7 @@ namespace block_timeline\privacy; defined('MOODLE_INTERNAL') || die(); +use \core_privacy\local\metadata\collection; /** * Privacy Subsystem for block_timeline. @@ -32,15 +33,40 @@ defined('MOODLE_INTERNAL') || die(); * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\user_preference_provider { /** - * Get the language string identifier with the component's language - * file to explain why this plugin stores no data. + * Returns meta-data information about the myoverview block. * - * @return string + * @param \core_privacy\local\metadata\collection $collection A collection of meta-data. + * @return \core_privacy\local\metadata\collection Return the collection of meta-data. */ - public static function get_reason() : string { - return 'privacy:metadata'; + public static function get_metadata(collection $collection) : collection { + $collection->add_user_preference('block_timeline_user_sort_preference', 'privacy:metadata:timelinesortpreference'); + $collection->add_user_preference('block_timeline_user_filter_preference', 'privacy:metadata:timelinefilterpreference'); + return $collection; + } + + /** + * Export all user preferences for the myoverview block + * + * @param int $userid The userid of the user whose data is to be exported. + */ + public static function export_user_preferences(int $userid) { + $preference = get_user_preferences('block_timeline_user_sort_preference', null, $userid); + if (isset($preference)) { + \core_privacy\local\request\writer::export_user_preference('block_timeline', 'block_timeline_user_sort_preference', + get_string($preference, 'block_timeline'), + get_string('privacy:metadata:timelinesortpreference', 'block_timeline') + ); + } + + $preference = get_user_preferences('block_timeline_user_filter_preference', null, $userid); + if (isset($preference)) { + \core_privacy\local\request\writer::export_user_preference('block_timeline', 'block_timeline_user_filter_preference', + get_string($preference, 'block_timeline'), + get_string('privacy:metadata:timelinefilterpreference', 'block_timeline') + ); + } } } diff --git a/blocks/timeline/lang/en/block_timeline.php b/blocks/timeline/lang/en/block_timeline.php index 70f961ce203..d474cc94478 100644 --- a/blocks/timeline/lang/en/block_timeline.php +++ b/blocks/timeline/lang/en/block_timeline.php @@ -41,9 +41,11 @@ $string['next7days'] = 'Next 7 days'; $string['next3months'] = 'Next 3 months'; $string['next6months'] = 'Next 6 months'; $string['overdue'] = 'Overdue'; +$string['all'] = 'All'; $string['pluginname'] = 'Timeline'; $string['sortbycourses'] = 'Sort by courses'; $string['sortbydates'] = 'Sort by dates'; $string['timeline'] = 'Timeline'; $string['viewcourse'] = 'View course'; -$string['privacy:metadata'] = 'The timeline block does not store any personal data.'; +$string['privacy:metadata:timelinesortpreference'] = 'The user sort preference for the timeline block.'; +$string['privacy:metadata:timelinefilterpreference'] = 'The user day filter preference for the timeline block.'; diff --git a/blocks/timeline/lib.php b/blocks/timeline/lib.php new file mode 100644 index 00000000000..26e8783f02a --- /dev/null +++ b/blocks/timeline/lib.php @@ -0,0 +1,70 @@ +. + +/** + * Library functions for timeline + * + * @package block_timeline + * @copyright 2018 Peter Dias + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +defined('MOODLE_INTERNAL') || die(); + +/** + * Define constants to store the SORT user preference + */ +define('BLOCK_TIMELINE_SORT_BY_DATES', 'sortbydates'); +define('BLOCK_TIMELINE_SORT_BY_COURSES', 'sortbycourses'); + +/** + * Define constants to store the FILTER user preference + */ +define('BLOCK_TIMELINE_FILTER_BY_NONE', 'all'); +define('BLOCK_TIMELINE_FILTER_BY_OVERDUE', 'overdue'); +define('BLOCK_TIMELINE_FILTER_BY_7_DAYS', 'next7days'); +define('BLOCK_TIMELINE_FILTER_BY_30_DAYS', 'next30days'); +define('BLOCK_TIMELINE_FILTER_BY_3_MONTHS', 'next3months'); +define('BLOCK_TIMELINE_FILTER_BY_6_MONTHS', 'next6months'); + +/** + * Returns the name of the user preferences as well as the details this plugin uses. + * + * @return array + */ +function block_timeline_user_preferences() { + $preferences['block_timeline_user_sort_preference'] = array( + 'null' => NULL_NOT_ALLOWED, + 'default' => BLOCK_TIMELINE_SORT_BY_DATES, + 'type' => PARAM_ALPHA, + 'choices' => array(BLOCK_TIMELINE_SORT_BY_DATES, BLOCK_TIMELINE_SORT_BY_COURSES) + ); + + $preferences['block_timeline_user_filter_preference'] = array( + 'null' => NULL_NOT_ALLOWED, + 'default' => BLOCK_TIMELINE_FILTER_BY_30_DAYS, + 'type' => PARAM_ALPHANUM, + 'choices' => array( + BLOCK_TIMELINE_FILTER_BY_NONE, + BLOCK_TIMELINE_FILTER_BY_OVERDUE, + BLOCK_TIMELINE_FILTER_BY_7_DAYS, + BLOCK_TIMELINE_FILTER_BY_30_DAYS, + BLOCK_TIMELINE_FILTER_BY_3_MONTHS, + BLOCK_TIMELINE_FILTER_BY_6_MONTHS + ) + ); + + return $preferences; +} diff --git a/blocks/timeline/templates/nav-day-filter.mustache b/blocks/timeline/templates/nav-day-filter.mustache index 90afd627173..938763bf18f 100644 --- a/blocks/timeline/templates/nav-day-filter.mustache +++ b/blocks/timeline/templates/nav-day-filter.mustache @@ -32,18 +32,20 @@