From 82482a0b65bb75764549305c107004af3aa0fbb7 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Thu, 26 Apr 2018 19:11:43 +0200 Subject: [PATCH 01/35] MDL-62229 tool_mobile: Add missing supported module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Workshop module it’s been supported since Moodle 3.4 so it should’ve been included here. --- admin/tool/mobile/classes/api.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/admin/tool/mobile/classes/api.php b/admin/tool/mobile/classes/api.php index 18d89e5ff374..4cff29d77540 100644 --- a/admin/tool/mobile/classes/api.php +++ b/admin/tool/mobile/classes/api.php @@ -308,7 +308,8 @@ public static function get_features_list() { $availablemods = core_plugin_manager::instance()->get_plugins_of_type('mod'); $coursemodules = array(); $appsupportedmodules = array('assign', 'book', 'chat', 'choice', 'data', 'feedback', 'folder', 'forum', 'glossary', 'imscp', - 'label', 'lesson', 'lti', 'page', 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki'); + 'label', 'lesson', 'lti', 'page', 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'); + foreach ($availablemods as $mod) { if (in_array($mod->name, $appsupportedmodules)) { $coursemodules['$mmCourseDelegate_mmaMod' . ucfirst($mod->name)] = $mod->displayname; From 75bff87d18a83022c3da4cdd99ed2376f6f023ae Mon Sep 17 00:00:00 2001 From: Bas Brands Date: Fri, 11 May 2018 14:51:04 +0200 Subject: [PATCH 02/35] MDL-62386 theme: Fix audio playback display for HTML audio Fixed for boost Fixed for bootstrapbase --- filter/mediaplugin/styles.css | 1 - 1 file changed, 1 deletion(-) diff --git a/filter/mediaplugin/styles.css b/filter/mediaplugin/styles.css index 8b2c41d8f625..9a17d5b671d8 100644 --- a/filter/mediaplugin/styles.css +++ b/filter/mediaplugin/styles.css @@ -13,7 +13,6 @@ /* Make videos as wide as possible without being wider than their containers */ width: 100vw; max-width: 100%; - height: auto; } .mediaplugin > div { From 13a215ee8ffb3ad0241c357522466314125ceefb Mon Sep 17 00:00:00 2001 From: Mark Nelson Date: Mon, 14 May 2018 18:29:52 +0800 Subject: [PATCH 03/35] MDL-62228 core: remove unnecessary context check in privacy providers --- admin/roles/classes/privacy/provider.php | 3 --- auth/oauth2/classes/privacy/provider.php | 4 ---- calendar/classes/privacy/provider.php | 4 ---- cohort/classes/privacy/provider.php | 4 ---- enrol/classes/privacy/provider.php | 3 --- mod/assignment/classes/privacy/provider.php | 4 ---- mod/glossary/classes/privacy/provider.php | 3 --- 7 files changed, 25 deletions(-) diff --git a/admin/roles/classes/privacy/provider.php b/admin/roles/classes/privacy/provider.php index ee8bcea104d9..e420f9e80ff2 100644 --- a/admin/roles/classes/privacy/provider.php +++ b/admin/roles/classes/privacy/provider.php @@ -313,9 +313,6 @@ public static function delete_data_for_all_users_in_context(\context $context) { // Don't belong to the modifier user. // Remove data from role_assignments. - if (empty($context)) { - return; - } $DB->delete_records('role_assignments', ['contextid' => $context->id]); } /** diff --git a/auth/oauth2/classes/privacy/provider.php b/auth/oauth2/classes/privacy/provider.php index 5a3476a6572c..76d34a56fc09 100644 --- a/auth/oauth2/classes/privacy/provider.php +++ b/auth/oauth2/classes/privacy/provider.php @@ -120,10 +120,6 @@ public static function export_user_data(approved_contextlist $contextlist) { * @param \context $context The context to delete data for. */ public static function delete_data_for_all_users_in_context(\context $context) { - if (empty($context)) { - return; - } - if ($context->contextlevel != CONTEXT_USER) { return; } diff --git a/calendar/classes/privacy/provider.php b/calendar/classes/privacy/provider.php index aa543a4aaf12..1eafc9fd8915 100644 --- a/calendar/classes/privacy/provider.php +++ b/calendar/classes/privacy/provider.php @@ -187,10 +187,6 @@ public static function export_user_preferences(int $userid) { * @param context $context Transform the specific context to delete data for. */ public static function delete_data_for_all_users_in_context(\context $context) { - if (empty($context)) { - return; - } - // Delete all Calendar Events in the specified context in batches. $eventids = array_keys(self::get_calendar_event_ids_by_context($context)); self::delete_batch_records('event', 'id', $eventids); diff --git a/cohort/classes/privacy/provider.php b/cohort/classes/privacy/provider.php index 4e625e21d3c5..edf3735b393a 100644 --- a/cohort/classes/privacy/provider.php +++ b/cohort/classes/privacy/provider.php @@ -143,10 +143,6 @@ public static function export_user_data(approved_contextlist $contextlist) { * @param context $context A user context. */ public static function delete_data_for_all_users_in_context(\context $context) { - if (empty($context)) { - return; - } - if (!$context instanceof \context_system && !$context instanceof \context_coursecat) { return; } diff --git a/enrol/classes/privacy/provider.php b/enrol/classes/privacy/provider.php index 46a0c6309ba9..c4867d272539 100644 --- a/enrol/classes/privacy/provider.php +++ b/enrol/classes/privacy/provider.php @@ -164,9 +164,6 @@ public static function export_user_data(approved_contextlist $contextlist) { public static function delete_data_for_all_users_in_context(\context $context) { global $DB; - if (empty($context)) { - return; - } // Sanity check that context is at the User context level. if ($context->contextlevel == CONTEXT_COURSE) { $sql = "SELECT ue.id diff --git a/mod/assignment/classes/privacy/provider.php b/mod/assignment/classes/privacy/provider.php index bc4dea16206a..6ed8802ba3f7 100644 --- a/mod/assignment/classes/privacy/provider.php +++ b/mod/assignment/classes/privacy/provider.php @@ -215,10 +215,6 @@ public static function export_user_preferences(int $userid) { public static function delete_data_for_all_users_in_context(\context $context) { global $DB; - if (empty($context)) { - return; - } - if ($context->contextlevel == CONTEXT_MODULE) { // Delete all assignment submissions for the assignment associated with the context module. $assignment = self::get_assignment_by_context($context); diff --git a/mod/glossary/classes/privacy/provider.php b/mod/glossary/classes/privacy/provider.php index 8a94ef36cea2..21b41b0043b1 100644 --- a/mod/glossary/classes/privacy/provider.php +++ b/mod/glossary/classes/privacy/provider.php @@ -240,9 +240,6 @@ protected static function export_glossary_data_for_user(array $glossarydata, \co */ public static function delete_data_for_all_users_in_context(\context $context) { global $DB; - if (empty($context)) { - return; - } if ($context->contextlevel != CONTEXT_MODULE) { return; From 4d2dc4246dc4c7011c4bd5a8c8ab0b10ad2dc6de Mon Sep 17 00:00:00 2001 From: David Monllao Date: Tue, 15 May 2018 10:41:57 +0200 Subject: [PATCH 04/35] MDL-62447 user: Fix component name typo --- user/templates/add_bulk_note.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user/templates/add_bulk_note.mustache b/user/templates/add_bulk_note.mustache index a7f514f14cec..39886c649150 100644 --- a/user/templates/add_bulk_note.mustache +++ b/user/templates/add_bulk_note.mustache @@ -43,7 +43,7 @@

From dc15df16517d148f66b2d1e26fd2265217ba8f1a Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Tue, 15 May 2018 12:01:45 +0200 Subject: [PATCH 05/35] MDL-62448 block_rss_client: Export all feeds from Privacy API --- blocks/rss_client/classes/privacy/provider.php | 9 +++++++-- blocks/rss_client/tests/privacy_test.php | 13 ++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/blocks/rss_client/classes/privacy/provider.php b/blocks/rss_client/classes/privacy/provider.php index edc8a2bd3a97..c1ea54f90af4 100644 --- a/blocks/rss_client/classes/privacy/provider.php +++ b/blocks/rss_client/classes/privacy/provider.php @@ -87,16 +87,21 @@ public static function get_contexts_for_userid(int $userid) : contextlist { * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { + $rssdata = []; $results = static::get_records($contextlist->get_user()->id); foreach ($results as $result) { - $data = (object) [ + $rssdata[] = (object) [ 'title' => $result->title, 'preferredtitle' => $result->preferredtitle, 'description' => $result->description, 'shared' => \core_privacy\local\request\transform::yesno($result->shared), 'url' => $result->url ]; - + } + if (!empty($rssdata)) { + $data = (object) [ + 'feeds' => $rssdata, + ]; \core_privacy\local\request\writer::with_context($contextlist->current())->export_data([ get_string('pluginname', 'block_rss_client')], $data); } diff --git a/blocks/rss_client/tests/privacy_test.php b/blocks/rss_client/tests/privacy_test.php index dfd37c13eede..901d1985cb2c 100644 --- a/blocks/rss_client/tests/privacy_test.php +++ b/blocks/rss_client/tests/privacy_test.php @@ -63,6 +63,7 @@ public function test_export_user_data() { $user = $this->getDataGenerator()->create_user(); $context = context_user::instance($user->id); + $this->add_rss_feed($user); $this->add_rss_feed($user); $writer = \core_privacy\local\request\writer::with_context($context); @@ -70,11 +71,13 @@ public function test_export_user_data() { $this->export_context_data_for_user($user->id, $context, 'block_rss_client'); $data = $writer->get_data([get_string('pluginname', 'block_rss_client')]); - $this->assertEquals('BBC News - World', $data->title); - $this->assertEquals('World News', $data->preferredtitle); - $this->assertEquals('Description: BBC News - World', $data->description); - $this->assertEquals(get_string('no'), $data->shared); - $this->assertEquals('http://feeds.bbci.co.uk/news/world/rss.xml?edition=uk', $data->url); + $this->assertCount(2, $data->feeds); + $feed1 = reset($data->feeds); + $this->assertEquals('BBC News - World', $feed1->title); + $this->assertEquals('World News', $feed1->preferredtitle); + $this->assertEquals('Description: BBC News - World', $feed1->description); + $this->assertEquals(get_string('no'), $feed1->shared); + $this->assertEquals('http://feeds.bbci.co.uk/news/world/rss.xml?edition=uk', $feed1->url); } /** From 859662766644dc6a7351e086973cc1fc7e934434 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Tue, 15 May 2018 12:25:04 +0800 Subject: [PATCH 06/35] MDL-62134 privacy: consistantly call components methods --- privacy/classes/manager.php | 80 ++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/privacy/classes/manager.php b/privacy/classes/manager.php index 65300686b84f..c33a82b908de 100644 --- a/privacy/classes/manager.php +++ b/privacy/classes/manager.php @@ -23,7 +23,14 @@ */ namespace core_privacy; use core_privacy\local\metadata\collection; +use core_privacy\local\metadata\null_provider; +use core_privacy\local\request\context_aware_provider; use core_privacy\local\request\contextlist_collection; +use core_privacy\local\request\core_data_provider; +use core_privacy\local\request\core_user_data_provider; +use core_privacy\local\request\data_provider; +use core_privacy\local\request\user_preference_provider; +use \core_privacy\local\metadata\provider as metadata_provider; defined('MOODLE_INTERNAL') || die(); @@ -120,7 +127,7 @@ class manager { */ public function component_is_compliant(string $component) : bool { // Components which don't store user data need only implement the null_provider. - if ($this->component_implements($component, \core_privacy\local\metadata\null_provider::class)) { + if ($this->component_implements($component, null_provider::class)) { return true; } @@ -129,8 +136,8 @@ public function component_is_compliant(string $component) : bool { } // Components which store user data must implement the local\metadata\provider and the local\request\data_provider. - if ($this->component_implements($component, \core_privacy\local\metadata\provider::class) && - $this->component_implements($component, \core_privacy\local\request\data_provider::class)) { + if ($this->component_implements($component, metadata_provider::class) && + $this->component_implements($component, data_provider::class)) { return true; } @@ -144,8 +151,8 @@ public function component_is_compliant(string $component) : bool { * @return string The key to retrieve the language string for the null provider reason. */ public function get_null_provider_reason(string $component) : string { - if ($this->component_implements($component, \core_privacy\local\metadata\null_provider::class)) { - return $this->get_provider_classname($component)::get_reason(); + if ($this->component_implements($component, null_provider::class)) { + return static::component_class_callback($component, null_provider::class, 'get_reason', []); } else { throw new \coding_exception('Call to undefined method', 'Please only call this method on a null provider.'); } @@ -177,8 +184,10 @@ public function get_metadata_for_components() : array { // Get the metadata, and put into an assoc array indexed by component name. $metadata = []; foreach ($this->get_component_list() as $component) { - if ($this->component_implements($component, \core_privacy\local\metadata\provider::class)) { - $metadata[$component] = $this->get_provider_classname($component)::get_metadata(new collection($component)); + $componentmetadata = static::component_class_callback($component, metadata_provider::class, + 'get_metadata', [new collection($component)]); + if ($componentmetadata !== null) { + $metadata[$component] = $componentmetadata; } } return $metadata; @@ -208,9 +217,9 @@ public function get_contexts_for_userid(int $userid) : contextlist_collection { $a->progress++; $a->datetime = userdate(time()); $progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2); - if ($this->component_implements($component, \core_privacy\local\request\core_user_data_provider::class)) { - $contextlist = $this->get_provider_classname($component)::get_contexts_for_userid($userid); - } else { + $contextlist = static::component_class_callback($component, core_user_data_provider::class, + 'get_contexts_for_userid', [$userid]); + if ($contextlist === null) { $contextlist = new local\request\contextlist(); } @@ -265,13 +274,14 @@ public function export_user_data(contextlist_collection $contextlistcollection) $progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2); // Core user data providers. - if ($this->component_implements($component, \core_privacy\local\request\core_user_data_provider::class)) { + if ($this->component_implements($component, core_user_data_provider::class)) { if (count($approvedcontextlist)) { // This plugin has data it knows about. It is responsible for storing basic data about anything it is // told to export. - $this->get_provider_classname($component)::export_user_data($approvedcontextlist); + static::component_class_callback($component, core_user_data_provider::class, + 'export_user_data', [$approvedcontextlist]); } - } else if (!$this->component_implements($component, \core_privacy\local\request\context_aware_provider::class)) { + } else if (!$this->component_implements($component, context_aware_provider::class)) { // This plugin does not know that it has data - export the shared data it doesn't know about. local\request\helper::export_data_for_null_provider($approvedcontextlist); } @@ -290,15 +300,13 @@ public function export_user_data(contextlist_collection $contextlistcollection) $a->datetime = userdate(time()); $progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2); // Core user preference providers. - if ($this->component_implements($component, \core_privacy\local\request\user_preference_provider::class)) { - $this->get_provider_classname($component)::export_user_preferences($contextlistcollection->get_userid()); - } + static::component_class_callback($component, user_preference_provider::class, + 'export_user_preferences', [$contextlistcollection->get_userid()]); // Contextual information providers. Give each component a chance to include context information based on the // existence of a child context in the contextlist_collection. - if ($this->component_implements($component, \core_privacy\local\request\context_aware_provider::class)) { - $this->get_provider_classname($component)::export_context_data($contextlistcollection); - } + static::component_class_callback($component, context_aware_provider::class, + 'export_context_data', [$contextlistcollection]); } $progress->output(get_string('trace:done', 'core_privacy'), 1); @@ -343,12 +351,11 @@ public function delete_data_for_user(contextlist_collection $contextlistcollecti $a->datetime = userdate(time()); $progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2); - if ($this->component_is_core_provider($component)) { - if (count($approvedcontextlist)) { - // The component knows about data that it has. - // Have it delete its own data. - $this->get_provider_classname($approvedcontextlist->get_component())::delete_data_for_user($approvedcontextlist); - } + if (count($approvedcontextlist)) { + // The component knows about data that it has. + // Have it delete its own data. + static::component_class_callback($approvedcontextlist->get_component(), core_user_data_provider::class, + 'delete_data_for_user', [$approvedcontextlist]); } // Delete any shared user data it doesn't know about. @@ -360,7 +367,7 @@ public function delete_data_for_user(contextlist_collection $contextlistcollecti /** * Delete all use data which matches the specified deletion criteria. * - * @param context $context The specific context to delete data for. + * @param \context $context The specific context to delete data for. */ public function delete_data_for_all_users_in_context(\context $context) { $progress = static::get_log_tracer(); @@ -380,11 +387,10 @@ public function delete_data_for_all_users_in_context(\context $context) { $a->datetime = userdate(time()); $progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2); - if ($this->component_implements($component, \core_privacy\local\request\core_user_data_provider::class)) { - // This component knows about specific data that it owns. - // Have it delete all of that user data for the context. - $this->get_provider_classname($component)::delete_data_for_all_users_in_context($context); - } + // If this component knows about specific data that it owns, + // have it delete all of that user data for the context. + static::component_class_callback($component, core_user_data_provider::class, + 'delete_data_for_all_users_in_context', [$context]); // Delete any shared user data it doesn't know about. local\request\helper::delete_data_for_all_users_in_context($component, $context); @@ -392,16 +398,6 @@ public function delete_data_for_all_users_in_context(\context $context) { $progress->output(get_string('trace:done', 'core_privacy'), 1); } - /** - * Check whether the specified component is a core provider. - * - * @param string $component the frankenstyle component name. - * @return bool true if the component is a core provider, false otherwise. - */ - protected function component_is_core_provider($component) { - return $this->component_implements($component, \core_privacy\local\request\core_data_provider::class); - } - /** * Returns a list of frankenstyle names of core components (plugins and subsystems). * @@ -433,7 +429,7 @@ protected function get_provider_classname($component) { * @return string the fully qualified provider classname. */ public static function get_provider_classname_for_component(string $component) { - return "$component\privacy\provider"; + return "$component\\privacy\\provider"; } /** From 2f477b5764d272f2f3aad9aabc6b6512b05b2989 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Tue, 15 May 2018 10:22:23 +0800 Subject: [PATCH 07/35] MDL-62426 enrol_flatfile: make provider a plugin provider The enrol_flatfile table can contain userdata relating to pending enrolments, so this should be exported and deleted as necessary. --- enrol/flatfile/classes/privacy/provider.php | 155 +++++++++++- enrol/flatfile/lang/en/enrol_flatfile.php | 10 +- .../flatfile/tests/privacy_provider_test.php | 239 ++++++++++++++++++ 3 files changed, 396 insertions(+), 8 deletions(-) create mode 100644 enrol/flatfile/tests/privacy_provider_test.php diff --git a/enrol/flatfile/classes/privacy/provider.php b/enrol/flatfile/classes/privacy/provider.php index 5e403cde6bf0..e6987da771c2 100644 --- a/enrol/flatfile/classes/privacy/provider.php +++ b/enrol/flatfile/classes/privacy/provider.php @@ -21,6 +21,13 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace enrol_flatfile\privacy; +use core_privacy\local\metadata\collection; +use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\context; +use core_privacy\local\request\contextlist; +use core_privacy\local\request\writer; +use core_privacy\local\request\transform; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for enrol_flatfile implementing null_provider. @@ -28,14 +35,148 @@ * @copyright 2018 Carlos Escobedo * @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\plugin\provider { + /** - * Get the language string identifier with the component's language - * file to explain why this plugin stores no data. + * Returns meta data about this system. * - * @return string + * @param collection $collection The initialised collection to add items to. + * @return collection A listing of user data stored through this system. */ - public static function get_reason() : string { - return 'privacy:metadata'; + public static function get_metadata(collection $collection) : collection { + return $collection->add_database_table('enrol_flatfile', [ + 'action' => 'privacy:metadata:enrol_flatfile:action', + 'roleid' => 'privacy:metadata:enrol_flatfile:roleid', + 'userid' => 'privacy:metadata:enrol_flatfile:userid', + 'courseid' => 'privacy:metadata:enrol_flatfile:courseid', + 'timestart' => 'privacy:metadata:enrol_flatfile:timestart', + 'timeend' => 'privacy:metadata:enrol_flatfile:timeend', + 'timemodified' => 'privacy:metadata:enrol_flatfile:timemodified' + ], 'privacy:metadata:enrol_flatfile'); } -} \ No newline at end of file + + /** + * Get the list of contexts that contain user information for the specified user. + * + * @param int $userid The user to search. + * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. + */ + public static function get_contexts_for_userid(int $userid) : contextlist { + $sql = "SELECT c.id + FROM {enrol_flatfile} ef + JOIN {context} c ON c.contextlevel = ? AND c.instanceid = ef.courseid + WHERE ef.userid = ?"; + $params = [CONTEXT_COURSE, $userid]; + + $contextlist = new contextlist(); + $contextlist->set_component('enrol_flatfile'); + return $contextlist->add_from_sql($sql, $params); + } + + /** + * Export all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + */ + public static function export_user_data(approved_contextlist $contextlist) { + global $DB; + + // Ensure all contexts are CONTEXT_COURSE. + $contexts = static::validate_contextlist_contexts($contextlist); + if (empty($contexts)) { + return; + } + + // Get the context instance ids from the contexts. These are the course ids.. + $contextinstanceids = array_map(function($context) { + return $context->instanceid; + }, $contexts); + $userid = $contextlist->get_user()->id; + + // Now, we just need to fetch and format all entries corresponding to the contextids provided. + $sql = "SELECT ef.action, r.shortname, ef.courseid, ef.timestart, ef.timeend, ef.timemodified + FROM {enrol_flatfile} ef + JOIN {context} c ON c.contextlevel = :contextlevel AND c.instanceid = ef.courseid + JOIN {role} r ON r.id = ef.roleid + WHERE ef.userid = :userid"; + $params = ['contextlevel' => CONTEXT_COURSE, 'userid' => $userid]; + list($insql, $inparams) = $DB->get_in_or_equal($contextinstanceids, SQL_PARAMS_NAMED); + $sql .= " AND ef.courseid $insql"; + $params = array_merge($params, $inparams); + + $futureenrolments = $DB->get_recordset_sql($sql, $params); + $enrolmentdata = []; + foreach ($futureenrolments as $futureenrolment) { + // It's possible to have more than one future enrolment per course. + $futureenrolment->timestart = transform::datetime($futureenrolment->timestart); + $futureenrolment->timeend = transform::datetime($futureenrolment->timeend); + $futureenrolment->timemodified = transform::datetime($futureenrolment->timemodified); + $enrolmentdata[$futureenrolment->courseid][] = $futureenrolment; + } + $futureenrolments->close(); + + // And finally, write out the data to the relevant course contexts. + $subcontext = [get_string('flatfileenrolments', 'enrol_flatfile')]; + foreach ($enrolmentdata as $courseid => $enrolments) { + $data = (object) [ + 'pendingenrolments' => $enrolments, + ]; + writer::with_context(\context_course::instance($courseid))->export_data($subcontext, $data); + } + } + + /** + * Delete all data for all users in the specified context. + * + * @param \context $context The specific context to delete data for. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + if ($context->contextlevel != CONTEXT_COURSE) { + return; + } + global $DB; + $DB->delete_records('enrol_flatfile', ['courseid' => $context->instanceid]); + } + + /** + * Delete all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. + */ + public static function delete_data_for_user(approved_contextlist $contextlist) { + // Only delete data from contexts which are at the COURSE_MODULE contextlevel. + $contexts = self::validate_contextlist_contexts($contextlist); + if (empty($contexts)) { + return; + } + + // Get the course ids based on the provided contexts. + $contextinstanceids = array_map(function($context) { + return $context->instanceid; + }, $contextlist->get_contexts()); + + global $DB; + $user = $contextlist->get_user(); + list($insql, $inparams) = $DB->get_in_or_equal($contextinstanceids, SQL_PARAMS_NAMED); + $params = array_merge(['userid' => $user->id], $inparams); + $sql = "userid = :userid AND courseid $insql"; + $DB->delete_records_select('enrol_flatfile', $sql, $params); + } + + /** + * Simple sanity check on the contextlist contexts, making sure they're of CONTEXT_COURSE contextlevel. + * + * @param approved_contextlist $contextlist + * @return array the array of contexts filtered to only include those of CONTEXT_COURSE contextlevel. + */ + protected static function validate_contextlist_contexts(approved_contextlist $contextlist) { + return array_reduce($contextlist->get_contexts(), function($carry, $context) { + if ($context->contextlevel == CONTEXT_COURSE) { + $carry[] = $context; + } + return $carry; + }, []); + } +} diff --git a/enrol/flatfile/lang/en/enrol_flatfile.php b/enrol/flatfile/lang/en/enrol_flatfile.php index 6f444e3bdf21..e815bf9ceb74 100644 --- a/enrol/flatfile/lang/en/enrol_flatfile.php +++ b/enrol/flatfile/lang/en/enrol_flatfile.php @@ -29,6 +29,7 @@ $string['filelockedmailsubject'] = 'Important error: Enrolment file'; $string['flatfile:manage'] = 'Manage user enrolments manually'; $string['flatfile:unenrol'] = 'Unenrol users from the course manually'; +$string['flatfileenrolments'] = 'Flat file (CSV) enrolments'; $string['flatfilesync'] = 'Flat file enrolment sync'; $string['location'] = 'File location'; $string['location_desc'] = 'Specify full path to the enrolment file. The file is automatically deleted after processing.'; @@ -61,4 +62,11 @@ del, student, 17, CF101 add, student, 21, CF101, 1091115000, 1091215000 '; -$string['privacy:metadata'] = 'The Flat file (CSV) enrolment plugin does not store any personal data.'; +$string['privacy:metadata:enrol_flatfile'] = 'The Flat file (CSV) enrolment plugin may store personal data relating to future enrolments in the enrol_flatfile table.'; +$string['privacy:metadata:enrol_flatfile:action'] = 'The enrolment action expected at the given date.'; +$string['privacy:metadata:enrol_flatfile:courseid'] = 'The courseid to which the enrolment relates.'; +$string['privacy:metadata:enrol_flatfile:roleid'] = 'The id of the role to be assigned or revoked.'; +$string['privacy:metadata:enrol_flatfile:timestart'] = 'The time at which the enrolment change starts.'; +$string['privacy:metadata:enrol_flatfile:timeend'] = 'The time at which the enrolment change ends.'; +$string['privacy:metadata:enrol_flatfile:timemodified'] = 'The modification time of this enrolment change.'; +$string['privacy:metadata:enrol_flatfile:userid'] = 'The id of the user to which the role assignment relates.'; diff --git a/enrol/flatfile/tests/privacy_provider_test.php b/enrol/flatfile/tests/privacy_provider_test.php new file mode 100644 index 000000000000..6f4d188b0c1b --- /dev/null +++ b/enrol/flatfile/tests/privacy_provider_test.php @@ -0,0 +1,239 @@ +. + +/** + * Privacy tests for enrol_flatfile. + * + * @package enrol_flatfile + * @category test + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +use core_privacy\local\metadata\collection; +use core_privacy\tests\provider_testcase; +use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\writer; +use enrol_flatfile\privacy\provider; + +/** + * Privacy tests for enrol_flatfile. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class enrol_flatfile_privacy_testcase extends provider_testcase { + + /** @var \stdClass $user1 a test user.*/ + protected $user1; + + /** @var \stdClass $user2 a test user.*/ + protected $user2; + + /** @var \context $coursecontext1 a course context.*/ + protected $coursecontext1; + + /** @var \context $coursecontext2 a course context.*/ + protected $coursecontext2; + + /** @var \context $coursecontext3 a course context.*/ + protected $coursecontext3; + + /** + * Called before every test. + */ + public function setUp() { + $this->resetAfterTest(true); + } + + /** + * Verify that get_metadata returns the database table mapping. + */ + public function test_get_metadata() { + $collection = new collection('enrol_flatfile'); + $collection = provider::get_metadata($collection); + $collectiondata = $collection->get_collection(); + $this->assertNotEmpty($collectiondata); + $this->assertInstanceOf(\core_privacy\local\metadata\types\database_table::class, $collectiondata[0]); + } + + /** + * Verify that the relevant course contexts are returned for users with pending enrolment records. + */ + public function test_get_contexts_for_user() { + global $DB; + // Create, via flatfile syncing, the future enrolments entries in the enrol_flatfile table. + $this->create_future_enrolments(); + + $this->assertEquals(3, $DB->count_records('enrol_flatfile')); + + // We expect to see 2 entries for user1, in course1 and course3. + $contextlist = provider::get_contexts_for_userid($this->user1->id); + $this->assertEquals(2, $contextlist->count()); + $contextids = $contextlist->get_contextids(); + $this->assertContains($this->coursecontext1->id, $contextids); + $this->assertContains($this->coursecontext3->id, $contextids); + + // And 1 for user2 on course2. + $contextlist = provider::get_contexts_for_userid($this->user2->id); + $this->assertEquals(1, $contextlist->count()); + $contextids = $contextlist->get_contextids(); + $this->assertContains($this->coursecontext2->id, $contextids); + } + + /** + * Verify the export includes any future enrolment records for the user. + */ + public function test_export_user_data() { + // Create, via flatfile syncing, the future enrolments entries in the enrol_flatfile table. + $this->create_future_enrolments(); + + // Get contexts containing user data. + $contextlist = provider::get_contexts_for_userid($this->user1->id); + $this->assertEquals(2, $contextlist->count()); + + $approvedcontextlist = new approved_contextlist( + $this->user1, + 'enrol_flatfile', + $contextlist->get_contextids() + ); + + // Export for the approved contexts. + provider::export_user_data($approvedcontextlist); + + // Verify we see one future course enrolment in course1, and one in course3. + $writer = writer::with_context($this->coursecontext1); + $this->assertNotEmpty($writer->get_data([get_string('flatfileenrolments', 'enrol_flatfile')])); + + $writer = writer::with_context($this->coursecontext3); + $this->assertNotEmpty($writer->get_data([get_string('flatfileenrolments', 'enrol_flatfile')])); + + // Verify we have nothing in course 2 for this user. + $writer = writer::with_context($this->coursecontext2); + $this->assertEmpty($writer->get_data([get_string('flatfileenrolments', 'enrol_flatfile')])); + } + + /** + * Verify export will limit any future enrolment records to only those contextids provided. + */ + public function test_export_user_data_restricted_context_subset() { + // Create, via flatfile syncing, the future enrolments entries in the enrol_flatfile table. + $this->create_future_enrolments(); + + // Now, limit the export scope to just course1's context and verify only that data is seen in any export. + $subsetapprovedcontextlist = new approved_contextlist( + $this->user1, + 'enrol_flatfile', + [$this->coursecontext1->id] + ); + + // Export for the approved contexts. + provider::export_user_data($subsetapprovedcontextlist); + + // Verify we see one future course enrolment in course1 only. + $writer = writer::with_context($this->coursecontext1); + $this->assertNotEmpty($writer->get_data([get_string('flatfileenrolments', 'enrol_flatfile')])); + + // And nothing in the course3 context. + $writer = writer::with_context($this->coursecontext3); + $this->assertEmpty($writer->get_data([get_string('flatfileenrolments', 'enrol_flatfile')])); + } + + /** + * Verify that records can be deleted by context. + */ + public function test_delete_data_for_all_users_in_context() { + global $DB; + // Create, via flatfile syncing, the future enrolments entries in the enrol_flatfile table. + $this->create_future_enrolments(); + + // Verify we have 1 future enrolments for course 1. + $this->assertEquals(1, $DB->count_records('enrol_flatfile', ['courseid' => $this->coursecontext1->instanceid])); + + // Now, run delete by context and confirm that record is removed. + provider::delete_data_for_all_users_in_context($this->coursecontext1); + $this->assertEquals(0, $DB->count_records('enrol_flatfile', ['courseid' => $this->coursecontext1->instanceid])); + } + + public function test_delete_data_for_user() { + global $DB; + // Create, via flatfile syncing, the future enrolments entries in the enrol_flatfile table. + $this->create_future_enrolments(); + + // Verify we have 2 future enrolments for course 1 and course 3. + $contextlist = provider::get_contexts_for_userid($this->user1->id); + $this->assertEquals(2, $contextlist->count()); + $contextids = $contextlist->get_contextids(); + $this->assertContains($this->coursecontext1->id, $contextids); + $this->assertContains($this->coursecontext3->id, $contextids); + + $approvedcontextlist = new approved_contextlist( + $this->user1, + 'enrol_flatfile', + $contextids + ); + + // Now, run delete for user and confirm that both records are removed. + provider::delete_data_for_user($approvedcontextlist); + $contextlist = provider::get_contexts_for_userid($this->user1->id); + $this->assertEquals(0, $contextlist->count()); + $this->assertEquals(0, $DB->count_records('enrol_flatfile', ['userid' => $this->user1->id])); + } + + /** + * Helper to sync a file and create the enrol_flatfile DB entries, for use with the get, export and delete tests. + */ + protected function create_future_enrolments() { + global $CFG; + $this->user1 = $this->getDataGenerator()->create_user(['idnumber' => 'u1']); + $this->user2 = $this->getDataGenerator()->create_user(['idnumber' => 'u2']); + + $course1 = $this->getDataGenerator()->create_course(['idnumber' => 'c1']); + $course2 = $this->getDataGenerator()->create_course(['idnumber' => 'c2']); + $course3 = $this->getDataGenerator()->create_course(['idnumber' => 'c3']); + $this->coursecontext1 = context_course::instance($course1->id); + $this->coursecontext2 = context_course::instance($course2->id); + $this->coursecontext3 = context_course::instance($course3->id); + + $now = time(); + $future = $now + 60 * 60 * 5; + $farfuture = $now + 60 * 60 * 24 * 5; + + $file = "$CFG->dataroot/enrol.txt"; + $data = "add,student,u1,c1,$future,0 + add,student,u2,c2,$future,0 + add,student,u1,c3,$future,$farfuture"; + file_put_contents($file, $data); + + $trace = new null_progress_trace(); + $this->enable_plugin(); + $flatfileplugin = enrol_get_plugin('flatfile'); + $flatfileplugin->set_config('location', $file); + $flatfileplugin->sync($trace); + } + + /** + * Enables the flatfile plugin for testing. + */ + protected function enable_plugin() { + $enabled = enrol_get_plugins(true); + $enabled['flatfile'] = true; + $enabled = array_keys($enabled); + set_config('enrol_plugins_enabled', implode(',', $enabled)); + } +} From 321d41a0220d15e5824c742a0ecff619a706c3c0 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Wed, 16 May 2018 10:17:26 +0800 Subject: [PATCH 08/35] MDL-62426 core_enrol: control enrolment subcontexts at the provider Added a function to the core_enrol provider allowing enrol plugins to set their subcontext relative to a fixed 'enrolments' parent dir. --- enrol/classes/privacy/provider.php | 12 ++++++++++++ enrol/flatfile/classes/privacy/provider.php | 2 +- enrol/flatfile/tests/privacy_provider_test.php | 14 +++++++++----- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/enrol/classes/privacy/provider.php b/enrol/classes/privacy/provider.php index 46a0c6309ba9..0b5533232e59 100644 --- a/enrol/classes/privacy/provider.php +++ b/enrol/classes/privacy/provider.php @@ -230,4 +230,16 @@ protected static function delete_user_data(string $sql, array $params) { $DB->delete_records_select('user_enrolments', "id $sql", $params); } + /** + * Get the subcontext for export. + * + * @param array $subcontext Any additional subcontext to use. + * @return array The array containing the full subcontext, i.e. [enrolments, subcontext] + */ + public static function get_subcontext(array $subcontext) { + return array_merge( + [get_string('privacy:metadata:user_enrolments', 'core_enrol')], + $subcontext + ); + } } diff --git a/enrol/flatfile/classes/privacy/provider.php b/enrol/flatfile/classes/privacy/provider.php index e6987da771c2..93e5d831791c 100644 --- a/enrol/flatfile/classes/privacy/provider.php +++ b/enrol/flatfile/classes/privacy/provider.php @@ -118,7 +118,7 @@ public static function export_user_data(approved_contextlist $contextlist) { $futureenrolments->close(); // And finally, write out the data to the relevant course contexts. - $subcontext = [get_string('flatfileenrolments', 'enrol_flatfile')]; + $subcontext = \core_enrol\privacy\provider::get_subcontext([get_string('pluginname', 'enrol_flatfile')]); foreach ($enrolmentdata as $courseid => $enrolments) { $data = (object) [ 'pendingenrolments' => $enrolments, diff --git a/enrol/flatfile/tests/privacy_provider_test.php b/enrol/flatfile/tests/privacy_provider_test.php index 6f4d188b0c1b..5ad5dbbf1752 100644 --- a/enrol/flatfile/tests/privacy_provider_test.php +++ b/enrol/flatfile/tests/privacy_provider_test.php @@ -117,15 +117,17 @@ public function test_export_user_data() { provider::export_user_data($approvedcontextlist); // Verify we see one future course enrolment in course1, and one in course3. + $subcontext = \core_enrol\privacy\provider::get_subcontext([get_string('pluginname', 'enrol_flatfile')]); + $writer = writer::with_context($this->coursecontext1); - $this->assertNotEmpty($writer->get_data([get_string('flatfileenrolments', 'enrol_flatfile')])); + $this->assertNotEmpty($writer->get_data($subcontext)); $writer = writer::with_context($this->coursecontext3); - $this->assertNotEmpty($writer->get_data([get_string('flatfileenrolments', 'enrol_flatfile')])); + $this->assertNotEmpty($writer->get_data($subcontext)); // Verify we have nothing in course 2 for this user. $writer = writer::with_context($this->coursecontext2); - $this->assertEmpty($writer->get_data([get_string('flatfileenrolments', 'enrol_flatfile')])); + $this->assertEmpty($writer->get_data($subcontext)); } /** @@ -146,12 +148,14 @@ public function test_export_user_data_restricted_context_subset() { provider::export_user_data($subsetapprovedcontextlist); // Verify we see one future course enrolment in course1 only. + $subcontext = \core_enrol\privacy\provider::get_subcontext([get_string('pluginname', 'enrol_flatfile')]); + $writer = writer::with_context($this->coursecontext1); - $this->assertNotEmpty($writer->get_data([get_string('flatfileenrolments', 'enrol_flatfile')])); + $this->assertNotEmpty($writer->get_data($subcontext)); // And nothing in the course3 context. $writer = writer::with_context($this->coursecontext3); - $this->assertEmpty($writer->get_data([get_string('flatfileenrolments', 'enrol_flatfile')])); + $this->assertEmpty($writer->get_data($subcontext)); } /** From e6a65ee7962ab506ac208771c1e0c2a1eb9071b4 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 15 May 2018 14:16:54 +0800 Subject: [PATCH 09/35] MDL-62134 core_privacy: Allow for a failure handler --- privacy/classes/manager.php | 71 +++++++- privacy/classes/manager_observer.php | 47 +++++ privacy/tests/fixtures/provider_a.php | 91 ++++++++++ .../fixtures/provider_throwing_exception.php | 88 +++++++++ privacy/tests/manager_test.php | 167 +++++++++++++++++- version.php | 2 +- 6 files changed, 454 insertions(+), 12 deletions(-) create mode 100644 privacy/classes/manager_observer.php create mode 100644 privacy/tests/fixtures/provider_a.php create mode 100644 privacy/tests/fixtures/provider_throwing_exception.php diff --git a/privacy/classes/manager.php b/privacy/classes/manager.php index c33a82b908de..47e73d291f99 100644 --- a/privacy/classes/manager.php +++ b/privacy/classes/manager.php @@ -26,7 +26,6 @@ use core_privacy\local\metadata\null_provider; use core_privacy\local\request\context_aware_provider; use core_privacy\local\request\contextlist_collection; -use core_privacy\local\request\core_data_provider; use core_privacy\local\request\core_user_data_provider; use core_privacy\local\request\data_provider; use core_privacy\local\request\user_preference_provider; @@ -110,6 +109,21 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class manager { + + /** + * @var manager_observer Observer. + */ + protected $observer; + + /** + * Set the failure handler. + * + * @param manager_observer $observer + */ + public function set_observer(manager_observer $observer) { + $this->observer = $observer; + } + /** * Checks whether the given component is compliant with the core_privacy API. * To be considered compliant, a component must declare whether (and where) it stores personal data. @@ -152,7 +166,8 @@ public function component_is_compliant(string $component) : bool { */ public function get_null_provider_reason(string $component) : string { if ($this->component_implements($component, null_provider::class)) { - return static::component_class_callback($component, null_provider::class, 'get_reason', []); + $reason = $this->handled_component_class_callback($component, null_provider::class, 'get_reason', []); + return empty($reason) ? 'privacy:reason' : $reason; } else { throw new \coding_exception('Call to undefined method', 'Please only call this method on a null provider.'); } @@ -184,7 +199,7 @@ public function get_metadata_for_components() : array { // Get the metadata, and put into an assoc array indexed by component name. $metadata = []; foreach ($this->get_component_list() as $component) { - $componentmetadata = static::component_class_callback($component, metadata_provider::class, + $componentmetadata = $this->handled_component_class_callback($component, metadata_provider::class, 'get_metadata', [new collection($component)]); if ($componentmetadata !== null) { $metadata[$component] = $componentmetadata; @@ -196,6 +211,7 @@ public function get_metadata_for_components() : array { /** * Gets a collection of resultset objects for all components. * + * * @param int $userid the id of the user we're fetching contexts for. * @return contextlist_collection the collection of contextlist items for the respective components. */ @@ -217,7 +233,7 @@ public function get_contexts_for_userid(int $userid) : contextlist_collection { $a->progress++; $a->datetime = userdate(time()); $progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2); - $contextlist = static::component_class_callback($component, core_user_data_provider::class, + $contextlist = $this->handled_component_class_callback($component, core_user_data_provider::class, 'get_contexts_for_userid', [$userid]); if ($contextlist === null) { $contextlist = new local\request\contextlist(); @@ -278,7 +294,7 @@ public function export_user_data(contextlist_collection $contextlistcollection) if (count($approvedcontextlist)) { // This plugin has data it knows about. It is responsible for storing basic data about anything it is // told to export. - static::component_class_callback($component, core_user_data_provider::class, + $this->handled_component_class_callback($component, core_user_data_provider::class, 'export_user_data', [$approvedcontextlist]); } } else if (!$this->component_implements($component, context_aware_provider::class)) { @@ -300,12 +316,12 @@ public function export_user_data(contextlist_collection $contextlistcollection) $a->datetime = userdate(time()); $progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2); // Core user preference providers. - static::component_class_callback($component, user_preference_provider::class, + $this->handled_component_class_callback($component, user_preference_provider::class, 'export_user_preferences', [$contextlistcollection->get_userid()]); // Contextual information providers. Give each component a chance to include context information based on the // existence of a child context in the contextlist_collection. - static::component_class_callback($component, context_aware_provider::class, + $this->handled_component_class_callback($component, context_aware_provider::class, 'export_context_data', [$contextlistcollection]); } $progress->output(get_string('trace:done', 'core_privacy'), 1); @@ -354,7 +370,7 @@ public function delete_data_for_user(contextlist_collection $contextlistcollecti if (count($approvedcontextlist)) { // The component knows about data that it has. // Have it delete its own data. - static::component_class_callback($approvedcontextlist->get_component(), core_user_data_provider::class, + $this->handled_component_class_callback($approvedcontextlist->get_component(), core_user_data_provider::class, 'delete_data_for_user', [$approvedcontextlist]); } @@ -389,7 +405,7 @@ public function delete_data_for_all_users_in_context(\context $context) { // If this component knows about specific data that it owns, // have it delete all of that user data for the context. - static::component_class_callback($component, core_user_data_provider::class, + $this->handled_component_class_callback($component, core_user_data_provider::class, 'delete_data_for_all_users_in_context', [$context]); // Delete any shared user data it doesn't know about. @@ -496,4 +512,41 @@ protected static function get_log_tracer() { return new \text_progress_trace(); } + + /** + * Call the named method with the specified params on the supplied component if it implements the relevant interface + * on its provider. + * + * @param string $component The component to call + * @param string $interface The interface to implement + * @param string $methodname The method to call + * @param array $params The params to call + * @return mixed + */ + protected function handled_component_class_callback(string $component, string $interface, string $methodname, array $params) { + try { + return static::component_class_callback($component, $interface, $methodname, $params); + } catch (\Throwable $e) { + debugging($e->getMessage(), DEBUG_DEVELOPER, $e->getTrace()); + $this->component_class_callback_failed($e, $component, $interface, $methodname, $params); + + return null; + } + } + + /** + * Notifies the observer of any failure. + * + * @param \Throwable $e + * @param string $component + * @param string $interface + * @param string $methodname + * @param array $params + */ + protected function component_class_callback_failed(\Throwable $e, string $component, string $interface, + string $methodname, array $params) { + if ($this->observer) { + call_user_func_array([$this->observer, 'handle_component_failure'], func_get_args()); + } + } } diff --git a/privacy/classes/manager_observer.php b/privacy/classes/manager_observer.php new file mode 100644 index 000000000000..b1d78477612f --- /dev/null +++ b/privacy/classes/manager_observer.php @@ -0,0 +1,47 @@ +. + +/** + * This file contains the interface required to observe failures in the manager. + * + * @package core_privacy + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_privacy; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The interface for a Manager observer. + * + * @package core_privacy + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface manager_observer { + + /** + * Handle failure of a component. + * + * @param \Throwable $e + * @param string $component + * @param string $interface + * @param string $methodname + * @param array $params + */ + public function handle_component_failure($e, $component, $interface, $methodname, array $params); +} diff --git a/privacy/tests/fixtures/provider_a.php b/privacy/tests/fixtures/provider_a.php new file mode 100644 index 000000000000..9f88acfbdfbb --- /dev/null +++ b/privacy/tests/fixtures/provider_a.php @@ -0,0 +1,91 @@ +. + +/** + * Test provider which works. + * + * @package core_privacy + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_component_a\privacy; + +defined('MOODLE_INTERNAL') || die(); + +use core_privacy\local\metadata\collection; +use core_privacy\local\request\contextlist; +use core_privacy\local\request\approved_contextlist; + +/** + * Mock core_user_data_provider for unit tests. + * + * @package core_privacy + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements + \core_privacy\local\metadata\provider, + \core_privacy\local\request\plugin\provider { + + /** + * Get metadata. + * + * @param collection $collection The initialised collection to add items to. + * @return collection A listing of user data stored through this system. + */ + public static function get_metadata(collection $collection) : collection { + $collection->add_subsystem_link('core_files'); + return $collection; + } + + /** + * Get the list of contexts that contain user information for the specified user. + * + * @param int $userid The user to search. + * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. + */ + public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist { + $c = new \core_privacy\local\request\contextlist(); + $c->add_system_context(); + + return $c; + } + + /** + * Export all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + */ + public static function export_user_data(approved_contextlist $contextlist) { + } + + /** + * Delete all data for all users in the specified context. + * + * @param context $context The specific context to delete data for. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + } + + /** + * Delete all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. + */ + public static function delete_data_for_user(approved_contextlist $contextlist) { + } +} diff --git a/privacy/tests/fixtures/provider_throwing_exception.php b/privacy/tests/fixtures/provider_throwing_exception.php new file mode 100644 index 000000000000..6c6c5fc5efd8 --- /dev/null +++ b/privacy/tests/fixtures/provider_throwing_exception.php @@ -0,0 +1,88 @@ +. + +/** + * Test provider which has issues. + * + * @package core_privacy + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_component_broken\privacy; + +use core_privacy\local\metadata\collection; +use core_privacy\local\request\contextlist; +use core_privacy\local\request\approved_contextlist; + +/** + * Mock core_user_data_provider for unit tests. + * + * @package core_privacy + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements + \core_privacy\local\metadata\provider, + \core_privacy\local\request\plugin\provider { + + /** + * Get metadata. + * + * @param collection $collection The initialised collection to add items to. + * @return collection A listing of user data stored through this system. + */ + public static function get_metadata(collection $collection) : collection { + throw new \coding_exception(__FUNCTION__); + } + + /** + * Get the list of contexts that contain user information for the specified user. + * + * @param int $userid The user to search. + * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. + */ + public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist { + throw new \coding_exception(__FUNCTION__); + } + + /** + * Export all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + */ + public static function export_user_data(approved_contextlist $contextlist) { + throw new \coding_exception(__FUNCTION__); + } + + /** + * Delete all data for all users in the specified context. + * + * @param context $context The specific context to delete data for. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + throw new \coding_exception(__FUNCTION__); + } + + /** + * Delete all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. + */ + public static function delete_data_for_user(approved_contextlist $contextlist) { + throw new \coding_exception(__FUNCTION__); + } +} diff --git a/privacy/tests/manager_test.php b/privacy/tests/manager_test.php index b16e1c7aa9c3..3f1f9acd55bc 100644 --- a/privacy/tests/manager_test.php +++ b/privacy/tests/manager_test.php @@ -28,8 +28,11 @@ require_once($CFG->dirroot . '/privacy/tests/fixtures/mock_provider.php'); require_once($CFG->dirroot . '/privacy/tests/fixtures/mock_plugin_subplugin_provider.php'); require_once($CFG->dirroot . '/privacy/tests/fixtures/mock_mod_with_user_data_provider.php'); +require_once($CFG->dirroot . '/privacy/tests/fixtures/provider_a.php'); +require_once($CFG->dirroot . '/privacy/tests/fixtures/provider_throwing_exception.php'); use \core_privacy\local\request\writer; +use \core_privacy\local\request\approved_contextlist; /** * Privacy manager unit tests. @@ -175,7 +178,7 @@ public function test_export_user_data() { // Create an approved contextlist. $approvedcontextlistcollection = new \core_privacy\local\request\contextlist_collection(10); foreach ($contextlistcollection->get_contextlists() as $contextlist) { - $approvedcontextlist = new \core_privacy\local\request\approved_contextlist(new stdClass(), $contextlist->get_component(), + $approvedcontextlist = new approved_contextlist(new stdClass(), $contextlist->get_component(), $contextlist->get_contextids()); $approvedcontextlistcollection->add_contextlist($approvedcontextlist); } @@ -210,7 +213,7 @@ public function test_delete_data_for_user() { // Create an approved contextlist. $approvedcontextlistcollection = new \core_privacy\local\request\contextlist_collection($user->id); foreach ($contextlistcollection->get_contextlists() as $contextlist) { - $approvedcontextlist = new \core_privacy\local\request\approved_contextlist($user, $contextlist->get_component(), + $approvedcontextlist = new approved_contextlist($user, $contextlist->get_component(), $contextlist->get_contextids()); $approvedcontextlistcollection->add_contextlist($approvedcontextlist); } @@ -300,4 +303,164 @@ public function is_empty_subsystem_provider() { ], ]; } + + /** + * Test that get_contexts_for_userid() with a failing item. + */ + public function test_get_contexts_for_userid_with_failing() { + // Get a mock manager, in which the core components list is mocked to include all mock plugins. + // testcomponent is a core provider, testcomponent2 isa null provider, testcomponent3 is subplugin provider (non core). + $mockman = $this->get_mock_manager_with_core_components(['mod_component_broken', 'mod_component_a']); + + $observer = $this->getMockBuilder(\core_privacy\manager_observer::class) + ->setMethods(['handle_component_failure']) + ->getMock(); + $mockman->set_observer($observer); + + $observer->expects($this->once()) + ->method('handle_component_failure') + ->with( + $this->isInstanceOf(\coding_exception::class), + $this->identicalTo('mod_component_broken'), + $this->identicalTo(\core_privacy\local\request\core_user_data_provider::class), + $this->identicalTo('get_contexts_for_userid'), + $this->anything() + ); + + // Get the contextlist_collection. + $contextlistcollection = $mockman->get_contexts_for_userid(10); + $this->assertDebuggingCalled(); + $this->assertInstanceOf(\core_privacy\local\request\contextlist_collection::class, $contextlistcollection); + $this->assertCount(1, $contextlistcollection); + + // The component which completed shoudl have returned a contextlist. + $this->assertInstanceOf(\core_privacy\local\request\contextlist::class, + $contextlistcollection->get_contextlist_for_component('mod_component_a')); + $this->assertEmpty($contextlistcollection->get_contextlist_for_component('mod_component_broken')); + } + + /** + * Test that export_user_data() with a failing item. + */ + public function test_export_user_data_with_failing() { + $user = \core_user::get_user_by_username('admin'); + $mockman = $this->get_mock_manager_with_core_components(['mod_component_broken', 'mod_component_a']); + $context = \context_system::instance(); + $contextid = $context->id; + + $observer = $this->getMockBuilder(\core_privacy\manager_observer::class) + ->setMethods(['handle_component_failure']) + ->getMock(); + $mockman->set_observer($observer); + + $observer->expects($this->once()) + ->method('handle_component_failure') + ->with( + $this->isInstanceOf(\coding_exception::class), + $this->identicalTo('mod_component_broken'), + $this->identicalTo(\core_privacy\local\request\core_user_data_provider::class), + $this->identicalTo('export_user_data'), + $this->anything() + ); + + $collection = new \core_privacy\local\request\contextlist_collection(10); + $collection->add_contextlist(new approved_contextlist($user, 'mod_component_broken', [$contextid])); + $collection->add_contextlist(new approved_contextlist($user, 'mod_component_a', [$contextid])); + + // Get the contextlist_collection. + $mockman->export_user_data($collection); + $this->assertDebuggingCalled(); + } + + /** + * Test that delete_data_for_user() with a failing item. + */ + public function test_delete_data_for_user_with_failing() { + $user = \core_user::get_user_by_username('admin'); + $mockman = $this->get_mock_manager_with_core_components(['mod_component_broken', 'mod_component_a']); + $context = \context_system::instance(); + $contextid = $context->id; + + $observer = $this->getMockBuilder(\core_privacy\manager_observer::class) + ->setMethods(['handle_component_failure']) + ->getMock(); + $mockman->set_observer($observer); + + $observer->expects($this->once()) + ->method('handle_component_failure') + ->with( + $this->isInstanceOf(\coding_exception::class), + $this->identicalTo('mod_component_broken'), + $this->identicalTo(\core_privacy\local\request\core_user_data_provider::class), + $this->identicalTo('delete_data_for_user'), + $this->anything() + ); + + $collection = new \core_privacy\local\request\contextlist_collection(10); + $collection->add_contextlist(new approved_contextlist($user, 'mod_component_broken', [$contextid])); + $collection->add_contextlist(new approved_contextlist($user, 'mod_component_a', [$contextid])); + + // Get the contextlist_collection. + $mockman->delete_data_for_user($collection); + $this->assertDebuggingCalled(); + } + + /** + * Test that delete_data_for_all_users_in_context() with a failing item. + */ + public function test_delete_data_for_all_users_in_context_with_failing() { + $user = \core_user::get_user_by_username('admin'); + $mockman = $this->get_mock_manager_with_core_components(['mod_component_broken', 'mod_component_a']); + $context = \context_system::instance(); + + $observer = $this->getMockBuilder(\core_privacy\manager_observer::class) + ->setMethods(['handle_component_failure']) + ->getMock(); + $mockman->set_observer($observer); + + $observer->expects($this->once()) + ->method('handle_component_failure') + ->with( + $this->isInstanceOf(\coding_exception::class), + $this->identicalTo('mod_component_broken'), + $this->identicalTo(\core_privacy\local\request\core_user_data_provider::class), + $this->identicalTo('delete_data_for_all_users_in_context'), + $this->anything() + ); + + // Get the contextlist_collection. + $mockman->delete_data_for_all_users_in_context($context); + $this->assertDebuggingCalled(); + } + + /** + * Test that get_metadata_for_components() with a failing item. + */ + public function test_get_metadata_for_components_with_failing() { + $user = \core_user::get_user_by_username('admin'); + $mockman = $this->get_mock_manager_with_core_components(['mod_component_broken', 'mod_component_a']); + $context = \context_system::instance(); + + $observer = $this->getMockBuilder(\core_privacy\manager_observer::class) + ->setMethods(['handle_component_failure']) + ->getMock(); + $mockman->set_observer($observer); + + $observer->expects($this->once()) + ->method('handle_component_failure') + ->with( + $this->isInstanceOf(\coding_exception::class), + $this->identicalTo('mod_component_broken'), + $this->identicalTo(\core_privacy\local\metadata\provider::class), + $this->identicalTo('get_metadata'), + $this->anything() + ); + + // Get the contextlist_collection. + $metadata = $mockman->get_metadata_for_components(); + $this->assertDebuggingCalled(); + + $this->assertInternalType('array', $metadata); + $this->assertCount(1, $metadata); + } } diff --git a/version.php b/version.php index 014321a834df..d0c84dc5d845 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2017111302.14; // 20171113 = branching date YYYYMMDD - do not modify! +$version = 2017111302.15; // 20171113 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. From d5f69f0c5c288ca0578d78c0a0f3489e39191201 Mon Sep 17 00:00:00 2001 From: Mark Nelson Date: Wed, 16 May 2018 11:41:19 +0800 Subject: [PATCH 10/35] MDL-62456 mod_lti: add missing add_external_location_link call --- mod/lti/classes/privacy/provider.php | 19 +++++++++++++++++++ mod/lti/lang/en/lti.php | 13 +++++++++++++ mod/lti/tests/privacy_provider_test.php | 5 ++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/mod/lti/classes/privacy/provider.php b/mod/lti/classes/privacy/provider.php index 022e4c046f4b..b5c7d41d75f7 100644 --- a/mod/lti/classes/privacy/provider.php +++ b/mod/lti/classes/privacy/provider.php @@ -49,6 +49,25 @@ class provider implements * @return collection the updated collection of metadata items. */ public static function get_metadata(collection $items) : collection { + $items->add_external_location_link( + 'lti_provider', + [ + 'userid' => 'privacy:metadata:userid', + 'username' => 'privacy:metadata:username', + 'useridnumber' => 'privacy:metadata:useridnumber', + 'firstname' => 'privacy:metadata:firstname', + 'lastname' => 'privacy:metadata:lastname', + 'fullname' => 'privacy:metadata:fullname', + 'email' => 'privacy:metadata:email', + 'role' => 'privacy:metadata:role', + 'courseid' => 'privacy:metadata:courseid', + 'courseidnumber' => 'privacy:metadata:courseidnumber', + 'courseshortname' => 'privacy:metadata:courseshortname', + 'coursefullname' => 'privacy:metadata:coursefullname', + ], + 'privacy:metadata:externalpurpose' + ); + $items->add_database_table( 'lti_submission', [ diff --git a/mod/lti/lang/en/lti.php b/mod/lti/lang/en/lti.php index eda118811a46..609dcba96569 100644 --- a/mod/lti/lang/en/lti.php +++ b/mod/lti/lang/en/lti.php @@ -356,7 +356,16 @@ $string['preferwidth'] = 'Preferred width'; $string['press_to_submit'] = 'Press to launch this activity'; $string['privacy'] = 'Privacy'; +$string['privacy:metadata:courseid'] = 'The ID of the course the user is accessing the LTI Consumer from'; +$string['privacy:metadata:courseidnumber'] = 'The ID number of the course the user is accessing the LTI Consumer from'; +$string['privacy:metadata:coursefullname'] = 'The fullname of the course the user is accessing the LTI Consumer from'; +$string['privacy:metadata:courseshortname'] = 'The shortname of the course the user is accessing the LTI Consumer from'; $string['privacy:metadata:createdby'] = 'The user who created the record'; +$string['privacy:metadata:email'] = 'The email address of the user accessing the LTI Consumer'; +$string['privacy:metadata:externalpurpose'] = 'The LTI Consumer provides user information and context to the LTI Tool Provider.'; +$string['privacy:metadata:firstname'] = 'The firstname of the user accessing the LTI Consumer'; +$string['privacy:metadata:fullname'] = 'The fullname of the user accessing the LTI Consumer'; +$string['privacy:metadata:lastname'] = 'The lastname of the user accessing the LTI Consumer'; $string['privacy:metadata:lti_submission'] = 'LTI submission'; $string['privacy:metadata:lti_submission:datesubmitted'] = 'The timestamp indicating when the submission was made'; $string['privacy:metadata:lti_submission:dateupdated'] = 'The timestamp indicating when the submission was modified'; @@ -367,8 +376,12 @@ $string['privacy:metadata:lti_tool_proxies:name'] = 'LTI proxy name'; $string['privacy:metadata:lti_types'] = 'LTI types'; $string['privacy:metadata:lti_types:name'] = 'LTI type name'; +$string['privacy:metadata:role'] = 'The role in the course for the user accessing the LTI Consumer'; $string['privacy:metadata:timecreated'] = 'The date at which the record was created'; $string['privacy:metadata:timemodified'] = 'The date at which the record was modified'; +$string['privacy:metadata:userid'] = 'The ID of the user accessing the LTI Consumer'; +$string['privacy:metadata:useridnumber'] = 'The ID number of the user accessing the LTI Consumer'; +$string['privacy:metadata:username'] = 'The username of the user accessing the LTI Consumer'; $string['quickgrade'] = 'Allow quick grading'; $string['quickgrade_help'] = 'If enabled, multiple tools can be graded on one page. Add grades and comments then click the "Save all my feedback" button to save all changes for that page.'; $string['redirect'] = 'You will be redirected in few seconds. If you are not, press the button.'; diff --git a/mod/lti/tests/privacy_provider_test.php b/mod/lti/tests/privacy_provider_test.php index e888927a71f7..15061606be3b 100644 --- a/mod/lti/tests/privacy_provider_test.php +++ b/mod/lti/tests/privacy_provider_test.php @@ -43,7 +43,10 @@ public function test_get_metadata() { $collection = new collection('mod_lti'); $newcollection = provider::get_metadata($collection); $itemcollection = $newcollection->get_collection(); - $this->assertCount(3, $itemcollection); + $this->assertCount(4, $itemcollection); + + $ltiproviderexternal = array_shift($itemcollection); + $this->assertEquals('lti_provider', $ltiproviderexternal->get_name()); $ltisubmissiontable = array_shift($itemcollection); $this->assertEquals('lti_submission', $ltisubmissiontable->get_name()); From 212b6daf3284a96a7ae414b746e97eaaf3dceb22 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Wed, 16 May 2018 11:03:38 +0800 Subject: [PATCH 11/35] MDL-62147 privacy: corrections to tables, temporary tables --- .../standard/classes/privacy/provider.php | 2 +- .../classes/privacy/provider.php | 62 +++++++++++++++++-- .../lang/en/block_recent_activity.php | 7 +++ lang/en/moodle.php | 10 +++ lang/en/user.php | 8 +++ lib/classes/privacy/provider.php | 10 +++ mod/chat/classes/privacy/provider.php | 22 ++++++- mod/chat/lang/en/chat.php | 9 +++ mod/forum/classes/privacy/provider.php | 8 +++ mod/forum/lang/en/forum.php | 5 ++ user/classes/privacy/provider.php | 14 +++++ 11 files changed, 147 insertions(+), 10 deletions(-) diff --git a/admin/tool/log/store/standard/classes/privacy/provider.php b/admin/tool/log/store/standard/classes/privacy/provider.php index 2761ca26c643..4ee8007c1ea2 100644 --- a/admin/tool/log/store/standard/classes/privacy/provider.php +++ b/admin/tool/log/store/standard/classes/privacy/provider.php @@ -51,7 +51,7 @@ class provider implements * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection) : collection { - $collection->add_database_table('log', [ + $collection->add_database_table('logstore_standard_log', [ 'eventname' => 'privacy:metadata:log:eventname', 'userid' => 'privacy:metadata:log:userid', 'relateduserid' => 'privacy:metadata:log:relateduserid', diff --git a/blocks/recent_activity/classes/privacy/provider.php b/blocks/recent_activity/classes/privacy/provider.php index 8e998d5353cd..b024b5b6d2f9 100644 --- a/blocks/recent_activity/classes/privacy/provider.php +++ b/blocks/recent_activity/classes/privacy/provider.php @@ -25,6 +25,10 @@ namespace block_recent_activity\privacy; +use core_privacy\local\metadata\collection; +use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\contextlist; + defined('MOODLE_INTERNAL') || die(); /** @@ -33,15 +37,61 @@ * @copyright 2018 Shamim Rezaie * @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\plugin\provider { + + /** + * Returns metadata. + * + * @param collection $collection The initialised collection to add items to. + * @return collection A listing of user data stored through this system. + */ + public static function get_metadata(collection $collection) : collection { + + // This plugin defines a db table but it is not considered personal data and, therefore, not exported or deleted. + $collection->add_database_table('block_recent_activity', [ + 'courseid' => 'privacy:metadata:block_recent_activity:courseid', + 'cmid' => 'privacy:metadata:block_recent_activity:cmid', + 'timecreated' => 'privacy:metadata:block_recent_activity:timecreated', + 'userid' => 'privacy:metadata:block_recent_activity:userid', + 'action' => 'privacy:metadata:block_recent_activity:action', + 'modname' => 'privacy:metadata:block_recent_activity:modname' + ], 'privacy:metadata:block_recent_activity'); + + return $collection; + } + + /** + * Get the list of contexts that contain user information for the specified user. + * + * @param int $userid The user to search. + * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. + */ + public static function get_contexts_for_userid(int $userid) : contextlist { + return new contextlist(); + } + + /** + * Export all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + */ + public static function export_user_data(approved_contextlist $contextlist) { + } + + /** + * Delete all data for all users in the specified context. + * + * @param \context $context The specific context to delete data for. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + } /** - * Get the language string identifier with the component's language - * file to explain why this plugin stores no data. + * Delete all user data for the specified user, in the specified contexts. * - * @return string + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ - public static function get_reason() : string { - return 'privacy:metadata'; + public static function delete_data_for_user(approved_contextlist $contextlist) { } } diff --git a/blocks/recent_activity/lang/en/block_recent_activity.php b/blocks/recent_activity/lang/en/block_recent_activity.php index 3f6eadb60fac..a569a7a533f4 100644 --- a/blocks/recent_activity/lang/en/block_recent_activity.php +++ b/blocks/recent_activity/lang/en/block_recent_activity.php @@ -25,6 +25,13 @@ $string['pluginname'] = 'Recent activity'; $string['privacy:metadata'] = 'The recent activity block contains a cache of data stored elsewhere in Moodle.'; +$string['privacy:metadata:block_recent_activity'] = 'Temporary log of recent teacher activity. Removed after two days'; +$string['privacy:metadata:block_recent_activity:action'] = 'Action: created, updated or deleted'; +$string['privacy:metadata:block_recent_activity:cmid'] = 'Course module id'; +$string['privacy:metadata:block_recent_activity:courseid'] = 'Course id'; +$string['privacy:metadata:block_recent_activity:modname'] = 'Module type name (for delete action)'; +$string['privacy:metadata:block_recent_activity:timecreated'] = 'Time when action was performed'; +$string['privacy:metadata:block_recent_activity:userid'] = 'User performing the action'; $string['recent_activity:addinstance'] = 'Add a new recent activity block'; $string['recent_activity:viewaddupdatemodule'] = 'View added and updated modules in recent activity block'; $string['recent_activity:viewdeletemodule'] = 'View deleted modules in recent activity block'; diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 85244fa463c7..6bc2c5d00e42 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -1561,6 +1561,16 @@ $string['privacy:metadata:events_queue:stackdump'] = 'Any stacktrace associated with this event.'; $string['privacy:metadata:events_queue:timecreated'] = 'The time that this event was created.'; $string['privacy:metadata:events_queue:userid'] = 'The userid associated with this event.'; +$string['privacy:metadata:log'] = 'A collection of past events'; +$string['privacy:metadata:log:action'] = 'A description of the action'; +$string['privacy:metadata:log:cmid'] = 'cmid'; +$string['privacy:metadata:log:course'] = 'course'; +$string['privacy:metadata:log:info'] = 'Additional information'; +$string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event'; +$string['privacy:metadata:log:module'] = 'module'; +$string['privacy:metadata:log:time'] = 'The date at wich the action took place'; +$string['privacy:metadata:log:url'] = 'The URL related to the event'; +$string['privacy:metadata:log:userid'] = 'The ID of the user who performed the action'; $string['privacy:metadata:task_adhoc'] = 'The status of adhoc tasks.'; $string['privacy:metadata:task_adhoc:component'] = 'The component owning the task.'; $string['privacy:metadata:task_adhoc:nextruntime'] = 'The earliest time to run this task.'; diff --git a/lang/en/user.php b/lang/en/user.php index 8a4d933a1ff7..8b0fd76ba30e 100644 --- a/lang/en/user.php +++ b/lang/en/user.php @@ -75,6 +75,10 @@ $string['privacy:metadata:mnethostid'] = 'An identifier for the mnet host if used.'; $string['privacy:metadata:model'] = 'The device name, occam or iPhone etc..'; $string['privacy:metadata:msn'] = 'The MSN identifier of the user.'; +$string['privacy:metadata:my_pages'] = 'User pages - dashboard and profile. This table does not contain personal data and only used to link dashboard blocks to users'; +$string['privacy:metadata:my_pages:name'] = 'Page name'; +$string['privacy:metadata:my_pages:private'] = 'Whether or not the page is private (dashboard) or public (profile)'; +$string['privacy:metadata:my_pages:userid'] = 'The user who owns this page or 0 for system defaults'; $string['privacy:metadata:password'] = 'The password for this user to log into the system.'; $string['privacy:metadata:passwordresettablesummary'] = 'A table tracking password reset confirmation tokens'; $string['privacy:metadata:passwordtablesummary'] = 'A rotating log of hashes of previously used passwords for the user.'; @@ -87,6 +91,10 @@ $string['privacy:metadata:requester'] = 'An identifier to a user that requested this course.'; $string['privacy:metadata:requestsummary'] = 'Stores information about requests for courses that users make.'; $string['privacy:metadata:suspended'] = 'A flag to show if the user has been suspended on this system.'; +$string['privacy:metadata:user_preferences'] = 'Preferences associated with the given user'; +$string['privacy:metadata:user_preferences:name'] = 'Preference name'; +$string['privacy:metadata:user_preferences:userid'] = 'User id'; +$string['privacy:metadata:user_preferences:value'] = 'Preference value'; $string['privacy:metadata:username'] = 'The username for this user.'; $string['privacy:metadata:secret'] = 'Secret.. not sure.'; $string['privacy:metadata:sessdata'] = 'Session content'; diff --git a/lib/classes/privacy/provider.php b/lib/classes/privacy/provider.php index e9e737f2190d..0e41d71adc85 100644 --- a/lib/classes/privacy/provider.php +++ b/lib/classes/privacy/provider.php @@ -91,6 +91,16 @@ public static function get_metadata(collection $collection) : collection { 'timecreated' => 'privacy:metadata:events_queue:timecreated', ], 'privacy:metadata:events_queue'); + // The log table is defined in core but used in logstore_legacy. + $collection->add_database_table('log', [ + 'time' => 'privacy:metadata:log:time', + 'userid' => 'privacy:metadata:log:userid', + 'ip' => 'privacy:metadata:log:ip', + 'action' => 'privacy:metadata:log:action', + 'url' => 'privacy:metadata:log:url', + 'info' => 'privacy:metadata:log:info' + ], 'privacy:metadata:log'); + return $collection; } diff --git a/mod/chat/classes/privacy/provider.php b/mod/chat/classes/privacy/provider.php index 84ed1176db5d..92cf51add9f4 100644 --- a/mod/chat/classes/privacy/provider.php +++ b/mod/chat/classes/privacy/provider.php @@ -63,10 +63,26 @@ public static function get_metadata(collection $collection) : collection { 'timestamp' => 'privacy:metadata:messages:timestamp', ], 'privacy:metadata:messages'); - // The tables chat_messages_current and chat_users are not reported here + // The tables chat_messages_current and chat_users are not exported/deleted // because they are considered as short-lived data and are deleted on a - // regular basis by cron, or during normal requests. MDL-62006 was raised - // to discuss and/or implement support for those tables. + // regular basis by cron, or during normal requests. TODO MDL-62006. + + $collection->add_database_table('chat_messages_current', [ + 'userid' => 'privacy:metadata:messages:userid', + 'message' => 'privacy:metadata:messages:message', + 'issystem' => 'privacy:metadata:messages:issystem', + 'timestamp' => 'privacy:metadata:messages:timestamp' + ], 'privacy:metadata:chat_messages_current'); + + $collection->add_database_table('chat_users', [ + 'userid' => 'privacy:metadata:chat_users:userid', + 'version' => 'privacy:metadata:chat_users:version', + 'ip' => 'privacy:metadata:chat_users:ip', + 'firstping' => 'privacy:metadata:chat_users:firstping', + 'lastping' => 'privacy:metadata:chat_users:lastping', + 'lastmessageping' => 'privacy:metadata:chat_users:lastmessageping', + 'lang' => 'privacy:metadata:chat_users:lang' + ], 'privacy:metadata:chat_users'); return $collection; } diff --git a/mod/chat/lang/en/chat.php b/mod/chat/lang/en/chat.php index 66772ae9359e..50d75bea0f1f 100644 --- a/mod/chat/lang/en/chat.php +++ b/mod/chat/lang/en/chat.php @@ -118,6 +118,15 @@ $string['pastchats'] = 'Past chat sessions'; $string['pluginadministration'] = 'Chat administration'; $string['pluginname'] = 'Chat'; +$string['privacy:metadata:chat_messages_current'] = 'Current chat session. This data is temporary and is deleted after the chat session is deleted'; +$string['privacy:metadata:chat_users'] = 'Keeps track of which users are in which chat rooms'; +$string['privacy:metadata:chat_users:firstping'] = 'Time of the first access to chat room'; +$string['privacy:metadata:chat_users:ip'] = 'User IP'; +$string['privacy:metadata:chat_users:lang'] = 'User language'; +$string['privacy:metadata:chat_users:lastmessageping'] = 'Time of the last message in this chat room'; +$string['privacy:metadata:chat_users:lastping'] = 'Time of the last access to chat room'; +$string['privacy:metadata:chat_users:userid'] = 'User id'; +$string['privacy:metadata:chat_users:version'] = 'How user accessed the chat (sockets/basic/ajax/header_js)'; $string['privacy:metadata:messages'] = 'A record of the messages sent during a chat session'; $string['privacy:metadata:messages:issystem'] = 'Whether the message is a system-generated message'; $string['privacy:metadata:messages:message'] = 'The message'; diff --git a/mod/forum/classes/privacy/provider.php b/mod/forum/classes/privacy/provider.php index 4bb37ae4a4ce..bf4b5e3bc7fd 100644 --- a/mod/forum/classes/privacy/provider.php +++ b/mod/forum/classes/privacy/provider.php @@ -117,6 +117,14 @@ public static function get_metadata(collection $items) : collection { 'forumid' => 'privacy:metadata:forum_track_prefs:forumid', ], 'privacy:metadata:forum_track_prefs'); + // The 'forum_queue' table stores temporary data that is not exported/deleted. + $items->add_database_table('forum_queue', [ + 'userid' => 'privacy:metadata:forum_queue:userid', + 'discussionid' => 'privacy:metadata:forum_queue:discussionid', + 'postid' => 'privacy:metadata:forum_queue:postid', + 'timemodified' => 'privacy:metadata:forum_queue:timemodified' + ], 'privacy:metadata:forum_queue'); + // Forum posts can be tagged and rated. $items->link_subsystem('core_tag', 'privacy:metadata:core_tag'); $items->link_subsystem('core_rating', 'privacy:metadata:core_rating'); diff --git a/mod/forum/lang/en/forum.php b/mod/forum/lang/en/forum.php index 49d66882038c..c9eedbf32338 100644 --- a/mod/forum/lang/en/forum.php +++ b/mod/forum/lang/en/forum.php @@ -459,6 +459,11 @@ $string['privacy:metadata:forum_posts:subject'] = 'The subject of the forum post.'; $string['privacy:metadata:forum_posts:totalscore'] = 'The message of the forum post.'; $string['privacy:metadata:forum_posts:userid'] = 'The ID of the user who authored the forum post.'; +$string['privacy:metadata:forum_queue'] = 'Temporary log of posts that will be mailed in digest form'; +$string['privacy:metadata:forum_queue:discussionid'] = 'Forum discussion id'; +$string['privacy:metadata:forum_queue:postid'] = 'Forum post id'; +$string['privacy:metadata:forum_queue:timemodified'] = 'The modified time of the original post'; +$string['privacy:metadata:forum_queue:userid'] = 'User who needs to be notified of the post'; $string['privacy:metadata:forum_read'] = 'Information about which posts have been read by the user.'; $string['privacy:metadata:forum_read:discussionid'] = 'The discussion that the post is in.'; $string['privacy:metadata:forum_read:firstread'] = 'The first time that the post was read.'; diff --git a/user/classes/privacy/provider.php b/user/classes/privacy/provider.php index 1619f5c151b7..b233ff6ba1a5 100644 --- a/user/classes/privacy/provider.php +++ b/user/classes/privacy/provider.php @@ -154,6 +154,18 @@ public static function get_metadata(collection $collection) : collection { 'requester' => 'privacy:metadata:requester' ]; + $mypages = [ + 'userid' => 'privacy:metadata:my_pages:userid', + 'name' => 'privacy:metadata:my_pages:name', + 'private' => 'privacy:metadata:my_pages:private', + ]; + + $userpreferences = [ + 'userid' => 'privacy:metadata:user_preferences:userid', + 'name' => 'privacy:metadata:user_preferences:name', + 'value' => 'privacy:metadata:user_preferences:value' + ]; + $collection->add_database_table('user', $userfields, 'privacy:metadata:usertablesummary'); $collection->add_database_table('user_password_history', $passwordhistory, 'privacy:metadata:passwordtablesummary'); $collection->add_database_table('user_password_resets', $userpasswordresets, 'privacy:metadata:passwordresettablesummary'); @@ -161,6 +173,8 @@ public static function get_metadata(collection $collection) : collection { $collection->add_database_table('user_devices', $userdevices, 'privacy:metadata:devicetablesummary'); $collection->add_database_table('course_request', $courserequest, 'privacy:metadata:requestsummary'); $collection->add_database_table('sessions', $usersessions, 'privacy:metadata:sessiontablesummary'); + $collection->add_database_table('my_pages', $mypages, 'privacy:metadata:my_pages'); + $collection->add_database_table('user_preferences', $userpreferences, 'privacy:metadata:user_preferences'); $collection->add_subsystem_link('core_files', [], 'privacy:metadata:filelink'); return $collection; From 6f0e3c70a97927cb348bb031ddf76dd756cc2439 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 16 May 2018 13:47:33 +0800 Subject: [PATCH 12/35] MDL-62463 mod_glossary: Fix SQL query The query was doing: WHERE c.id ... AND ... OR ... OR ... Which equates to: WHERE (c.id ... AND ...) OR ... OR ... Adding parens to: WHERE (c.id ... AND (... OR ... OR ...)) --- mod/glossary/classes/privacy/provider.php | 31 +++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/mod/glossary/classes/privacy/provider.php b/mod/glossary/classes/privacy/provider.php index c610a41c583c..dcf1ecc663cd 100644 --- a/mod/glossary/classes/privacy/provider.php +++ b/mod/glossary/classes/privacy/provider.php @@ -130,18 +130,33 @@ public static function export_user_data(approved_contextlist $contextlist) { FROM {glossary_entries} ge JOIN {glossary} g ON ge.glossaryid = g.id JOIN {course_modules} cm ON g.id = cm.instance - JOIN {context} c ON cm.id = c.instanceid + JOIN {modules} m ON cm.module = m.id AND m.name = :modulename + JOIN {context} c ON cm.id = c.instanceid AND c.contextlevel = :contextlevel WHERE c.id {$contextsql} - AND ge.userid = :userid - OR EXISTS (SELECT 1 FROM {comments} com WHERE com.commentarea = :commentarea AND com.itemid = ge.id - AND com.userid = :commentuserid) - OR EXISTS (SELECT 1 FROM {rating} r WHERE r.contextid = c.id AND r.itemid = ge.id - AND r.component = :ratingcomponent - AND r.ratingarea = :ratingarea - AND r.userid = :ratinguserid) + AND ( + ge.userid = :userid + OR + EXISTS ( + SELECT 1 + FROM {comments} com + WHERE com.commentarea = :commentarea AND com.itemid = ge.id AND com.userid = :commentuserid + ) + OR + EXISTS ( + SELECT 1 + FROM {rating} r + WHERE r.contextid = c.id + AND r.itemid = ge.id + AND r.component = :ratingcomponent + AND r.ratingarea = :ratingarea + AND r.userid = :ratinguserid + ) + ) ORDER BY ge.id, cm.id"; $params = [ 'userid' => $user->id, + 'modulename' => 'glossary', + 'contextlevel' => CONTEXT_MODULE, 'commentarea' => 'glossary_entry', 'commentuserid' => $user->id, 'ratingcomponent' => 'mod_glossary', From 3a20fb23a52a936f5e2f8589a7699d47f3500795 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Wed, 9 May 2018 16:27:07 +0800 Subject: [PATCH 13/35] MDL-62147 privacy: unittest ensures that all tables covered --- privacy/tests/provider_test.php | 73 ++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/privacy/tests/provider_test.php b/privacy/tests/provider_test.php index 496d99678b45..0ca9768083b9 100644 --- a/privacy/tests/provider_test.php +++ b/privacy/tests/provider_test.php @@ -46,7 +46,10 @@ class provider_testcase extends advanced_testcase { * @return array the array of frankenstyle component names with the relevant class name. */ public function get_component_list() { - $components = []; + $components = ['core' => [ + 'component' => 'core', + 'classname' => manager::get_provider_classname_for_component('core') + ]]; // Get all plugins. $plugintypes = \core_component::get_plugin_types(); foreach ($plugintypes as $plugintype => $typedir) { @@ -204,4 +207,72 @@ protected static function component_implements($providerclass, $interface) { return false; } + /** + * Finds user fields in a table + * + * Returns fields that have foreign key to user table and fields that are named 'userid'. + * + * @param xmldb_table $table + * @return array + */ + protected function get_userid_fields(xmldb_table $table) { + $userfields = []; + + // Find all fields that have a foreign key to 'id' field in 'user' table. + $keys = $table->getKeys(); + foreach ($keys as $key) { + $reffields = $key->getRefFields(); + $fields = $key->getFields(); + if ($key->getRefTable() === 'user' && count($reffields) == 1 && $reffields[0] == 'id' && count($fields) == 1) { + $userfields[$fields[0]] = $fields[0]; + } + } + // Find fields with the name 'userid' even if they don't have a foreign key. + $fields = $table->getFields(); + foreach ($fields as $field) { + if ($field->getName() == 'userid') { + $userfields['userid'] = 'userid'; + } + } + + return $userfields; + } + + /** + * Test that all tables with user fields are covered by metadata providers + */ + public function test_table_coverage() { + global $DB; + $dbman = $DB->get_manager(); + $schema = $dbman->get_install_xml_schema(); + $tables = []; + foreach ($schema->getTables() as $table) { + if ($table->getName() === 'role_sortorder') { + // TODO MDL-62459 this table is not used anywhere. Remove the table and this statement. + continue; + } + if ($fields = $this->get_userid_fields($table)) { + $tables[$table->getName()] = ' - ' . $table->getName() . ' (' . join(', ', $fields) . ')'; + } + } + + $componentlist = $this->metadata_provider_provider(); + foreach ($componentlist as $componentarray) { + $component = $componentarray['component']; + $classname = $componentarray['classname']; + $collection = new collection($component); + $metadata = $classname::get_metadata($collection); + foreach ($metadata->get_collection() as $item) { + if ($item instanceof database_table) { + unset($tables[$item->get_name()]); + } + } + } + + if ($tables) { + $this->fail("The following tables with user fields must be covered with metadata providers: \n". + join("\n", $tables)); + } + + } } From f2f7255f7bee67dad2d316eb458faddf0692b020 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Wed, 16 May 2018 10:35:48 +0800 Subject: [PATCH 14/35] MDL-62147 portfolio: add missing tables to privacy provider --- lang/en/portfolio.php | 10 +++ portfolio/classes/privacy/provider.php | 92 ++++++++++++++++++++--- portfolio/tests/privacy_provider_test.php | 26 ++++++- 3 files changed, 117 insertions(+), 11 deletions(-) diff --git a/lang/en/portfolio.php b/lang/en/portfolio.php index 792cb6ec7d6e..32d846aa8139 100644 --- a/lang/en/portfolio.php +++ b/lang/en/portfolio.php @@ -170,6 +170,16 @@ $string['privacy:metadata:name'] = 'Name of the preference.'; $string['privacy:metadata:instance'] = 'Identifier for the portfolio.'; $string['privacy:metadata:instancesummary'] = 'This stores portfolio both instances and preferences for the portfolios user is using.'; +$string['privacy:metadata:portfolio_log'] = 'Log of portfolio transfers (used to later check for duplicates)'; +$string['privacy:metadata:portfolio_log:caller_class'] = 'Name of the class used to create the transfer'; +$string['privacy:metadata:portfolio_log:caller_component'] = 'Component name responsible for exporting'; +$string['privacy:metadata:portfolio_log:time'] = 'Time of transfer (in the case of a queued transfer this is the time the actual transfer ran, not when the user started)'; +$string['privacy:metadata:portfolio_log:userid'] = 'User who exported content'; +$string['privacy:metadata:portfolio_tempdata'] = 'Stores temporary data for portfolio exports, cleaned by cron after one day'; +$string['privacy:metadata:portfolio_tempdata:data'] = 'Export data'; +$string['privacy:metadata:portfolio_tempdata:expirytime'] = 'Time this record will expire'; +$string['privacy:metadata:portfolio_tempdata:instance'] = 'Portfolio plugin instance being used'; +$string['privacy:metadata:portfolio_tempdata:userid'] = 'User performing export'; $string['privacy:metadata:value'] = 'Value for the preference'; $string['privacy:metadata:userid'] = 'The user Identifier.'; $string['privacy:path'] = 'Portfolio instances'; diff --git a/portfolio/classes/privacy/provider.php b/portfolio/classes/privacy/provider.php index 3f1b7951a4a6..8ea61c9f3f1d 100644 --- a/portfolio/classes/privacy/provider.php +++ b/portfolio/classes/privacy/provider.php @@ -29,6 +29,7 @@ use core_privacy\local\request\context; use core_privacy\local\request\contextlist; use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\transform; /** * Provider for the portfolio API. @@ -56,6 +57,22 @@ public static function get_metadata(collection $collection) : collection { 'name' => 'privacy:metadata:name', 'value' => 'privacy:metadata:value' ], 'privacy:metadata:instancesummary'); + + $collection->add_database_table('portfolio_log', [ + 'userid' => 'privacy:metadata:portfolio_log:userid', + 'time' => 'privacy:metadata:portfolio_log:time', + 'caller_class' => 'privacy:metadata:portfolio_log:caller_class', + 'caller_component' => 'privacy:metadata:portfolio_log:caller_component', + ], 'privacy:metadata:portfolio_log'); + + // Temporary data is not exported/deleted in privacy API. It is cleaned by cron. + $collection->add_database_table('portfolio_tempdata', [ + 'data' => 'privacy:metadata:portfolio_tempdata:data', + 'expirytime' => 'privacy:metadata:portfolio_tempdata:expirytime', + 'userid' => 'privacy:metadata:portfolio_tempdata:userid', + 'instance' => 'privacy:metadata:portfolio_tempdata:instance', + ], 'privacy:metadata:portfolio_tempdata'); + $collection->add_plugintype_link('portfolio', [], 'privacy:metadata'); return $collection; } @@ -69,9 +86,11 @@ public static function get_metadata(collection $collection) : collection { public static function get_contexts_for_userid(int $userid) : contextlist { $sql = "SELECT ctx.id FROM {context} ctx - JOIN {portfolio_instance_user} piu ON ctx.instanceid = piu.userid AND ctx.contextlevel = :usercontext - WHERE piu.userid = :userid"; - $params = ['userid' => $userid, 'usercontext' => CONTEXT_USER]; + WHERE ctx.instanceid = :userid AND ctx.contextlevel = :usercontext + AND (EXISTS (SELECT 1 FROM {portfolio_instance_user} WHERE userid = :userid1) OR + EXISTS (SELECT 1 FROM {portfolio_log} WHERE userid = :userid2)) + "; + $params = ['userid' => $userid, 'usercontext' => CONTEXT_USER, 'userid1' => $userid, 'userid2' => $userid]; $contextlist = new contextlist(); $contextlist->add_from_sql($sql, $params); return $contextlist; @@ -95,16 +114,63 @@ public static function export_user_data(approved_contextlist $contextlist) { } }); + if (empty($correctusercontext)) { + return; + } + $usercontext = array_shift($correctusercontext); + $sql = "SELECT pi.id AS instanceid, pi.name, + piu.id AS preferenceid, piu.name AS preference, piu.value, + pl.id AS logid, pl.time AS logtime, pl.caller_class, pl.caller_file, + pl.caller_component, pl.returnurl, pl.continueurl + FROM {portfolio_instance} pi + LEFT JOIN {portfolio_instance_user} piu ON piu.instance = pi.id AND piu.userid = :userid1 + LEFT JOIN {portfolio_log} pl ON pl.portfolio = pi.id AND pl.userid = :userid2 + WHERE piu.id IS NOT NULL OR pl.id IS NOT NULL"; + $params = ['userid1' => $usercontext->instanceid, 'userid2' => $usercontext->instanceid]; + $instances = []; + $rs = $DB->get_recordset_sql($sql, $params); + foreach ($rs as $record) { + $instances += [$record->name => + (object)[ + 'name' => $record->name, + 'preferences' => [], + 'logs' => [], + ] + ]; + if ($record->preferenceid) { + $instances[$record->name]->preferences[$record->preferenceid] = (object)[ + 'name' => $record->preference, + 'value' => $record->value, + ]; + } + if ($record->logid) { + $instances[$record->name]->logs[$record->logid] = (object)[ + 'time' => transform::datetime($record->logtime), + 'caller_class' => $record->caller_class, + 'caller_file' => $record->caller_file, + 'caller_component' => $record->caller_component, + 'returnurl' => $record->returnurl, + 'continueurl' => $record->continueurl + ]; + } + } + $rs->close(); - $sql = "SELECT pi.name, piu.name AS preference, piu.value - FROM {portfolio_instance_user} piu - JOIN {portfolio_instance} pi ON piu.instance = pi.id - WHERE piu.userid = :userid"; - $params = ['userid' => $usercontext->instanceid]; - $instances = $DB->get_records_sql($sql, $params); if (!empty($instances)) { + foreach ($instances as &$instance) { + if (!empty($instance->preferences)) { + $instance->preferences = array_values($instance->preferences); + } else { + unset($instance->preferences); + } + if (!empty($instance->logs)) { + $instance->logs = array_values($instance->logs); + } else { + unset($instance->logs); + } + } \core_privacy\local\request\writer::with_context($contextlist->current())->export_data( [get_string('privacy:path', 'portfolio')], (object) $instances); } @@ -120,6 +186,8 @@ public static function delete_data_for_all_users_in_context(\context $context) { // Context could be anything, BEWARE! if ($context->contextlevel == CONTEXT_USER) { $DB->delete_records('portfolio_instance_user', ['userid' => $context->instanceid]); + $DB->delete_records('portfolio_tempdata', ['userid' => $context->instanceid]); + $DB->delete_records('portfolio_log', ['userid' => $context->instanceid]); } } @@ -141,9 +209,15 @@ public static function delete_data_for_user(approved_contextlist $contextlist) { } }); + if (empty($correctusercontext)) { + return; + } + $usercontext = array_shift($correctusercontext); $DB->delete_records('portfolio_instance_user', ['userid' => $usercontext->instanceid]); + $DB->delete_records('portfolio_tempdata', ['userid' => $usercontext->instanceid]); + $DB->delete_records('portfolio_log', ['userid' => $usercontext->instanceid]); } /** diff --git a/portfolio/tests/privacy_provider_test.php b/portfolio/tests/privacy_provider_test.php index 93ffb98167cb..a1e2a663969d 100644 --- a/portfolio/tests/privacy_provider_test.php +++ b/portfolio/tests/privacy_provider_test.php @@ -47,6 +47,22 @@ protected function create_portfolio_data($plugin, $name, $user, $preference, $va 'value' => $value ]; $DB->insert_record('portfolio_instance_user', $userinstance); + + $DB->insert_record('portfolio_log', [ + 'portfolio' => $portfolioinstance->id, + 'userid' => $user->id, + 'caller_class' => 'forum_portfolio_caller', + 'caller_component' => 'mod_forum', + 'time' => time(), + ]); + + $DB->insert_record('portfolio_log', [ + 'portfolio' => $portfolioinstance->id, + 'userid' => $user->id, + 'caller_class' => 'workshop_portfolio_caller', + 'caller_component' => 'mod_workshop', + 'time' => time(), + ]); } /** @@ -57,9 +73,11 @@ public function test_get_metadata() { $collection = \core_portfolio\privacy\provider::get_metadata($collection); $this->assertNotEmpty($collection); $items = $collection->get_collection(); - $this->assertEquals(2, count($items)); + $this->assertEquals(4, count($items)); $this->assertInstanceOf(\core_privacy\local\metadata\types\database_table::class, $items[0]); - $this->assertInstanceOf(\core_privacy\local\metadata\types\plugintype_link::class, $items[1]); + $this->assertInstanceOf(\core_privacy\local\metadata\types\database_table::class, $items[1]); + $this->assertInstanceOf(\core_privacy\local\metadata\types\database_table::class, $items[2]); + $this->assertInstanceOf(\core_privacy\local\metadata\types\plugintype_link::class, $items[3]); } /** @@ -105,6 +123,7 @@ public function test_delete_data_for_all_users_in_context() { \core_portfolio\privacy\provider::delete_data_for_all_users_in_context($systemcontext); $records = $DB->get_records('portfolio_instance_user'); $this->assertCount(2, $records); + $this->assertCount(4, $DB->get_records('portfolio_log')); $context = context_user::instance($user1->id); \core_portfolio\privacy\provider::delete_data_for_all_users_in_context($context); $records = $DB->get_records('portfolio_instance_user'); @@ -112,6 +131,7 @@ public function test_delete_data_for_all_users_in_context() { $this->assertCount(1, $records); $data = array_shift($records); $this->assertEquals($user2->id, $data->userid); + $this->assertCount(2, $DB->get_records('portfolio_log')); } /** @@ -128,6 +148,7 @@ public function test_delete_data_for_user() { $records = $DB->get_records('portfolio_instance_user'); $this->assertCount(2, $records); + $this->assertCount(4, $DB->get_records('portfolio_log')); $context = context_user::instance($user1->id); $contextlist = new \core_privacy\local\request\approved_contextlist($user1, 'core_portfolio', [$context->id]); @@ -137,5 +158,6 @@ public function test_delete_data_for_user() { $this->assertCount(1, $records); $data = array_shift($records); $this->assertEquals($user2->id, $data->userid); + $this->assertCount(2, $DB->get_records('portfolio_log')); } } From 5c1b473931c674070fa5970e6508a1fc1931da59 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Wed, 16 May 2018 10:44:47 +0800 Subject: [PATCH 15/35] MDL-62147 core_grades: Declare metadata of scales and import tables --- grade/classes/privacy/provider.php | 36 +++++++++++++++++++++++++++++- lang/en/grades.php | 25 +++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/grade/classes/privacy/provider.php b/grade/classes/privacy/provider.php index 282253a8f5ae..a9c033f6b282 100644 --- a/grade/classes/privacy/provider.php +++ b/grade/classes/privacy/provider.php @@ -97,9 +97,43 @@ public static function get_metadata(collection $collection) : collection { 'loggeduser' => 'privacy:metadata:history:loggeduser', ]), 'privacy:metadata:gradeshistory'); - // The table grade_import_values is not reported because its data is temporary and only + $collection->add_database_table('scale_history', [ + 'action' => 'privacy:metadata:scale_history:action', + 'timemodified' => 'privacy:metadata:scale_history:timemodified', + 'loggeduser' => 'privacy:metadata:scale_history:loggeduser', + 'userid' => 'privacy:metadata:scale_history:userid', + 'name' => 'privacy:metadata:scale_history:name', + 'scale' => 'privacy:metadata:scale_history:scale', + 'description' => 'privacy:metadata:scale_history:description' + ], 'privacy:metadata:scale_history'); + + // The following tables are reported but not exported/deleted because their data is temporary and only // used during an import. It's content is deleted after a successful, or failed, import. + $collection->add_database_table('grade_import_newitem', [ + 'itemname' => 'privacy:metadata:grade_import_newitem:itemname', + 'importcode' => 'privacy:metadata:grade_import_newitem:importcode', + 'importer' => 'privacy:metadata:grade_import_newitem:importer' + ], 'privacy:metadata:grade_import_newitem'); + + $collection->add_database_table('grade_import_values', [ + 'userid' => 'privacy:metadata:grade_import_values:userid', + 'finalgrade' => 'privacy:metadata:grade_import_values:finalgrade', + 'feedback' => 'privacy:metadata:grade_import_values:feedback', + 'importcode' => 'privacy:metadata:grade_import_values:importcode', + 'importer' => 'privacy:metadata:grade_import_values:importer', + 'importonlyfeedback' => 'privacy:metadata:grade_import_values:importonlyfeedback' + ], 'privacy:metadata:grade_import_values'); + + // Table 'scale' stores userid of the user who created a scale. This is not considered to be user data. + $collection->add_database_table('scale', [ + 'userid' => 'privacy:metadata:scale:userid', + 'name' => 'privacy:metadata:scale:name', + 'scale' => 'privacy:metadata:scale:scale', + 'timemodified' => 'privacy:metadata:scale:timemodified', + 'description' => 'privacy:metadata:scale:description' + ], 'privacy:metadata:scale'); + return $collection; } diff --git a/lang/en/grades.php b/lang/en/grades.php index 1d31a0ea1689..feca6be41c4f 100644 --- a/lang/en/grades.php +++ b/lang/en/grades.php @@ -324,6 +324,31 @@ $string['graderreport'] = 'Grader report'; $string['grades'] = 'Grades'; $string['gradesforuser'] = 'Grades for {$a->user}'; +$string['privacy:metadata:grade_import_newitem'] = 'Temporary table for storing new grade_item names from grade import'; +$string['privacy:metadata:grade_import_newitem:importcode'] = 'A unique batch code for identifying one batch of imports'; +$string['privacy:metadata:grade_import_newitem:importer'] = 'User importing the data'; +$string['privacy:metadata:grade_import_newitem:itemname'] = 'New grade item name'; +$string['privacy:metadata:grade_import_values'] = 'Temporary table for importing grades'; +$string['privacy:metadata:grade_import_values:feedback'] = 'Grade feedback'; +$string['privacy:metadata:grade_import_values:finalgrade'] = 'Raw grade value'; +$string['privacy:metadata:grade_import_values:importcode'] = 'A unique batch code for identifying one batch of imports'; +$string['privacy:metadata:grade_import_values:importer'] = 'User importing the data'; +$string['privacy:metadata:grade_import_values:importonlyfeedback'] = 'Flag if only feedback was imported'; +$string['privacy:metadata:grade_import_values:userid'] = 'User whose grade was imported'; +$string['privacy:metadata:scale'] = 'Grading scales, store ID of the user who created the scale. Not considered personal information'; +$string['privacy:metadata:scale:description'] = 'Description of the scale'; +$string['privacy:metadata:scale:name'] = 'Name of the scale'; +$string['privacy:metadata:scale:scale'] = 'Values in the scale'; +$string['privacy:metadata:scale:timemodified'] = 'Time when the scale was last modified'; +$string['privacy:metadata:scale:userid'] = 'ID of the user who created the scale'; +$string['privacy:metadata:scale_history'] = 'History table'; +$string['privacy:metadata:scale_history:action'] = 'created/modified/deleted constants'; +$string['privacy:metadata:scale_history:description'] = 'description'; +$string['privacy:metadata:scale_history:loggeduser'] = 'the userid of the person who last modified this outcome'; +$string['privacy:metadata:scale_history:name'] = 'name'; +$string['privacy:metadata:scale_history:scale'] = 'scale'; +$string['privacy:metadata:scale_history:timemodified'] = 'The last time this grade_item was modified'; +$string['privacy:metadata:scale_history:userid'] = 'userid'; $string['singleview'] = 'Single view for {$a}'; $string['gradesonly'] = 'Change to grades only'; $string['gradesmoduledeletionpendingwarning'] = 'Warning: Activity deletion in progress! Some grades are about to be removed.'; From 9510c3c535e59185f4cf61bd76e50b2dadef8360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Massart?= Date: Wed, 16 May 2018 12:34:25 +0800 Subject: [PATCH 16/35] MDL-62147 core_grades: Report contexts and data related to scales --- grade/classes/privacy/provider.php | 161 ++++++++++++++++++++++++----- grade/tests/privacy_test.php | 132 ++++++++++++++++++++++- lang/en/grades.php | 40 +++---- 3 files changed, 280 insertions(+), 53 deletions(-) diff --git a/grade/classes/privacy/provider.php b/grade/classes/privacy/provider.php index a9c033f6b282..39ec59c811af 100644 --- a/grade/classes/privacy/provider.php +++ b/grade/classes/privacy/provider.php @@ -60,6 +60,7 @@ class provider implements */ public static function get_metadata(collection $collection) : collection { + // Tables without 'real' user information. $collection->add_database_table('grade_outcomes', [ 'timemodified' => 'privacy:metadata:outcomes:timemodified', 'usermodified' => 'privacy:metadata:outcomes:usermodified', @@ -80,6 +81,18 @@ public static function get_metadata(collection $collection) : collection { 'loggeduser' => 'privacy:metadata:history:loggeduser', ], 'privacy:metadata:itemshistory'); + $collection->add_database_table('scale', [ + 'userid' => 'privacy:metadata:scale:userid', + 'timemodified' => 'privacy:metadata:scale:timemodified', + ], 'privacy:metadata:scale'); + + $collection->add_database_table('scale_history', [ + 'userid' => 'privacy:metadata:scale:userid', + 'timemodified' => 'privacy:metadata:history:timemodified', + 'loggeduser' => 'privacy:metadata:history:loggeduser', + ], 'privacy:metadata:scalehistory'); + + // Table with user information. $gradescommonfields = [ 'userid' => 'privacy:metadata:grades:userid', 'usermodified' => 'privacy:metadata:grades:usermodified', @@ -97,16 +110,6 @@ public static function get_metadata(collection $collection) : collection { 'loggeduser' => 'privacy:metadata:history:loggeduser', ]), 'privacy:metadata:gradeshistory'); - $collection->add_database_table('scale_history', [ - 'action' => 'privacy:metadata:scale_history:action', - 'timemodified' => 'privacy:metadata:scale_history:timemodified', - 'loggeduser' => 'privacy:metadata:scale_history:loggeduser', - 'userid' => 'privacy:metadata:scale_history:userid', - 'name' => 'privacy:metadata:scale_history:name', - 'scale' => 'privacy:metadata:scale_history:scale', - 'description' => 'privacy:metadata:scale_history:description' - ], 'privacy:metadata:scale_history'); - // The following tables are reported but not exported/deleted because their data is temporary and only // used during an import. It's content is deleted after a successful, or failed, import. @@ -125,15 +128,6 @@ public static function get_metadata(collection $collection) : collection { 'importonlyfeedback' => 'privacy:metadata:grade_import_values:importonlyfeedback' ], 'privacy:metadata:grade_import_values'); - // Table 'scale' stores userid of the user who created a scale. This is not considered to be user data. - $collection->add_database_table('scale', [ - 'userid' => 'privacy:metadata:scale:userid', - 'name' => 'privacy:metadata:scale:name', - 'scale' => 'privacy:metadata:scale:scale', - 'timemodified' => 'privacy:metadata:scale:timemodified', - 'description' => 'privacy:metadata:scale:description' - ], 'privacy:metadata:scale'); - return $collection; } @@ -152,18 +146,29 @@ public static function get_contexts_for_userid(int $userid) : \core_privacy\loca FROM {grade_outcomes} go JOIN {context} ctx ON (go.courseid > 0 AND ctx.instanceid = go.courseid AND ctx.contextlevel = :courselevel) - OR (ctx.id = :syscontextid) + OR ((go.courseid IS NULL OR go.courseid < 1) AND ctx.id = :syscontextid) WHERE go.usermodified = :userid"; $params = ['userid' => $userid, 'courselevel' => CONTEXT_COURSE, 'syscontextid' => SYSCONTEXTID]; $contextlist->add_from_sql($sql, $params); - // Add where appear in the history of outcomes, categories or items. + // Add where we modified scales. + $sql = " + SELECT DISTINCT ctx.id + FROM {scale} s + JOIN {context} ctx + ON (s.courseid > 0 AND ctx.instanceid = s.courseid AND ctx.contextlevel = :courselevel) + OR (s.courseid = 0 AND ctx.id = :syscontextid) + WHERE s.userid = :userid"; + $params = ['userid' => $userid, 'courselevel' => CONTEXT_COURSE, 'syscontextid' => SYSCONTEXTID]; + $contextlist->add_from_sql($sql, $params); + + // Add where appear in the history of outcomes, categories, scales or items. $sql = " SELECT DISTINCT ctx.id FROM {context} ctx LEFT JOIN {grade_outcomes_history} goh ON goh.loggeduser = :userid1 AND ( (goh.courseid > 0 AND goh.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel1) - OR ((goh.courseid IS NULL OR goh.courseid < 1) AND ctx.id = :syscontextid) + OR ((goh.courseid IS NULL OR goh.courseid < 1) AND ctx.id = :syscontextid1) ) LEFT JOIN {grade_categories_history} gch ON gch.loggeduser = :userid2 AND ( gch.courseid = ctx.instanceid @@ -173,17 +178,28 @@ public static function get_contexts_for_userid(int $userid) : \core_privacy\loca gih.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel3 ) + LEFT JOIN {scale_history} sh + ON (sh.userid = :userid4 OR sh.loggeduser = :userid5) + AND ( + (sh.courseid > 0 AND sh.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel4) + OR (sh.courseid = 0 AND ctx.id = :syscontextid2) + ) WHERE goh.id IS NOT NULL OR gch.id IS NOT NULL - OR gih.id IS NOT NULL"; + OR gih.id IS NOT NULL + OR sh.id IS NOT NULL"; $params = [ - 'syscontextid' => SYSCONTEXTID, + 'syscontextid1' => SYSCONTEXTID, + 'syscontextid2' => SYSCONTEXTID, 'courselevel1' => CONTEXT_COURSE, 'courselevel2' => CONTEXT_COURSE, 'courselevel3' => CONTEXT_COURSE, + 'courselevel4' => CONTEXT_COURSE, 'userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid, + 'userid4' => $userid, + 'userid5' => $userid, ]; $contextlist->add_from_sql($sql, $params); @@ -274,6 +290,9 @@ public static function export_user_data(approved_contextlist $contextlist) { // Export the outcomes. static::export_user_data_outcomes_in_contexts($contextlist); + // Export the scales. + static::export_user_data_scales_in_contexts($contextlist); + // Export the historical grades which have become orphans (their grade items were deleted). // We place those in ther user context of the graded user. $userids = array_values(array_map(function($context) { @@ -693,6 +712,100 @@ protected static function export_user_data_outcomes_in_contexts(approved_context }); } + /** + * Export the user data related to scales. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + * @return void + */ + protected static function export_user_data_scales_in_contexts(approved_contextlist $contextlist) { + global $DB; + + $rootpath = [get_string('grades', 'core_grades')]; + $relatedtomepath = array_merge($rootpath, [get_string('privacy:path:relatedtome', 'core_grades')]); + $userid = $contextlist->get_user()->id; + + // Reorganise the contexts. + $reduced = array_reduce($contextlist->get_contexts(), function($carry, $context) { + if ($context->contextlevel == CONTEXT_SYSTEM) { + $carry['in_system'] = true; + } else if ($context->contextlevel == CONTEXT_COURSE) { + $carry['courseids'][] = $context->instanceid; + } + return $carry; + }, [ + 'in_system' => false, + 'courseids' => [] + ]); + + // Construct SQL. + $sqltemplateparts = []; + $templateparams = []; + if ($reduced['in_system']) { + $sqltemplateparts[] = '{prefix}.courseid = 0'; + } + if (!empty($reduced['courseids'])) { + list($insql, $inparams) = $DB->get_in_or_equal($reduced['courseids'], SQL_PARAMS_NAMED); + $sqltemplateparts[] = "{prefix}.courseid $insql"; + $templateparams = array_merge($templateparams, $inparams); + } + if (empty($sqltemplateparts)) { + return; + } + $sqltemplate = '(' . implode(' OR ', $sqltemplateparts) . ')'; + + // Export edited scales. + $sqlwhere = str_replace('{prefix}', 's', $sqltemplate); + $sql = " + SELECT s.id, s.courseid, s.name, s.timemodified + FROM {scale} s + WHERE $sqlwhere + AND s.userid = :userid + ORDER BY s.courseid, s.timemodified, s.id"; + $params = array_merge($templateparams, ['userid' => $userid]); + $recordset = $DB->get_recordset_sql($sql, $params); + static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) { + $carry[] = [ + 'name' => $record->name, + 'timemodified' => transform::datetime($record->timemodified), + 'created_or_modified_by_you' => transform::yesno(true) + ]; + return $carry; + + }, function($courseid, $data) use ($relatedtomepath) { + $context = $courseid ? context_course::instance($courseid) : context_system::instance(); + writer::with_context($context)->export_related_data($relatedtomepath, 'scales', + (object) ['scales' => $data]); + }); + + // Export edits of scales history. + $sqlwhere = str_replace('{prefix}', 'sh', $sqltemplate); + $sql = " + SELECT sh.id, sh.courseid, sh.name, sh.userid, sh.timemodified, sh.action, sh.loggeduser + FROM {scale_history} sh + WHERE $sqlwhere + AND sh.loggeduser = :userid1 + OR sh.userid = :userid2 + ORDER BY sh.courseid, sh.timemodified, sh.id"; + $params = array_merge($templateparams, ['userid1' => $userid, 'userid2' => $userid]); + $recordset = $DB->get_recordset_sql($sql, $params); + static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) use ($userid) { + $carry[] = [ + 'name' => $record->name, + 'timemodified' => transform::datetime($record->timemodified), + 'author_of_change_was_you' => transform::yesno($record->userid == $userid), + 'author_of_action_was_you' => transform::yesno($record->loggeduser == $userid), + 'action' => static::transform_history_action($record->action) + ]; + return $carry; + + }, function($courseid, $data) use ($relatedtomepath) { + $context = $courseid ? context_course::instance($courseid) : context_system::instance(); + writer::with_context($context)->export_related_data($relatedtomepath, 'scales_history', + (object) ['modified_records' => $data]); + }); + } + /** * Extract grade_grade from a record. * diff --git a/grade/tests/privacy_test.php b/grade/tests/privacy_test.php index 83605a11c003..f7dfc15c569f 100644 --- a/grade/tests/privacy_test.php +++ b/grade/tests/privacy_test.php @@ -64,6 +64,11 @@ public function test_get_contexts_for_userid_gradebook_edits() { $u4 = $dg->create_user(); $u5 = $dg->create_user(); $u6 = $dg->create_user(); + $u7 = $dg->create_user(); + $u8 = $dg->create_user(); + $u9 = $dg->create_user(); + $u10 = $dg->create_user(); + $u11 = $dg->create_user(); $sysctx = context_system::instance(); $c1ctx = context_course::instance($c1->id); @@ -80,16 +85,22 @@ public function test_get_contexts_for_userid_gradebook_edits() { 'fullname' => 'go2']), false); // Nothing as of now. - foreach ([$u1, $u2, $u3, $u4] as $u) { + foreach ([$u1, $u2, $u3, $u4, $u5, $u6, $u7, $u8, $u9, $u10, $u11] as $u) { $contexts = array_flip(provider::get_contexts_for_userid($u->id)->get_contextids()); $this->assertEmpty($contexts); } $go0 = new grade_outcome(['shortname' => 'go0', 'fullname' => 'go0', 'usermodified' => $u1->id]); $go0->insert(); - $go1 = new grade_outcome(['shortname' => 'go1', 'fullname' => 'go1', 'courseid' => $c1->id, 'usermodified' => $u1->id]); + $go1 = new grade_outcome(['shortname' => 'go1', 'fullname' => 'go1', 'courseid' => $c1->id, 'usermodified' => $u11->id]); $go1->insert(); + // Create scales. + $s1 = new grade_scale(['name' => 's1', 'scale' => 'a,b', 'userid' => $u7->id, 'courseid' => 0, 'description' => '']); + $s1->insert(); + $s2 = new grade_scale(['name' => 's2', 'scale' => 'a,b', 'userid' => $u8->id, 'courseid' => $c1->id, 'description' => '']); + $s2->insert(); + // User 2 creates history. $this->setUser($u2); $go0->shortname .= ' edited'; @@ -118,11 +129,18 @@ public function test_get_contexts_for_userid_gradebook_edits() { $this->setUser($u6); $gi2a->delete(); + // User 9 creates history. + $this->setUser($u9); + $s1->name .= ' edited'; + $s1->update(); + // Assert contexts. $contexts = array_flip(provider::get_contexts_for_userid($u1->id)->get_contextids()); - $this->assertCount(2, $contexts); - $this->assertArrayHasKey($c1ctx->id, $contexts); + $this->assertCount(1, $contexts); $this->assertArrayHasKey($sysctx->id, $contexts); + $contexts = array_flip(provider::get_contexts_for_userid($u11->id)->get_contextids()); + $this->assertCount(1, $contexts); + $this->assertArrayHasKey($c1ctx->id, $contexts); $contexts = array_flip(provider::get_contexts_for_userid($u2->id)->get_contextids()); $this->assertCount(2, $contexts); $this->assertArrayHasKey($sysctx->id, $contexts); @@ -140,6 +158,23 @@ public function test_get_contexts_for_userid_gradebook_edits() { $contexts = array_flip(provider::get_contexts_for_userid($u6->id)->get_contextids()); $this->assertCount(1, $contexts); $this->assertArrayHasKey($c2ctx->id, $contexts); + $contexts = array_flip(provider::get_contexts_for_userid($u7->id)->get_contextids()); + $this->assertCount(1, $contexts); + $this->assertArrayHasKey($sysctx->id, $contexts); + $contexts = array_flip(provider::get_contexts_for_userid($u8->id)->get_contextids()); + $this->assertCount(1, $contexts); + $this->assertArrayHasKey($c1ctx->id, $contexts); + $contexts = array_flip(provider::get_contexts_for_userid($u9->id)->get_contextids()); + $this->assertCount(1, $contexts); + $this->assertArrayHasKey($sysctx->id, $contexts); + + // User 10 creates history. + $this->setUser($u10); + $s2->delete(); + + $contexts = array_flip(provider::get_contexts_for_userid($u10->id)->get_contextids()); + $this->assertCount(1, $contexts); + $this->assertArrayHasKey($c1ctx->id, $contexts); } public function test_get_contexts_for_userid_grades_and_history() { @@ -609,6 +644,10 @@ public function test_export_data_for_user_about_gradebook_edits() { $u4 = $dg->create_user(); $u5 = $dg->create_user(); $u6 = $dg->create_user(); + $u7 = $dg->create_user(); + $u8 = $dg->create_user(); + $u9 = $dg->create_user(); + $u10 = $dg->create_user(); $sysctx = context_system::instance(); $u1ctx = context_user::instance($u1->id); @@ -641,6 +680,14 @@ public function test_export_data_for_user_about_gradebook_edits() { $go1 = new grade_outcome(['shortname' => 'go1', 'fullname' => 'go1', 'courseid' => $c1->id, 'usermodified' => $u1->id]); $go1->insert(); + // Create scales. + $s1 = new grade_scale(['name' => 's1', 'scale' => 'a,b', 'userid' => $u7->id, 'courseid' => 0, 'description' => '']); + $s1->insert(); + $s2 = new grade_scale(['name' => 's2', 'scale' => 'a,b', 'userid' => $u8->id, 'courseid' => $c1->id, 'description' => '']); + $s2->insert(); + $s3 = new grade_scale(['name' => 's3', 'scale' => 'a,b', 'userid' => $u8->id, 'courseid' => $c2->id, 'description' => '']); + $s3->insert(); + // User 2 creates history. $this->setUser($u2); $go0->shortname .= ' edited'; @@ -669,6 +716,15 @@ public function test_export_data_for_user_about_gradebook_edits() { $this->setUser($u6); $gi2a->delete(); + // User 9 creates history. + $this->setUser($u9); + $s1->name .= ' edited'; + $s1->update(); + + // User 10 creates history. + $this->setUser($u10); + $s3->delete(); + $this->setAdminUser(); // Export data for u1. @@ -755,6 +811,74 @@ public function test_export_data_for_user_about_gradebook_edits() { $this->assertEquals(transform::yesno(true), $data->modified_records[0]['logged_in_user_was_you']); $this->assertEquals(get_string('privacy:request:historyactiondelete', 'core_grades'), $data->modified_records[0]['action']); + + // Export data for u7. + writer::reset(); + provider::export_user_data(new approved_contextlist($u7, 'core_grades', $allcontexts)); + $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales'); + $this->assertEmpty($data); + $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales'); + $this->assertCount(1, $data->scales); + $this->assertEquals($s1->name, $data->scales[0]['name']); + $this->assertEquals(transform::yesno(true), $data->scales[0]['created_or_modified_by_you']); + + // Export data for u8. + writer::reset(); + provider::export_user_data(new approved_contextlist($u8, 'core_grades', $allcontexts)); + $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales'); + $this->assertEmpty($data); + $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales'); + $this->assertCount(1, $data->scales); + $this->assertEquals($s2->name, $data->scales[0]['name']); + $this->assertEquals(transform::yesno(true), $data->scales[0]['created_or_modified_by_you']); + $data = writer::with_context($c2ctx)->get_related_data($relatedtomepath, 'scales_history'); + $this->assertCount(2, $data->modified_records); + $this->assertEquals($s3->name, $data->modified_records[0]['name']); + $this->assertEquals(transform::yesno(true), $data->modified_records[0]['author_of_change_was_you']); + $this->assertEquals(transform::yesno(false), $data->modified_records[0]['author_of_action_was_you']); + $this->assertEquals(get_string('privacy:request:historyactioninsert', 'core_grades'), + $data->modified_records[0]['action']); + $this->assertEquals($s3->name, $data->modified_records[1]['name']); + $this->assertEquals(transform::yesno(true), $data->modified_records[1]['author_of_change_was_you']); + $this->assertEquals(transform::yesno(false), $data->modified_records[1]['author_of_action_was_you']); + $this->assertEquals(get_string('privacy:request:historyactiondelete', 'core_grades'), + $data->modified_records[1]['action']); + + // Export data for u9. + writer::reset(); + provider::export_user_data(new approved_contextlist($u9, 'core_grades', $allcontexts)); + $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales'); + $this->assertEmpty($data); + $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales'); + $this->assertEmpty($data); + $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales_history'); + $this->assertCount(1, $data->modified_records); + $this->assertEquals($s1->name, $data->modified_records[0]['name']); + $this->assertEquals(transform::yesno(false), $data->modified_records[0]['author_of_change_was_you']); + $this->assertEquals(transform::yesno(true), $data->modified_records[0]['author_of_action_was_you']); + $this->assertEquals(get_string('privacy:request:historyactionupdate', 'core_grades'), + $data->modified_records[0]['action']); + + // Export data for u10. + writer::reset(); + provider::export_user_data(new approved_contextlist($u10, 'core_grades', $allcontexts)); + $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales'); + $this->assertEmpty($data); + $data = writer::with_context($c2ctx)->get_related_data($relatedtomepath, 'scales'); + $this->assertEmpty($data); + $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales'); + $this->assertEmpty($data); + $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales_history'); + $this->assertEmpty($data); + $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales_history'); + $this->assertEmpty($data); + $data = writer::with_context($c2ctx)->get_related_data($relatedtomepath, 'scales_history'); + $this->assertCount(1, $data->modified_records); + $this->assertEquals($s3->name, $data->modified_records[0]['name']); + $this->assertEquals(transform::yesno(false), $data->modified_records[0]['author_of_change_was_you']); + $this->assertEquals(transform::yesno(true), $data->modified_records[0]['author_of_action_was_you']); + $this->assertEquals(get_string('privacy:request:historyactiondelete', 'core_grades'), + $data->modified_records[0]['action']); } /** diff --git a/lang/en/grades.php b/lang/en/grades.php index feca6be41c4f..f00afe5f397d 100644 --- a/lang/en/grades.php +++ b/lang/en/grades.php @@ -324,31 +324,6 @@ $string['graderreport'] = 'Grader report'; $string['grades'] = 'Grades'; $string['gradesforuser'] = 'Grades for {$a->user}'; -$string['privacy:metadata:grade_import_newitem'] = 'Temporary table for storing new grade_item names from grade import'; -$string['privacy:metadata:grade_import_newitem:importcode'] = 'A unique batch code for identifying one batch of imports'; -$string['privacy:metadata:grade_import_newitem:importer'] = 'User importing the data'; -$string['privacy:metadata:grade_import_newitem:itemname'] = 'New grade item name'; -$string['privacy:metadata:grade_import_values'] = 'Temporary table for importing grades'; -$string['privacy:metadata:grade_import_values:feedback'] = 'Grade feedback'; -$string['privacy:metadata:grade_import_values:finalgrade'] = 'Raw grade value'; -$string['privacy:metadata:grade_import_values:importcode'] = 'A unique batch code for identifying one batch of imports'; -$string['privacy:metadata:grade_import_values:importer'] = 'User importing the data'; -$string['privacy:metadata:grade_import_values:importonlyfeedback'] = 'Flag if only feedback was imported'; -$string['privacy:metadata:grade_import_values:userid'] = 'User whose grade was imported'; -$string['privacy:metadata:scale'] = 'Grading scales, store ID of the user who created the scale. Not considered personal information'; -$string['privacy:metadata:scale:description'] = 'Description of the scale'; -$string['privacy:metadata:scale:name'] = 'Name of the scale'; -$string['privacy:metadata:scale:scale'] = 'Values in the scale'; -$string['privacy:metadata:scale:timemodified'] = 'Time when the scale was last modified'; -$string['privacy:metadata:scale:userid'] = 'ID of the user who created the scale'; -$string['privacy:metadata:scale_history'] = 'History table'; -$string['privacy:metadata:scale_history:action'] = 'created/modified/deleted constants'; -$string['privacy:metadata:scale_history:description'] = 'description'; -$string['privacy:metadata:scale_history:loggeduser'] = 'the userid of the person who last modified this outcome'; -$string['privacy:metadata:scale_history:name'] = 'name'; -$string['privacy:metadata:scale_history:scale'] = 'scale'; -$string['privacy:metadata:scale_history:timemodified'] = 'The last time this grade_item was modified'; -$string['privacy:metadata:scale_history:userid'] = 'userid'; $string['singleview'] = 'Single view for {$a}'; $string['gradesonly'] = 'Change to grades only'; $string['gradesmoduledeletionpendingwarning'] = 'Warning: Activity deletion in progress! Some grades are about to be removed.'; @@ -633,6 +608,17 @@ $string['prefshow'] = 'Show/hide toggles'; $string['previewrows'] = 'Preview rows'; $string['privacy:metadata:categorieshistory'] = 'A record of previous versions of grade categories'; +$string['privacy:metadata:grade_import_newitem'] = 'Temporary table for storing new grade_item names from grade import'; +$string['privacy:metadata:grade_import_newitem:importcode'] = 'A unique batch code for identifying one batch of imports'; +$string['privacy:metadata:grade_import_newitem:importer'] = 'User importing the data'; +$string['privacy:metadata:grade_import_newitem:itemname'] = 'New grade item name'; +$string['privacy:metadata:grade_import_values'] = 'Temporary table for importing grades'; +$string['privacy:metadata:grade_import_values:feedback'] = 'Grade feedback'; +$string['privacy:metadata:grade_import_values:finalgrade'] = 'Raw grade value'; +$string['privacy:metadata:grade_import_values:importcode'] = 'A unique batch code for identifying one batch of imports'; +$string['privacy:metadata:grade_import_values:importer'] = 'User importing the data'; +$string['privacy:metadata:grade_import_values:importonlyfeedback'] = 'Flag if only feedback was imported'; +$string['privacy:metadata:grade_import_values:userid'] = 'User whose grade was imported'; $string['privacy:metadata:grades'] = 'A record of grades'; $string['privacy:metadata:grades:aggregationstatus'] = 'The aggregation status'; $string['privacy:metadata:grades:aggregationweight'] = 'The weight in aggregation'; @@ -650,6 +636,10 @@ $string['privacy:metadata:outcomes:timemodified'] = 'Time at which the record was modified'; $string['privacy:metadata:outcomes:usermodified'] = 'The user who last modified the record'; $string['privacy:metadata:outcomeshistory'] = 'A record of previous versions of outcomes'; +$string['privacy:metadata:scale'] = 'A record of scales'; +$string['privacy:metadata:scale:timemodified'] = 'Time at which the record was last modified'; +$string['privacy:metadata:scale:userid'] = 'The user who last modified the record'; +$string['privacy:metadata:scalehistory'] = 'A record of previous versions of scales'; $string['privacy:path:relatedtome'] = 'Related to me'; $string['privacy:request:historyactiondelete'] = 'Delete'; $string['privacy:request:historyactioninsert'] = 'Insert'; From 24fc1d080eb174f2286ae5b47ffc0061d24bcaac Mon Sep 17 00:00:00 2001 From: Derick Turner Date: Wed, 16 May 2018 12:39:25 +0100 Subject: [PATCH 17/35] IOMAD: User firstname and lastname should be trimmed for leading/trailing spaces. closes #935 --- blocks/iomad_company_admin/company_user_create_form.php | 4 ++++ blocks/iomad_company_admin/editadvanced.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/blocks/iomad_company_admin/company_user_create_form.php b/blocks/iomad_company_admin/company_user_create_form.php index 5199e40d757d..06e8f08a1892 100644 --- a/blocks/iomad_company_admin/company_user_create_form.php +++ b/blocks/iomad_company_admin/company_user_create_form.php @@ -421,6 +421,10 @@ public function validation($usernew, $files) { redirect($dashboardurl); die; } else if ($data = $mform->get_data()) { + // Trim first and lastnames + $data->firstname = trim($data->firstname); + $data->lastname = trim($data->lastname); + $data->userid = $USER->id; if ($companyid > 0) { $data->companyid = $companyid; diff --git a/blocks/iomad_company_admin/editadvanced.php b/blocks/iomad_company_admin/editadvanced.php index 2df0af3e6f50..253c7d41877b 100644 --- a/blocks/iomad_company_admin/editadvanced.php +++ b/blocks/iomad_company_admin/editadvanced.php @@ -152,6 +152,10 @@ $userform->set_data($user); if ($usernew = $userform->get_data()) { + // Trim first and lastnames + $usernew->firstname = trim($usernew->firstname); + $usernew->lastname = trim($usernew->lastname); + if ($usernew->id == -1) { $event = \core\event\user_updated::create(array('context' => $systemcontext, 'userid' => $usernew->id, 'relateduserid' => $USER->id)); $event->trigger(); From e5c3ba2df94f0215090d5239e52eae40d6cf6397 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Wed, 16 May 2018 15:25:11 +0800 Subject: [PATCH 18/35] MDL-62469 qtype_calculated: check remaining placeholders, see MDL-62275 --- question/type/calculated/question.php | 8 +++++++- .../type/calculated/tests/variablesubstituter_test.php | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/question/type/calculated/question.php b/question/type/calculated/question.php index 6fb1cf121662..2936c05ba681 100644 --- a/question/type/calculated/question.php +++ b/question/type/calculated/question.php @@ -424,7 +424,13 @@ public function calculate($expression) { if ($error = qtype_calculated_find_formula_errors($expression)) { throw new moodle_exception('illegalformulasyntax', 'qtype_calculated', '', $error); } - return $this->calculate_raw($this->substitute_values_for_eval($expression)); + $expression = $this->substitute_values_for_eval($expression); + if ($datasets = question_bank::get_qtype('calculated')->find_dataset_names($expression)) { + // Some placeholders were not substituted. + throw new moodle_exception('illegalformulasyntax', 'qtype_calculated', '', + '{' . reset($datasets) . '}'); + } + return $this->calculate_raw($expression); } /** diff --git a/question/type/calculated/tests/variablesubstituter_test.php b/question/type/calculated/tests/variablesubstituter_test.php index 0a66ad866f42..a42d42680801 100644 --- a/question/type/calculated/tests/variablesubstituter_test.php +++ b/question/type/calculated/tests/variablesubstituter_test.php @@ -99,6 +99,13 @@ public function test_replace_expressions_in_text_formula() { $this->assertEquals('= 3', $vs->replace_expressions_in_text('= {={a} + {b}}')); } + public function test_expression_has_unmapped_placeholder() { + $this->expectException('moodle_exception'); + $this->expectExceptionMessage(get_string('illegalformulasyntax', 'qtype_calculated', '{c}')); + $vs = new qtype_calculated_variable_substituter(array('a' => 1, 'b' => 2), '.'); + $vs->calculate('{c} - {a} + {b}'); + } + public function test_replace_expressions_in_text_negative() { $vs = new qtype_calculated_variable_substituter(array('a' => -1, 'b' => 2), '.'); $this->assertEquals('temperatures -1 and 2', From 05a434229c6301596abf624a66ebc10d6267c302 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Wed, 16 May 2018 18:42:18 +0200 Subject: [PATCH 19/35] Moodle release 3.4.3 --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index d0c84dc5d845..a66943d873f6 100644 --- a/version.php +++ b/version.php @@ -29,11 +29,11 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2017111302.15; // 20171113 = branching date YYYYMMDD - do not modify! +$version = 2017111303.00; // 20171113 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '3.4.2+ (Build: 20180515)'; // Human-friendly version name +$release = '3.4.3 (Build: 20180517)'; // Human-friendly version name $branch = '34'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From a534d3e044ba3954509e486c9ea3e2277fca696a Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Mon, 14 May 2018 15:26:04 +0100 Subject: [PATCH 20/35] MDL-62440 participants: out-of-memory is many site-wide role assigns --- lib/accesslib.php | 12 +++++++++++- lib/tests/accesslib_test.php | 2 +- user/classes/participants_table.php | 15 +++++++++++++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/accesslib.php b/lib/accesslib.php index bd400e4e3511..09191fadc502 100644 --- a/lib/accesslib.php +++ b/lib/accesslib.php @@ -2680,6 +2680,10 @@ function get_archetype_roles($archetype) { /** * Gets all the user roles assigned in this context, or higher contexts for a list of users. * + * If you try using the combination $userids = [], $checkparentcontexts = true then this is likely + * to cause an out-of-memory error on large Moodle sites, so this combination is deprecated and + * outputs a warning, even though it is the default. + * * @param context $context * @param array $userids. An empty list means fetch all role assignments for the context. * @param bool $checkparentcontexts defaults to true @@ -2687,7 +2691,13 @@ function get_archetype_roles($archetype) { * @return array */ function get_users_roles(context $context, $userids = [], $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') { - global $USER, $DB; + global $DB; + + if (!$userids && $checkparentcontexts) { + debugging('Please do not call get_users_roles() with $checkparentcontexts = true ' . + 'and $userids array not set. This combination causes large Moodle sites ' . + 'with lots of site-wide role assignemnts to run out of memory.', DEBUG_DEVELOPER); + } if ($checkparentcontexts) { $contextids = $context->get_parent_context_ids(); diff --git a/lib/tests/accesslib_test.php b/lib/tests/accesslib_test.php index a4d16e5d60b2..b98dc82fac06 100644 --- a/lib/tests/accesslib_test.php +++ b/lib/tests/accesslib_test.php @@ -1612,7 +1612,7 @@ public function test_get_user_roles() { $u2roles = get_user_roles($coursecontext, $user2->id); - $allroles = get_users_roles($coursecontext); + $allroles = get_users_roles($coursecontext, [], false); $specificuserroles = get_users_roles($coursecontext, [$user1->id, $user2->id]); $this->assertEquals($u1roles, $allroles[$user1->id]); $this->assertEquals($u1roles, $specificuserroles[$user1->id]); diff --git a/user/classes/participants_table.php b/user/classes/participants_table.php index f634ab13e7bc..14005b1e2303 100644 --- a/user/classes/participants_table.php +++ b/user/classes/participants_table.php @@ -233,7 +233,6 @@ public function __construct($courseid, $currentgroup, $accesssince, $roleid, $en $this->groups = groups_get_all_groups($courseid, 0, 0, 'g.*', true); } $this->allroles = role_fix_names(get_all_roles($this->context), $this->context); - $this->allroleassignments = get_users_roles($this->context, [], true, 'c.contextlevel DESC, r.sortorder ASC'); $this->assignableroles = get_assignable_roles($this->context, ROLENAME_ALIAS, false); $this->profileroles = get_profile_roles($this->context); } @@ -441,9 +440,21 @@ public function query_db($pagesize, $useinitialsbar = true) { $sort = 'ORDER BY ' . $sort; } - $this->rawdata = user_get_participants($this->course->id, $this->currentgroup, $this->accesssince, + $rawdata = user_get_participants($this->course->id, $this->currentgroup, $this->accesssince, $this->roleid, $this->enrolid, $this->status, $this->search, $twhere, $tparams, $sort, $this->get_page_start(), $this->get_page_size()); + $this->rawdata = []; + foreach ($rawdata as $user) { + $this->rawdata[$user->id] = $user; + } + $rawdata->close(); + + if ($this->rawdata) { + $this->allroleassignments = get_users_roles($this->context, array_keys($this->rawdata), + true, 'c.contextlevel DESC, r.sortorder ASC'); + } else { + $this->allroleassignments = []; + } // Set initial bars. if ($useinitialsbar) { From d7cdf10744f6928babf7af65b4701ac083272c85 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Thu, 17 May 2018 10:54:35 +0100 Subject: [PATCH 21/35] MDL-62482 file_storage: may have many files with same content hash --- lib/filestorage/file_storage.php | 2 +- lib/filestorage/tests/file_storage_test.php | 72 +++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/lib/filestorage/file_storage.php b/lib/filestorage/file_storage.php index 15b4ef4e58bd..e15d0715d173 100644 --- a/lib/filestorage/file_storage.php +++ b/lib/filestorage/file_storage.php @@ -1575,7 +1575,7 @@ public function create_file_from_reference($filerecord, $repositoryid, $referenc $existingfile = null; if (isset($filerecord->contenthash)) { - $existingfile = $DB->get_record('files', array('contenthash' => $filerecord->contenthash)); + $existingfile = $DB->get_record('files', array('contenthash' => $filerecord->contenthash), '*', IGNORE_MULTIPLE); } if (!empty($existingfile)) { // There is an existing file already available. diff --git a/lib/filestorage/tests/file_storage_test.php b/lib/filestorage/tests/file_storage_test.php index fdbece52b43c..65210b22360a 100644 --- a/lib/filestorage/tests/file_storage_test.php +++ b/lib/filestorage/tests/file_storage_test.php @@ -397,6 +397,78 @@ public function test_create_file_from_reference() { $this->assertEquals($content, $importedfile->get_content()); } + /** + * Create file from reference tests + * + * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org} + */ + public function test_create_file_from_reference_with_content_hash() { + global $CFG, $DB; + + $this->resetAfterTest(); + // Create user. + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $this->setUser($user); + $usercontext = context_user::instance($user->id); + $syscontext = context_system::instance(); + + $fs = get_file_storage(); + + $repositorypluginname = 'user'; + // Override repository permission. + $capability = 'repository/' . $repositorypluginname . ':view'; + $guestroleid = $DB->get_field('role', 'id', array('shortname' => 'guest')); + assign_capability($capability, CAP_ALLOW, $guestroleid, $syscontext->id, true); + + $args = array(); + $args['type'] = $repositorypluginname; + $repos = repository::get_instances($args); + $userrepository = reset($repos); + $this->assertInstanceOf('repository', $userrepository); + + $component = 'user'; + $filearea = 'private'; + $itemid = 0; + $filepath = '/'; + $filename = 'userfile.txt'; + + $filerecord = array( + 'contextid' => $usercontext->id, + 'component' => $component, + 'filearea' => $filearea, + 'itemid' => $itemid, + 'filepath' => $filepath, + 'filename' => $filename, + ); + + $content = 'Test content'; + $originalfile = $fs->create_file_from_string($filerecord, $content); + $this->assertInstanceOf('stored_file', $originalfile); + + $otherfilerecord = $filerecord; + $otherfilerecord['filename'] = 'other-filename.txt'; + $otherfilewithsamecontents = $fs->create_file_from_string($otherfilerecord, $content); + $this->assertInstanceOf('stored_file', $otherfilewithsamecontents); + + $newfilerecord = array( + 'contextid' => $syscontext->id, + 'component' => 'core', + 'filearea' => 'phpunit', + 'itemid' => 0, + 'filepath' => $filepath, + 'filename' => $filename, + 'contenthash' => $originalfile->get_contenthash(), + ); + $ref = $fs->pack_reference($filerecord); + $newstoredfile = $fs->create_file_from_reference($newfilerecord, $userrepository->id, $ref); + $this->assertInstanceOf('stored_file', $newstoredfile); + $this->assertEquals($userrepository->id, $newstoredfile->get_repository_id()); + $this->assertEquals($originalfile->get_contenthash(), $newstoredfile->get_contenthash()); + $this->assertEquals($originalfile->get_filesize(), $newstoredfile->get_filesize()); + $this->assertRegExp('#' . $filename . '$#', $newstoredfile->get_reference_details()); + } + private function setup_three_private_files() { $this->resetAfterTest(); From 83acfca76a38f0f6d9089ed0d7ef129ec0c9d020 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Thu, 17 May 2018 12:10:48 +0100 Subject: [PATCH 22/35] MDL-62488 filebrowser: Stop unit tests assuming number of categories --- lib/filebrowser/tests/file_browser_test.php | 34 ++++++++++++++++----- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/filebrowser/tests/file_browser_test.php b/lib/filebrowser/tests/file_browser_test.php index 25835debed33..99f3af2444ce 100644 --- a/lib/filebrowser/tests/file_browser_test.php +++ b/lib/filebrowser/tests/file_browser_test.php @@ -33,6 +33,14 @@ */ class file_browser_testcase extends advanced_testcase { + /** @var int */ + protected $initialnonempty; + /** @var int */ + protected $initialcategories; + /** @var int */ + protected $initialcourses; + /** @var int */ + protected $initialjpg; /** @var stdClass */ protected $course1; /** @var stdClass */ @@ -57,6 +65,18 @@ public function setUp() { $this->setAdminUser(); + $browser = get_file_browser(); + $fileinfo = $browser->get_file_info(context_system::instance()); + $this->initialnonempty = $fileinfo->count_non_empty_children(); + $this->initialcategories = count(array_filter($fileinfo->get_children(), function($a) { + return $a instanceof file_info_context_coursecat; + })); + $this->initialcourses = count(array_filter($fileinfo->get_children(), function($a) { + return $a instanceof file_info_context_course; + })); + $this->initialcourses -= 1; // This includes the site course by default. + $this->initialjpg = $fileinfo->count_non_empty_children(['.jpg']); + $this->getDataGenerator()->create_category(); // Empty category. $this->course1 = $this->getDataGenerator()->create_course(); // Empty course. @@ -98,11 +118,11 @@ public function test_file_info_context_system() { $browser = get_file_browser(); $fileinfo = $browser->get_file_info(context_system::instance()); $this->assertNotEmpty($fileinfo->count_non_empty_children()); - $this->assertEquals(1, count($fileinfo->get_non_empty_children())); + $this->assertEquals($this->initialnonempty + 1, count($fileinfo->get_non_empty_children())); $categorychildren = array_filter($fileinfo->get_children(), function($a) { return $a instanceof file_info_context_coursecat; }); - $this->assertEquals(2, count($categorychildren)); + $this->assertEquals($this->initialcategories + 1, count($categorychildren)); } /** @@ -117,19 +137,19 @@ public function test_file_info_context_system_hidden() { $browser = get_file_browser(); $fileinfo = $browser->get_file_info(context_system::instance()); $this->assertNotEmpty($fileinfo->count_non_empty_children()); - $this->assertEquals(2, count($fileinfo->get_non_empty_children())); + $this->assertEquals($this->initialnonempty + 2, count($fileinfo->get_non_empty_children())); // Should be 1 category children (empty category). $categorychildren = array_filter($fileinfo->get_children(), function($a) { return $a instanceof file_info_context_coursecat; }); - $this->assertEquals(1, count($categorychildren)); + $this->assertEquals($this->initialcategories, count($categorychildren)); // Should be 2 course children - courses that belonged to hidden subcategory are now direct children of "System". $coursechildren = array_filter($fileinfo->get_children(), function($a) { return $a instanceof file_info_context_course; }); - $this->assertEquals(2, count($coursechildren)); + $this->assertEquals($this->initialcourses + 2, count($coursechildren)); } /** @@ -146,7 +166,7 @@ public function test_file_info_context_coursecat() { $coursechildren = array_filter($fileinfo->get_children(), function($a) { return $a instanceof file_info_context_course; }); - $this->assertEquals(2, count($coursechildren)); + $this->assertEquals($this->initialcourses + 2, count($coursechildren)); } /** @@ -159,7 +179,7 @@ public function test_file_info_context_coursecat_jpg() { $browser = get_file_browser(); $fileinfo = $browser->get_file_info(context_system::instance()); $this->assertNotEmpty($fileinfo->count_non_empty_children(['.jpg'])); - $this->assertEquals(1, count($fileinfo->get_non_empty_children(['.jpg']))); + $this->assertEquals($this->initialjpg + 1, count($fileinfo->get_non_empty_children(['.jpg']))); } /** From 4cc2b64cb6df25bffb04bda187ed646d9d7e77eb Mon Sep 17 00:00:00 2001 From: Matteo Scaramuccia Date: Fri, 4 May 2018 00:18:25 +0200 Subject: [PATCH 23/35] MDL-61893 JavaScript: Accept any node version but Carbon (LTS) --- .travis.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5abe62386580..91e8776b8bcd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,7 +56,7 @@ matrix: include: # Run grunt/npm install on highest version ('node' is an alias for the latest node.js version.) - php: 7.2 - env: DB=none TASK=GRUNT NVM_VERSION='8.9' + env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon' exclude: # MySQL - it's just too slow. diff --git a/package.json b/package.json index 3a79c54c0e9b..0e58fe25acf2 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,6 @@ "xpath": "0.0.23" }, "engines": { - "node": "8.9" + "node": ">=8.9 <9" } } From dd61328be3cceec9ba28f2da0abba3b14bfc9c37 Mon Sep 17 00:00:00 2001 From: Matteo Scaramuccia Date: Fri, 4 May 2018 00:42:44 +0200 Subject: [PATCH 24/35] MDL-61893 JavaScript: Bumped npm packages Performed by removing the current 'npm-shrinkwrap.json' file: $ rm -Rf node_modules $ rm -f npm-shrinkwrap.json $ npm install $ npm shrinkwrap --- npm-shrinkwrap.json | 2746 ++++++++++++++++++------------------------- 1 file changed, 1156 insertions(+), 1590 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 822297e38888..c7e1d35aab6e 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -14,15 +14,15 @@ } }, "abbrev": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", - "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, "acorn": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", - "integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", + "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", "dev": true }, "acorn-jsx": { @@ -43,19 +43,21 @@ } }, "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { "co": "4.6.0", - "json-stable-stringify": "1.0.1" + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", "dev": true }, "align-text": { @@ -82,9 +84,9 @@ "dev": true }, "ansi-escapes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz", - "integrity": "sha1-W65SvkJIeN2Xg+iRDj/Cki6DyBs=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, "ansi-regex": { @@ -100,9 +102,9 @@ "dev": true }, "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { "sprintf-js": "1.0.3" @@ -120,7 +122,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, "array-differ": { @@ -163,9 +165,9 @@ "dev": true }, "asap": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz", - "integrity": "sha1-UidltQw1EEkOUtfc/ghe+bqWlY8=", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", "dev": true, "optional": true }, @@ -177,11 +179,10 @@ "optional": true }, "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", - "dev": true, - "optional": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, "async": { "version": "1.5.2", @@ -197,30 +198,30 @@ "optional": true }, "autoprefixer": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.1.2.tgz", - "integrity": "sha1-++rwfUj9h44Ggr98vurecorbKxg=", + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", + "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", "dev": true, "requires": { - "browserslist": "2.2.0", - "caniuse-lite": "1.0.30000703", + "browserslist": "1.7.7", + "caniuse-db": "1.0.30000833", "normalize-range": "0.1.2", "num2fraction": "1.2.2", - "postcss": "6.0.7", + "postcss": "5.2.18", "postcss-value-parser": "3.3.0" } }, "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", "dev": true, "optional": true }, "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", "dev": true, "optional": true }, @@ -233,12 +234,36 @@ "chalk": "1.1.3", "esutils": "2.0.2", "js-tokens": "3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } } }, "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "bcrypt-pbkdf": { @@ -258,15 +283,15 @@ "dev": true, "requires": { "bytes": "2.2.0", - "content-type": "1.0.2", + "content-type": "1.0.4", "debug": "2.2.0", - "depd": "1.1.0", + "depd": "1.1.2", "http-errors": "1.3.1", "iconv-lite": "0.4.13", "on-finished": "2.3.0", "qs": "5.2.0", "raw-body": "2.1.7", - "type-is": "1.6.15" + "type-is": "1.6.16" }, "dependencies": { "debug": { @@ -299,21 +324,22 @@ } }, "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "dev": true, + "optional": true, "requires": { - "hoek": "2.16.3" + "hoek": "4.2.1" } }, "brace-expansion": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", - "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "0.4.2", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -338,19 +364,19 @@ } }, "browserslist": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.2.0.tgz", - "integrity": "sha1-XjXsmT5GfGRkuMtwhEc4aJHen1A=", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", "dev": true, "requires": { - "caniuse-lite": "1.0.30000703", - "electron-to-chromium": "1.3.16" + "caniuse-db": "1.0.30000833", + "electron-to-chromium": "1.3.45" } }, - "buffer-shims": { + "buffer-from": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", + "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", "dev": true }, "builtin-modules": { @@ -397,15 +423,15 @@ } }, "caniuse-db": { - "version": "1.0.30000703", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000703.tgz", - "integrity": "sha1-x9iZuK3qbaGqWHNV05rIUz+HhAM=", + "version": "1.0.30000833", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000833.tgz", + "integrity": "sha1-K9e+cqQBZY0svLj012AN7r6xxnY=", "dev": true }, "caniuse-lite": { - "version": "1.0.30000703", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000703.tgz", - "integrity": "sha1-Zm6MPx5PersdFtSOBOfp6N+TSSU=", + "version": "1.0.30000833", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000833.tgz", + "integrity": "sha512-tKNuKu4WLImh4NxoTgntxFpDrRiA0Q6Q1NycNhuMST0Kx+Pt8YnRDW6V8xsyH6AtO2CpAoibatEk5eaEhP3O1g==", "dev": true }, "caseless": { @@ -426,22 +452,46 @@ } }, "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "2.2.1", + "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "supports-color": "5.4.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } } }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, "circular-json": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz", - "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", "dev": true }, "cli": { @@ -464,6 +514,12 @@ "minimatch": "0.3.0" } }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, "minimatch": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", @@ -511,13 +567,13 @@ } }, "clone-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.0.tgz", - "integrity": "sha1-6uCiQT9VwJQvgYwin+/OhF1/Oxw=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.1.tgz", + "integrity": "sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==", "dev": true, "requires": { "is-regexp": "1.0.0", - "is-supported-regexp-flag": "1.0.0" + "is-supported-regexp-flag": "1.0.1" } }, "co": { @@ -533,9 +589,9 @@ "dev": true }, "color-convert": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", - "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "dev": true, "requires": { "color-name": "1.1.3" @@ -554,9 +610,9 @@ "dev": true }, "colorguard": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/colorguard/-/colorguard-1.2.0.tgz", - "integrity": "sha1-8/rK9cquuk71RlPZ+yW7cxd8DYQ=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorguard/-/colorguard-1.2.1.tgz", + "integrity": "sha512-qYVKTg626qpDg4/eBnPXidEPXn5+krbYqHVfyyEFBWV5z3IF4p44HKY/eE2t1ohlcrlIkDgHmFJMfQ8qMLnSFw==", "dev": true, "requires": { "chalk": "1.1.3", @@ -565,28 +621,23 @@ "object-assign": "4.1.1", "pipetteur": "2.0.3", "plur": "2.1.2", - "postcss": "5.2.17", + "postcss": "5.2.18", "postcss-reporter": "1.4.1", "text-table": "0.2.0", "yargs": "1.3.3" }, "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.17.tgz", - "integrity": "sha1-z09Ze4ZNZcikkrLqvp1wbIecOIs=", + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "chalk": "1.1.3", - "js-base64": "2.1.9", - "source-map": "0.5.6", - "supports-color": "3.2.3" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "postcss-reporter": { @@ -596,18 +647,18 @@ "dev": true, "requires": { "chalk": "1.1.3", - "lodash": "4.17.4", + "lodash": "4.17.10", "log-symbols": "1.0.2", - "postcss": "5.2.17" + "postcss": "5.2.18" } }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "has-flag": "1.0.0" + "ansi-regex": "2.1.1" } }, "yargs": { @@ -625,9 +676,9 @@ "dev": true }, "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "dev": true, "requires": { "delayed-stream": "1.0.0" @@ -649,13 +700,14 @@ "dev": true }, "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { + "buffer-from": "1.0.0", "inherits": "2.0.3", - "readable-stream": "2.2.9", + "readable-stream": "2.3.6", "typedarray": "0.0.6" } }, @@ -669,9 +721,9 @@ } }, "content-type": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", - "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, "core-util-is": { @@ -681,13 +733,13 @@ "dev": true }, "cosmiconfig": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-2.1.3.tgz", - "integrity": "sha1-lSdx6w3dwcs/ovb75RpSLpOz7go=", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-2.2.2.tgz", + "integrity": "sha512-GiNXLwAFPYHy25XmTPpafYvn3CLAkJ8FLsscq78MQd1Kh0OU6Yzhn4eV2MVF4G9WEQZoWEGltatdR+ntGPMl5A==", "dev": true, "requires": { "is-directory": "0.3.1", - "js-yaml": "3.8.4", + "js-yaml": "3.11.0", "minimist": "1.2.0", "object-assign": "4.1.1", "os-homedir": "1.0.2", @@ -743,31 +795,31 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "shebang-command": "1.2.0", - "which": "1.2.14" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - } + "which": "1.3.0" } }, "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", "dev": true, "optional": true, "requires": { - "boom": "2.10.1" + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "dev": true, + "optional": true, + "requires": { + "hoek": "4.2.1" + } + } } }, "css-color-names": { @@ -830,7 +882,7 @@ "integrity": "sha1-bLLN/2lHJZ39r3kGJjM6hi3pSLA=", "dev": true, "requires": { - "source-map": "0.5.6" + "source-map": "0.5.7" } }, "csslint": { @@ -865,15 +917,6 @@ "optional": true, "requires": { "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "optional": true - } } }, "date-now": { @@ -893,9 +936,9 @@ } }, "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" @@ -921,11 +964,11 @@ "requires": { "globby": "5.0.0", "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", "object-assign": "4.1.1", "pify": "2.3.0", "pinkie-promise": "2.0.1", - "rimraf": "2.6.1" + "rimraf": "2.6.2" } }, "delayed-stream": { @@ -935,19 +978,18 @@ "dev": true }, "depd": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", - "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, "doctrine": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", - "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" + "esutils": "2.0.2" } }, "doiuse": { @@ -957,55 +999,19 @@ "dev": true, "requires": { "browserslist": "1.7.7", - "caniuse-db": "1.0.30000703", + "caniuse-db": "1.0.30000833", "css-rule-stream": "1.1.0", "duplexer2": "0.0.2", "jsonfilter": "1.1.2", "ldjson-stream": "1.2.1", - "lodash": "4.17.4", + "lodash": "4.17.10", "multimatch": "2.1.0", - "postcss": "5.2.17", + "postcss": "5.2.18", "source-map": "0.4.4", "through2": "0.6.5", "yargs": "3.10.0" }, "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000703", - "electron-to-chromium": "1.3.16" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.17.tgz", - "integrity": "sha1-z09Ze4ZNZcikkrLqvp1wbIecOIs=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.1.9", - "source-map": "0.5.6", - "supports-color": "3.2.3" - }, - "dependencies": { - "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", - "dev": true - } - } - }, "source-map": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", @@ -1014,15 +1020,6 @@ "requires": { "amdefine": "1.0.1" } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } } } }, @@ -1133,9 +1130,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.16", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.16.tgz", - "integrity": "sha1-0OAmc1dUdwkBrjAaIWZMukXZL30=", + "version": "1.3.45", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.45.tgz", + "integrity": "sha1-RYrBscXHYM6IEaFtK/vZfsMLr7g=", "dev": true }, "entities": { @@ -1145,13 +1142,13 @@ "dev": true }, "errno": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", - "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "optional": true, "requires": { - "prr": "0.0.0" + "prr": "1.0.1" } }, "error-ex": { @@ -1177,7 +1174,7 @@ "requires": { "esprima": "1.0.4", "estraverse": "1.3.2", - "source-map": "0.5.6" + "source-map": "0.5.7" }, "dependencies": { "esprima": { @@ -1200,30 +1197,30 @@ "integrity": "sha1-3cf8f9cL+TIFsLNEm7FqHp59SVA=", "dev": true, "requires": { - "ajv": "5.2.2", + "ajv": "5.5.2", "babel-code-frame": "6.26.0", - "chalk": "2.1.0", - "concat-stream": "1.6.0", + "chalk": "2.4.1", + "concat-stream": "1.6.2", "cross-spawn": "5.1.0", - "debug": "2.6.8", - "doctrine": "2.0.0", + "debug": "2.6.9", + "doctrine": "2.1.0", "eslint-scope": "3.7.1", - "espree": "3.5.0", - "esquery": "1.0.0", + "espree": "3.5.4", + "esquery": "1.0.1", "estraverse": "4.2.0", "esutils": "2.0.2", "file-entry-cache": "2.0.0", "functional-red-black-tree": "1.0.1", "glob": "7.1.2", "globals": "9.18.0", - "ignore": "3.3.3", + "ignore": "3.3.8", "imurmurhash": "0.1.4", - "inquirer": "3.2.3", - "is-resolvable": "1.0.0", - "js-yaml": "3.10.0", + "inquirer": "3.3.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.11.0", "json-stable-stringify": "1.0.1", "levn": "0.3.0", - "lodash": "4.17.4", + "lodash": "4.17.10", "minimatch": "3.0.4", "mkdirp": "0.5.1", "natural-compare": "1.4.0", @@ -1235,88 +1232,8 @@ "semver": "5.3.0", "strip-ansi": "4.0.0", "strip-json-comments": "2.0.1", - "table": "4.0.1", + "table": "4.0.3", "text-table": "0.2.0" - }, - "dependencies": { - "ajv": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.2.tgz", - "integrity": "sha1-R8aNaehvXZUxA7AHSpQw3GPaXjk=", - "dev": true, - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "json-schema-traverse": "0.3.1", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" - } - }, - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } } }, "eslint-plugin-promise": { @@ -1331,43 +1248,42 @@ "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", "dev": true, "requires": { - "esrecurse": "4.2.0", + "esrecurse": "4.2.1", "estraverse": "4.2.0" } }, "espree": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.0.tgz", - "integrity": "sha1-mDWGJb3QVYYeon4oZ+pyn69GPY0=", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", "dev": true, "requires": { - "acorn": "5.1.2", + "acorn": "5.5.3", "acorn-jsx": "3.0.1" } }, "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", "dev": true }, "esquery": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { "estraverse": "4.2.0" } }, "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "4.2.0", - "object-assign": "4.1.1" + "estraverse": "4.2.0" } }, "estraverse": { @@ -1394,7 +1310,7 @@ "integrity": "sha1-c9CQTjlbPKsGWLCNCewlMH8pu3M=", "dev": true, "requires": { - "clone-regexp": "1.0.0" + "clone-regexp": "1.0.1" } }, "exit": { @@ -1429,14 +1345,14 @@ "optional": true }, "external-editor": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.4.tgz", - "integrity": "sha1-HtkZnanL/i7y96MbL96LDRI2iXI=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { - "iconv-lite": "0.4.17", - "jschardet": "1.5.1", - "tmp": "0.0.31" + "chardet": "0.4.2", + "iconv-lite": "0.4.21", + "tmp": "0.0.33" } }, "extglob": { @@ -1449,15 +1365,21 @@ } }, "extsprintf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", - "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", "dev": true }, "fast-levenshtein": { @@ -1472,17 +1394,16 @@ "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", "dev": true, "requires": { - "websocket-driver": "0.6.5" + "websocket-driver": "0.7.0" } }, "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" + "escape-string-regexp": "1.0.5" } }, "file-entry-cache": { @@ -1491,7 +1412,7 @@ "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", "dev": true, "requires": { - "flat-cache": "1.2.2", + "flat-cache": "1.3.0", "object-assign": "4.1.1" } }, @@ -1533,6 +1454,12 @@ } } }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, "minimatch": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.4.0.tgz", @@ -1593,12 +1520,12 @@ } }, "flat-cache": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", - "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", "dev": true, "requires": { - "circular-json": "0.3.1", + "circular-json": "0.3.3", "del": "2.2.2", "graceful-fs": "4.1.11", "write": "0.2.1" @@ -1633,15 +1560,15 @@ "optional": true }, "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "dev": true, "optional": true, "requires": { "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" + "combined-stream": "1.0.6", + "mime-types": "2.1.18" } }, "fs.realpath": { @@ -1668,7 +1595,7 @@ "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=", "dev": true, "requires": { - "globule": "1.1.0" + "globule": "1.2.0" } }, "gear": { @@ -1767,6 +1694,12 @@ "ycssmin": "1.0.1" } }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, "mime": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", @@ -1783,10 +1716,10 @@ "sigmund": "1.0.1" } }, - "shelljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", - "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", "dev": true }, "uglify-js": { @@ -1817,15 +1750,6 @@ "optional": true, "requires": { "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "optional": true - } } }, "gherkin": { @@ -1871,7 +1795,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { "fs.realpath": "1.0.0", @@ -1928,22 +1852,14 @@ "dev": true }, "globule": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.1.0.tgz", - "integrity": "sha1-xJNS5NwYPYWJPuglOF65lLtt9F8=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", + "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", "dev": true, "requires": { "glob": "7.1.2", - "lodash": "4.16.6", + "lodash": "4.17.10", "minimatch": "3.0.4" - }, - "dependencies": { - "lodash": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.6.tgz", - "integrity": "sha1-0iyaxmAojzhD4Wun0rXQbMon13c=", - "dev": true - } } }, "graceful-fs": { @@ -1972,9 +1888,9 @@ "glob": "7.0.6", "grunt-cli": "1.2.0", "grunt-known-options": "1.1.0", - "grunt-legacy-log": "1.0.0", + "grunt-legacy-log": "1.0.2", "grunt-legacy-util": "1.0.0", - "iconv-lite": "0.4.17", + "iconv-lite": "0.4.21", "js-yaml": "3.5.5", "minimatch": "3.0.4", "nopt": "3.0.6", @@ -2020,7 +1936,7 @@ "integrity": "sha1-A3fDgBfKvHMisNH7zSWkkWQfL74=", "dev": true, "requires": { - "argparse": "1.0.9", + "argparse": "1.0.10", "esprima": "2.7.3" } }, @@ -2041,7 +1957,31 @@ "async": "1.5.2", "chalk": "1.1.3", "less": "2.6.1", - "lodash": "4.17.4" + "lodash": "4.17.10" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } } }, "grunt-contrib-uglify": { @@ -2051,10 +1991,34 @@ "dev": true, "requires": { "chalk": "1.1.3", - "lodash": "4.17.4", + "lodash": "4.17.10", "maxmin": "1.1.0", "uglify-js": "2.6.4", "uri-path": "1.0.0" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } } }, "grunt-contrib-watch": { @@ -2083,39 +2047,8 @@ "integrity": "sha512-VZlDOLrB2KKefDDcx/wR8rEEz7smDwDKVblmooa+itdt/2jWw3ee2AiZB5Ap4s4AoRY0pbHRjZ3HHwY8uKR9Rw==", "dev": true, "requires": { - "chalk": "2.1.0", + "chalk": "2.4.1", "eslint": "4.6.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" - } - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } } }, "grunt-known-options": { @@ -2125,24 +2058,15 @@ "dev": true }, "grunt-legacy-log": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz", - "integrity": "sha1-+4bxgJhHvAfcR4Q/ns1srLYt8tU=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.2.tgz", + "integrity": "sha512-WdedTJ/6zCXnI/coaouzqvkI19uwqbcPkdsXiDRKJyB5rOUlOxnCnTVbpeUdEckKVir2uHF3rDBYppj2p6N3+g==", "dev": true, "requires": { "colors": "1.1.2", "grunt-legacy-log-utils": "1.0.0", "hooker": "0.2.3", - "lodash": "3.10.1", - "underscore.string": "3.2.3" - }, - "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true - } + "lodash": "4.17.10" } }, "grunt-legacy-log-utils": { @@ -2155,11 +2079,33 @@ "lodash": "4.3.0" }, "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, "lodash": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } } } }, @@ -2183,6 +2129,15 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", "dev": true + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true, + "requires": { + "isexe": "2.0.0" + } } } }, @@ -2195,73 +2150,12 @@ "stylelint": "7.13.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", "dev": true }, - "autoprefixer": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", - "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-db": "1.0.30000703", - "normalize-range": "0.1.2", - "num2fraction": "1.2.2", - "postcss": "5.2.17", - "postcss-value-parser": "3.3.0" - } - }, - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000703", - "electron-to-chromium": "1.3.16" - } - }, - "chalk": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.0.1.tgz", - "integrity": "sha512-Mp+FXEI+FrwY/XYV45b2YD3E8i3HwnEAoFcM0qlZzq/RZ9RwWitt2Y/c7cqRAz70U7hfekqx6qNYthuKFO6K0g==", - "dev": true, - "requires": { - "ansi-styles": "3.1.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.1.0.tgz", - "integrity": "sha1-CcIC1ckX7CMYjKpcnLkXnNlUd1A=", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "supports-color": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.0.tgz", - "integrity": "sha512-Ts0Mu/A1S1aZxEJNG88I4Oc9rcZSBFNac5e27yh4j2mqbhZSSzR1Ah79EYwSn9Zuh7lrlGD2cVGzw1RKGzyLSg==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, "get-stdin": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", @@ -2281,131 +2175,12 @@ "pinkie-promise": "2.0.1" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "postcss": { - "version": "5.2.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.17.tgz", - "integrity": "sha1-z09Ze4ZNZcikkrLqvp1wbIecOIs=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.1.9", - "source-map": "0.5.6", - "supports-color": "3.2.3" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - } - } - }, - "postcss-less": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-0.14.0.tgz", - "integrity": "sha1-xjGwicbM5CK5oQ86lY0r7dOBkyQ=", - "dev": true, - "requires": { - "postcss": "5.2.17" - } - }, - "postcss-reporter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-3.0.0.tgz", - "integrity": "sha1-CeoPN6RExWk4eGBuCbAY6+/3z48=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "lodash": "4.17.4", - "log-symbols": "1.0.2", - "postcss": "5.2.17" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "postcss-scss": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-0.4.1.tgz", - "integrity": "sha1-rXcbgfD3L19IRdCKpg+TVXZT1Uw=", - "dev": true, - "requires": { - "postcss": "5.2.17" - } - }, "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, "stylelint": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-7.13.0.tgz", @@ -2414,10 +2189,10 @@ "requires": { "autoprefixer": "6.7.7", "balanced-match": "0.4.2", - "chalk": "2.0.1", - "colorguard": "1.2.0", - "cosmiconfig": "2.1.3", - "debug": "2.6.8", + "chalk": "2.4.1", + "colorguard": "1.2.1", + "cosmiconfig": "2.2.2", + "debug": "2.6.9", "doiuse": "2.6.0", "execall": "1.0.0", "file-entry-cache": "2.0.0", @@ -2425,17 +2200,17 @@ "globby": "6.1.0", "globjoin": "0.1.4", "html-tags": "2.0.0", - "ignore": "3.3.3", + "ignore": "3.3.8", "imurmurhash": "0.1.4", "known-css-properties": "0.2.0", - "lodash": "4.17.4", + "lodash": "4.17.10", "log-symbols": "1.0.2", - "mathml-tag-names": "2.0.1", + "mathml-tag-names": "2.1.0", "meow": "3.7.0", "micromatch": "2.3.11", "normalize-selector": "0.2.0", "pify": "2.3.0", - "postcss": "5.2.17", + "postcss": "5.2.18", "postcss-less": "0.14.0", "postcss-media-query-parser": "0.2.3", "postcss-reporter": "3.0.0", @@ -2444,66 +2219,13 @@ "postcss-selector-parser": "2.2.3", "postcss-value-parser": "3.3.0", "resolve-from": "3.0.0", - "specificity": "0.3.1", + "specificity": "0.3.2", "string-width": "2.1.1", "style-search": "0.1.0", "stylehacks": "2.3.2", "sugarss": "0.2.0", "svg-tags": "1.0.0", - "table": "4.0.1" - } - }, - "sugarss": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-0.2.0.tgz", - "integrity": "sha1-rDQjdWMyfG/4l7ZHQr9q7BkK054=", - "dev": true, - "requires": { - "postcss": "5.2.17" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - }, - "table": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.1.tgz", - "integrity": "sha1-qBFsEz+sLGH0pCCrbN9cTWHw5DU=", - "dev": true, - "requires": { - "ajv": "4.11.8", - "ajv-keywords": "1.5.1", - "chalk": "1.1.3", - "lodash": "4.17.4", - "slice-ansi": "0.0.4", - "string-width": "2.1.1" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "table": "4.0.3" } } } @@ -2515,7 +2237,7 @@ "dev": true, "requires": { "browserify-zlib": "0.1.4", - "concat-stream": "1.6.0" + "concat-stream": "1.6.2" } }, "handlebars": { @@ -2560,21 +2282,21 @@ } }, "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", "dev": true, "optional": true }, "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "dev": true, "optional": true, "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" + "ajv": "5.5.2", + "har-schema": "2.0.0" } }, "has-ansi": { @@ -2587,28 +2309,28 @@ } }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", "dev": true, "optional": true, "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" } }, "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", "dev": true }, "hooker": { @@ -2618,9 +2340,9 @@ "dev": true }, "hosted-git-info": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.4.2.tgz", - "integrity": "sha1-AHa59GonBQbduq6lZJaJdGBhKmc=", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", + "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", "dev": true }, "html-tags": { @@ -2675,31 +2397,40 @@ "dev": true, "requires": { "inherits": "2.0.3", - "statuses": "1.3.1" + "statuses": "1.5.0" } }, + "http-parser-js": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.12.tgz", + "integrity": "sha1-uc+/Sizybw/DSxDKFImid3HjR08=", + "dev": true + }, "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "optional": true, "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" } }, "iconv-lite": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.17.tgz", - "integrity": "sha1-T9qjs4rLwsAxsEXQ7c3+HsqxjI0=", - "dev": true + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz", + "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } }, "ignore": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz", - "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.8.tgz", + "integrity": "sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg==", "dev": true }, "image-size": { @@ -2747,18 +2478,18 @@ "dev": true }, "inquirer": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.2.3.tgz", - "integrity": "sha512-Bc3KbimpDTOeQdDj18Ir/rlsGuhBSSNqdOnxaAuKhpkdnMMuKsEGbZD2v5KFF9oso2OU+BPh7+/u5obmFDRmWw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", "dev": true, "requires": { - "ansi-escapes": "2.0.0", - "chalk": "2.1.0", + "ansi-escapes": "3.1.0", + "chalk": "2.4.1", "cli-cursor": "2.1.0", "cli-width": "2.2.0", - "external-editor": "2.0.4", + "external-editor": "2.2.0", "figures": "2.0.0", - "lodash": "4.17.4", + "lodash": "4.17.10", "mute-stream": "0.0.7", "run-async": "2.3.0", "rx-lite": "4.0.8", @@ -2766,67 +2497,12 @@ "string-width": "2.1.1", "strip-ansi": "4.0.0", "through": "2.3.8" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" - } - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "1.0.5" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } } }, "irregular-plurals": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.3.0.tgz", - "integrity": "sha1-evBpMb33S+M9z1haE+BvzMFsrs8=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.4.0.tgz", + "integrity": "sha1-LKmwM2UREYVUEvFr5dd8YqRYp2Y=", "dev": true }, "is-arrayish": { @@ -2836,9 +2512,9 @@ "dev": true }, "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-builtin-module": { @@ -2923,18 +2599,18 @@ "dev": true }, "is-path-in-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { - "is-path-inside": "1.0.0" + "is-path-inside": "1.0.1" } }, "is-path-inside": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", - "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { "path-is-inside": "1.0.2" @@ -2965,18 +2641,15 @@ "dev": true }, "is-resolvable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", - "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", - "dev": true, - "requires": { - "tryit": "1.0.3" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true }, "is-supported-regexp-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.0.tgz", - "integrity": "sha1-i1IMhfrnolM4LUsCZS4EVXbhO7g=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz", + "integrity": "sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==", "dev": true }, "is-typedarray": { @@ -3122,20 +2795,10 @@ } } }, - "jodid25519": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", - "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, "js-base64": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz", - "integrity": "sha1-8OgK4DmkvWVLXygfyT8EqRSn/M4=", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz", + "integrity": "sha512-H7ErYLM34CvDMto3GbD6xD0JLUGYXR3QTcH6B/tr4Hi/QpSThnCsIp+Sy5FRTw3B0d6py4HcNkW7nO/wdtGWEw==", "dev": true }, "js-tokens": { @@ -3145,13 +2808,13 @@ "dev": true }, "js-yaml": { - "version": "3.8.4", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz", - "integrity": "sha1-UgtFZPhlc7qWZir4Woyvp7S1pvY=", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", "dev": true, "requires": { - "argparse": "1.0.9", - "esprima": "3.1.3" + "argparse": "1.0.10", + "esprima": "4.0.0" } }, "jsbn": { @@ -3161,12 +2824,6 @@ "dev": true, "optional": true }, - "jschardet": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.5.1.tgz", - "integrity": "sha512-vE2hT1D0HLZCLLclfBSfkfTTedhVj0fubHpJBHKwwUWX0nSbhPAfk+SG9rTX95BYNmau8rGFfCeaT6T5OW1C2A==", - "dev": true - }, "jshint": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/jshint/-/jshint-0.9.1.tgz", @@ -3224,6 +2881,13 @@ "minimatch": "0.3.0" } }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true, + "optional": true + }, "minimatch": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", @@ -3241,7 +2905,7 @@ "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", "dev": true, "requires": { - "abbrev": "1.1.0" + "abbrev": "1.1.1" } } } @@ -3308,25 +2972,16 @@ "dev": true }, "jsprim": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", - "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "dev": true, "optional": true, "requires": { "assert-plus": "1.0.0", - "extsprintf": "1.0.2", + "extsprintf": "1.3.0", "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "optional": true - } + "verror": "1.10.0" } }, "kind-of": { @@ -3335,13 +2990,13 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } }, "known-css-properties": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.2.0.tgz", - "integrity": "sha1-iZyUvjaOVbQtfbjVvn1zpKSkFFQ=", + "integrity": "sha512-UTCzU28rRI9wkb8qSGoZa9pgWvxr4LjP2MEhi9XHb/1XMOJy0uTnIxaxzj8My/PORG+kQG6VzAcGvRw66eIOfA==", "dev": true }, "knox": { @@ -3351,7 +3006,7 @@ "dev": true, "requires": { "debug": "0.7.4", - "mime": "1.3.6", + "mime": "1.6.0", "stream-counter": "0.1.0", "xml2js": "0.2.8" }, @@ -3386,14 +3041,14 @@ "integrity": "sha1-ZY4B7JrDFJlZxrbfvPvAoXCv2no=", "dev": true, "requires": { - "errno": "0.1.4", + "errno": "0.1.7", "graceful-fs": "4.1.11", "image-size": "0.4.0", - "mime": "1.3.6", + "mime": "1.6.0", "mkdirp": "0.5.1", - "promise": "7.1.1", - "request": "2.81.0", - "source-map": "0.5.6" + "promise": "7.3.1", + "request": "2.85.0", + "source-map": "0.5.7" } }, "levn": { @@ -3407,9 +3062,9 @@ } }, "livereload-js": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.2.2.tgz", - "integrity": "sha1-bIclfmSKtHW8JOoldFftzB+NC8I=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz", + "integrity": "sha512-j1R0/FeGa64Y+NmqfZhyoVRzcFlOZ8sNlKzHjh4VvLULFACZhn68XrX5DFg2FhMvSMJmROuFxRSa560ECWKBMg==", "dev": true }, "load-json-file": { @@ -3423,23 +3078,12 @@ "pify": "2.3.0", "pinkie-promise": "2.0.1", "strip-bom": "2.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "0.2.1" - } - } } }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", "dev": true }, "log-symbols": { @@ -3449,6 +3093,30 @@ "dev": true, "requires": { "chalk": "1.1.3" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } } }, "longest": { @@ -3468,10 +3136,14 @@ } }, "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", - "dev": true + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", + "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } }, "map-obj": { "version": "1.0.1", @@ -3480,9 +3152,9 @@ "dev": true }, "mathml-tag-names": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.0.1.tgz", - "integrity": "sha1-jUEmgWi/htEQK5gQnijlMeejRXg=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz", + "integrity": "sha512-3Zs9P/0zzwTob2pdgT0CHZuMbnSUSp8MB1bddfm+HDmnFWHGT4jvEZRf+2RuPoa+cjdn/z25SEt5gFTqdhvJAg==", "dev": true }, "maxmin": { @@ -3495,6 +3167,40 @@ "figures": "1.7.0", "gzip-size": "1.0.0", "pretty-bytes": "1.0.4" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } } }, "media-typer": { @@ -3514,7 +3220,7 @@ "loud-rejection": "1.6.0", "map-obj": "1.0.1", "minimist": "1.2.0", - "normalize-package-data": "2.3.8", + "normalize-package-data": "2.4.0", "object-assign": "4.1.1", "read-pkg-up": "1.0.1", "redent": "1.0.0", @@ -3547,43 +3253,43 @@ "normalize-path": "2.1.1", "object.omit": "2.0.1", "parse-glob": "3.0.4", - "regex-cache": "0.4.3" + "regex-cache": "0.4.4" } }, "mime": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz", - "integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA=", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true }, "mime-db": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", - "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", "dev": true }, "mime-types": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", - "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "dev": true, "requires": { - "mime-db": "1.27.0" + "mime-db": "1.33.0" } }, "mimic-fn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", - "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -3637,19 +3343,19 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1.1.0" + "abbrev": "1.1.1" } }, "normalize-package-data": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz", - "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs=", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { - "hosted-git-info": "2.4.2", + "hosted-git-info": "2.6.0", "is-builtin-module": "1.0.0", "semver": "5.3.0", - "validate-npm-package-license": "3.0.1" + "validate-npm-package-license": "3.0.3" } }, "normalize-path": { @@ -3658,7 +3364,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "1.0.2" + "remove-trailing-separator": "1.1.0" } }, "normalize-range": { @@ -3727,9 +3433,9 @@ } }, "onecolor": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/onecolor/-/onecolor-3.0.4.tgz", - "integrity": "sha1-daRvgNpseqpbTarhekcZi9llJJQ=", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/onecolor/-/onecolor-3.0.5.tgz", + "integrity": "sha1-Nu/zIgE3nv3xGA+0ReUajiQl+fY=", "dev": true }, "onetime": { @@ -3738,7 +3444,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "1.1.0" + "mimic-fn": "1.2.0" } }, "optimist": { @@ -3818,9 +3524,9 @@ "dev": true }, "parseurl": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", - "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", "dev": true }, "path-exists": { @@ -3856,9 +3562,9 @@ } }, "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true, "optional": true }, @@ -3889,7 +3595,7 @@ "integrity": "sha1-GVV2CVno0aEcsqUOyD7sRwYz5J8=", "dev": true, "requires": { - "onecolor": "3.0.4", + "onecolor": "3.0.5", "synesthesia": "1.0.1" } }, @@ -3899,7 +3605,7 @@ "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=", "dev": true, "requires": { - "irregular-plurals": "1.3.0" + "irregular-plurals": "1.4.0" } }, "pluralize": { @@ -3909,72 +3615,51 @@ "dev": true }, "postcss": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.7.tgz", - "integrity": "sha1-agl0d8RtE9BWCoF9aavAuuVJ0KA=", + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", "dev": true, "requires": { - "chalk": "2.0.1", - "source-map": "0.5.6", - "supports-color": "4.2.0" + "chalk": "1.1.3", + "js-base64": "2.4.3", + "source-map": "0.5.7", + "supports-color": "3.2.3" }, "dependencies": { - "ansi-styles": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.1.0.tgz", - "integrity": "sha1-CcIC1ckX7CMYjKpcnLkXnNlUd1A=", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, "chalk": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.0.1.tgz", - "integrity": "sha1-2+xJQ20q4V9TYRTnbRRlbNvA9E0=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "3.1.0", + "ansi-styles": "2.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "4.2.0" + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } } }, - "supports-color": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.0.tgz", - "integrity": "sha1-rZhtx+sjFdAJtNd8gWnCIxpoQDc=", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, - "postcss-less": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-1.1.0.tgz", - "integrity": "sha1-vcx2vmTEMk2HP7xc2foueZ5DBfo=", - "dev": true, - "requires": { - "postcss": "5.2.17" - }, - "dependencies": { "has-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", "dev": true }, - "postcss": { - "version": "5.2.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.17.tgz", - "integrity": "sha1-z09Ze4ZNZcikkrLqvp1wbIecOIs=", + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "chalk": "1.1.3", - "js-base64": "2.1.9", - "source-map": "0.5.6", - "supports-color": "3.2.3" + "ansi-regex": "2.1.1" } }, "supports-color": { @@ -3988,6 +3673,15 @@ } } }, + "postcss-less": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-0.14.0.tgz", + "integrity": "sha1-xjGwicbM5CK5oQ86lY0r7dOBkyQ=", + "dev": true, + "requires": { + "postcss": "5.2.18" + } + }, "postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", @@ -3995,14 +3689,39 @@ "dev": true }, "postcss-reporter": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-4.0.0.tgz", - "integrity": "sha1-EzVsNlw2eDrd6I4o4J27puxsZQE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-3.0.0.tgz", + "integrity": "sha1-CeoPN6RExWk4eGBuCbAY6+/3z48=", "dev": true, "requires": { "chalk": "1.1.3", - "lodash": "4.17.4", - "log-symbols": "1.0.2" + "lodash": "4.17.10", + "log-symbols": "1.0.2", + "postcss": "5.2.18" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } } }, "postcss-resolve-nested-selector": { @@ -4012,12 +3731,12 @@ "dev": true }, "postcss-scss": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-1.0.2.tgz", - "integrity": "sha1-/0XPM1S4ee6JpOtoaA9GrJuxT5Q=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-0.4.1.tgz", + "integrity": "sha1-rXcbgfD3L19IRdCKpg+TVXZT1Uw=", "dev": true, "requires": { - "postcss": "6.0.7" + "postcss": "5.2.18" } }, "postcss-selector-parser": { @@ -4060,9 +3779,9 @@ } }, "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true }, "progress": { @@ -4072,19 +3791,19 @@ "dev": true }, "promise": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz", - "integrity": "sha1-SJZUxpJha4qlWwck+oCbt9tJxb8=", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "dev": true, "optional": true, "requires": { - "asap": "2.0.5" + "asap": "2.0.6" } }, "prr": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", - "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true, "optional": true }, @@ -4095,23 +3814,22 @@ "dev": true }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true, - "optional": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", + "dev": true }, "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", "dev": true, "optional": true }, "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", "dev": true, "requires": { "is-number": "3.0.0", @@ -4133,7 +3851,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } @@ -4144,7 +3862,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } @@ -4190,7 +3908,7 @@ "dev": true, "requires": { "load-json-file": "1.1.0", - "normalize-package-data": "2.3.8", + "normalize-package-data": "2.4.0", "path-type": "1.1.0" } }, @@ -4205,17 +3923,17 @@ } }, "readable-stream": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", - "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "buffer-shims": "1.0.0", "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", "util-deprecate": "1.0.2" } }, @@ -4230,19 +3948,18 @@ } }, "regex-cache": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", - "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, "requires": { - "is-equal-shallow": "0.1.3", - "is-primitive": "2.0.0" + "is-equal-shallow": "0.1.3" } }, "remove-trailing-separator": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz", - "integrity": "sha1-abBi2XhyetFNxrVrpKt3L9jXBRE=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, "repeat-element": { @@ -4267,34 +3984,34 @@ } }, "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "version": "2.85.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", + "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", "dev": true, "optional": true, "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", + "aws-sign2": "0.7.0", + "aws4": "1.7.0", "caseless": "0.12.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", + "mime-types": "2.1.18", "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.2", "stringstream": "0.0.5", - "tough-cookie": "2.3.2", + "tough-cookie": "2.3.4", "tunnel-agent": "0.6.0", - "uuid": "3.0.1" + "uuid": "3.2.1" } }, "require-from-string": { @@ -4345,9 +4062,9 @@ } }, "rimraf": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", - "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { "glob": "7.1.2" @@ -4378,9 +4095,15 @@ } }, "safe-buffer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", - "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "sax": { @@ -4410,6 +4133,12 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "dev": true + }, "shifter": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/shifter/-/shifter-0.5.0.tgz", @@ -4426,11 +4155,11 @@ "mkdirp": "0.5.1", "nopt": "3.0.6", "progress": "0.1.0", - "rimraf": "2.6.1", + "rimraf": "2.6.2", "timethat": "0.0.3", - "walkdir": "0.0.11", + "walkdir": "0.0.12", "watch": "0.8.0", - "which": "1.2.14", + "which": "1.3.0", "yuglify": "0.1.4", "yui-lint": "0.2.0", "yuicompressor": "2.4.7", @@ -4458,52 +4187,66 @@ "dev": true }, "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0" + } }, "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "dev": true, "optional": true, "requires": { - "hoek": "2.16.3" + "hoek": "4.2.1" } }, "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { - "spdx-license-ids": "1.2.2" + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" } }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", "dev": true }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" + } + }, "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", "dev": true }, "specificity": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.3.1.tgz", - "integrity": "sha1-8bBoQkzjF64HR42V3jwhz4Xo1Wc=", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.3.2.tgz", + "integrity": "sha512-Nc/QN/A425Qog7j9aHmwOrlwX2e7pNI47ciwxwy4jOlvbbMHkNNJchit+FX+UjF3IAdiaaV5BKeWuDUnws6G1A==", "dev": true }, "split2": { @@ -4522,9 +4265,9 @@ "dev": true }, "sshpk": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", - "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "dev": true, "optional": true, "requires": { @@ -4534,24 +4277,14 @@ "dashdash": "1.14.1", "ecc-jsbn": "0.1.1", "getpass": "0.1.7", - "jodid25519": "1.0.2", "jsbn": "0.1.1", "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "optional": true - } } }, "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, "stream-combiner": { @@ -4607,32 +4340,15 @@ "requires": { "is-fullwidth-code-point": "2.0.0", "strip-ansi": "4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } } }, "string_decoder": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", - "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.0.1" + "safe-buffer": "5.1.2" } }, "stringstream": { @@ -4643,12 +4359,29 @@ "optional": true }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + } + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" } }, "strip-indent": { @@ -4661,9 +4394,9 @@ } }, "strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, "style-search": { @@ -4683,7 +4416,7 @@ "log-symbols": "1.0.2", "minimist": "1.2.0", "plur": "2.1.2", - "postcss": "5.2.17", + "postcss": "5.2.18", "postcss-reporter": "1.4.1", "postcss-selector-parser": "2.2.3", "read-file-stdin": "0.2.1", @@ -4691,40 +4424,25 @@ "write-file-stdout": "0.0.2" }, "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "caniuse-db": "1.0.30000703", - "electron-to-chromium": "1.3.16" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, - "postcss": { - "version": "5.2.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.17.tgz", - "integrity": "sha1-z09Ze4ZNZcikkrLqvp1wbIecOIs=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.1.9", - "source-map": "0.5.6", - "supports-color": "3.2.3" - } - }, "postcss-reporter": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-1.4.1.tgz", @@ -4732,321 +4450,88 @@ "dev": true, "requires": { "chalk": "1.1.3", - "lodash": "4.17.4", + "lodash": "4.17.10", "log-symbols": "1.0.2", - "postcss": "5.2.17" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "stylelint": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-8.0.0.tgz", - "integrity": "sha1-h2ESEXdssxXJP89sWLwmHTySYS4=", - "dev": true, - "requires": { - "autoprefixer": "7.1.2", - "balanced-match": "1.0.0", - "chalk": "2.0.1", - "cosmiconfig": "2.1.3", - "debug": "2.6.8", - "execall": "1.0.0", - "file-entry-cache": "2.0.0", - "get-stdin": "5.0.1", - "globby": "6.1.0", - "globjoin": "0.1.4", - "html-tags": "2.0.0", - "ignore": "3.3.3", - "imurmurhash": "0.1.4", - "known-css-properties": "0.2.0", - "lodash": "4.17.4", - "log-symbols": "1.0.2", - "mathml-tag-names": "2.0.1", - "meow": "3.7.0", - "micromatch": "2.3.11", - "normalize-selector": "0.2.0", - "pify": "3.0.0", - "postcss": "6.0.7", - "postcss-less": "1.1.0", - "postcss-media-query-parser": "0.2.3", - "postcss-reporter": "4.0.0", - "postcss-resolve-nested-selector": "0.1.1", - "postcss-scss": "1.0.2", - "postcss-selector-parser": "2.2.3", - "postcss-value-parser": "3.3.0", - "resolve-from": "3.0.0", - "specificity": "0.3.1", - "string-width": "2.1.1", - "style-search": "0.1.0", - "sugarss": "1.0.0", - "svg-tags": "1.0.0", - "table": "4.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.1.0.tgz", - "integrity": "sha1-CcIC1ckX7CMYjKpcnLkXnNlUd1A=", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "chalk": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.0.1.tgz", - "integrity": "sha1-2+xJQ20q4V9TYRTnbRRlbNvA9E0=", - "dev": true, - "requires": { - "ansi-styles": "3.1.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.2.0" - } - }, - "get-stdin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", - "dev": true - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "postcss": "5.2.18" } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "supports-color": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.0.tgz", - "integrity": "sha1-rZhtx+sjFdAJtNd8gWnCIxpoQDc=", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - }, - "table": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.1.tgz", - "integrity": "sha1-qBFsEz+sLGH0pCCrbN9cTWHw5DU=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ajv": "4.11.8", - "ajv-keywords": "1.5.1", - "chalk": "1.1.3", - "lodash": "4.17.4", - "slice-ansi": "0.0.4", - "string-width": "2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "ansi-regex": "2.1.1" } } } }, - "stylelint-checkstyle-formatter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stylelint-checkstyle-formatter/-/stylelint-checkstyle-formatter-0.1.0.tgz", - "integrity": "sha1-jEAoU+kqCq6DcVZwyvY+efIRmac=", - "dev": true, - "requires": { - "lodash": "3.10.1" - }, - "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true - } - } - }, - "stylelint-csstree-validator": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stylelint-csstree-validator/-/stylelint-csstree-validator-1.1.1.tgz", - "integrity": "sha1-7ToeLEgt9QZEcx/PVX4t/6nY3H8=", + "stylelint": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-8.0.0.tgz", + "integrity": "sha512-k1GkRhOtghvYu5PWCdec7SNN22KZZLq4TL1vVyykBvHr91oUS7eVfX2IAZJjBpYKh9Gdep+AnSZCwuUn+J76Bw==", "dev": true, "requires": { - "css-tree": "1.0.0-alpha16", - "stylelint": "7.13.0" + "autoprefixer": "7.2.6", + "balanced-match": "1.0.0", + "chalk": "2.4.1", + "cosmiconfig": "2.2.2", + "debug": "2.6.9", + "execall": "1.0.0", + "file-entry-cache": "2.0.0", + "get-stdin": "5.0.1", + "globby": "6.1.0", + "globjoin": "0.1.4", + "html-tags": "2.0.0", + "ignore": "3.3.8", + "imurmurhash": "0.1.4", + "known-css-properties": "0.2.0", + "lodash": "4.17.10", + "log-symbols": "1.0.2", + "mathml-tag-names": "2.1.0", + "meow": "3.7.0", + "micromatch": "2.3.11", + "normalize-selector": "0.2.0", + "pify": "3.0.0", + "postcss": "6.0.22", + "postcss-less": "1.1.5", + "postcss-media-query-parser": "0.2.3", + "postcss-reporter": "4.0.0", + "postcss-resolve-nested-selector": "0.1.1", + "postcss-scss": "1.0.5", + "postcss-selector-parser": "2.2.3", + "postcss-value-parser": "3.3.0", + "resolve-from": "3.0.0", + "specificity": "0.3.2", + "string-width": "2.1.1", + "style-search": "0.1.0", + "sugarss": "1.0.1", + "svg-tags": "1.0.0", + "table": "4.0.3" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, "autoprefixer": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", - "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.2.6.tgz", + "integrity": "sha512-Iq8TRIB+/9eQ8rbGhcP7ct5cYb/3qjNYAR2SnzLCEcwF6rvVOax8+9+fccgXk4bEhQGjOZd5TLhsksmAdsbGqQ==", "dev": true, "requires": { - "browserslist": "1.7.7", - "caniuse-db": "1.0.30000703", + "browserslist": "2.11.3", + "caniuse-lite": "1.0.30000833", "normalize-range": "0.1.2", "num2fraction": "1.2.2", - "postcss": "5.2.17", + "postcss": "6.0.22", "postcss-value-parser": "3.3.0" } }, "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000703", - "electron-to-chromium": "1.3.16" - } - }, - "chalk": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.0.1.tgz", - "integrity": "sha1-2+xJQ20q4V9TYRTnbRRlbNvA9E0=", + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", + "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", "dev": true, "requires": { - "ansi-styles": "3.1.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.1.0.tgz", - "integrity": "sha1-CcIC1ckX7CMYjKpcnLkXnNlUd1A=", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "supports-color": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.0.tgz", - "integrity": "sha1-rZhtx+sjFdAJtNd8gWnCIxpoQDc=", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } + "caniuse-lite": "1.0.30000833", + "electron-to-chromium": "1.3.45" } }, "get-stdin": { @@ -5066,30 +4551,40 @@ "object-assign": "4.1.1", "pify": "2.3.0", "pinkie-promise": "2.0.1" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "postcss": { - "version": "5.2.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.17.tgz", - "integrity": "sha1-z09Ze4ZNZcikkrLqvp1wbIecOIs=", + "version": "6.0.22", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz", + "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==", "dev": true, "requires": { - "chalk": "1.1.3", - "js-base64": "2.1.9", - "source-map": "0.5.6", - "supports-color": "3.2.3" + "chalk": "2.4.1", + "source-map": "0.6.1", + "supports-color": "5.4.0" + } + }, + "postcss-less": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-1.1.5.tgz", + "integrity": "sha512-QQIiIqgEjNnquc0d4b6HDOSFZxbFQoy4MPpli2lSLpKhMyBkKwwca2HFqu4xzxlKID/F2fxSOowwtKpgczhF7A==", + "dev": true, + "requires": { + "postcss": "5.2.18" }, "dependencies": { "chalk": { @@ -5112,28 +4607,51 @@ "dev": true } } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "1.1.3", + "js-base64": "2.4.3", + "source-map": "0.5.7", + "supports-color": "3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } } } }, - "postcss-less": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-0.14.0.tgz", - "integrity": "sha1-xjGwicbM5CK5oQ86lY0r7dOBkyQ=", - "dev": true, - "requires": { - "postcss": "5.2.17" - } - }, "postcss-reporter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-3.0.0.tgz", - "integrity": "sha1-CeoPN6RExWk4eGBuCbAY6+/3z48=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-4.0.0.tgz", + "integrity": "sha512-IEVx20y277AIs3bZ6sUdzdq0YOE2RRbwnjUvTMfYYZmws0mE7YgqxZd0J8j60Byaf/QbjxyLfFJEQHH2bb+ecA==", "dev": true, "requires": { "chalk": "1.1.3", - "lodash": "4.17.4", - "log-symbols": "1.0.2", - "postcss": "5.2.17" + "lodash": "4.17.10", + "log-symbols": "1.0.2" }, "dependencies": { "chalk": { @@ -5158,12 +4676,12 @@ } }, "postcss-scss": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-0.4.1.tgz", - "integrity": "sha1-rXcbgfD3L19IRdCKpg+TVXZT1Uw=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-1.0.5.tgz", + "integrity": "sha512-gJB1tKYMkBy0MU+COt6WXA4ZiRctAKoWLa6qD7a6bbEbBMqrpa/BhfQdN80eYMV+JkKddZVEpZlOggnGShpvyg==", "dev": true, "requires": { - "postcss": "5.2.17" + "postcss": "6.0.22" } }, "resolve-from": { @@ -5172,27 +4690,99 @@ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } + "ansi-regex": "2.1.1" + } + }, + "sugarss": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-1.0.1.tgz", + "integrity": "sha512-3qgLZytikQQEVn1/FrhY7B68gPUUGY3R1Q1vTiD5xT+Ti1DP/8iZuwFet9ONs5+bmL8pZoDQ6JrQHVgrNlK6mA==", + "dev": true, + "requires": { + "postcss": "6.0.22" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "stylelint-checkstyle-formatter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stylelint-checkstyle-formatter/-/stylelint-checkstyle-formatter-0.1.0.tgz", + "integrity": "sha1-jEAoU+kqCq6DcVZwyvY+efIRmac=", + "dev": true, + "requires": { + "lodash": "3.10.1" + }, + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + } + } + }, + "stylelint-csstree-validator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stylelint-csstree-validator/-/stylelint-csstree-validator-1.1.1.tgz", + "integrity": "sha1-7ToeLEgt9QZEcx/PVX4t/6nY3H8=", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha16", + "stylelint": "7.13.0" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + }, + "get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "dev": true + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" } }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, "stylelint": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-7.13.0.tgz", @@ -5201,10 +4791,10 @@ "requires": { "autoprefixer": "6.7.7", "balanced-match": "0.4.2", - "chalk": "2.0.1", - "colorguard": "1.2.0", - "cosmiconfig": "2.1.3", - "debug": "2.6.8", + "chalk": "2.4.1", + "colorguard": "1.2.1", + "cosmiconfig": "2.2.2", + "debug": "2.6.9", "doiuse": "2.6.0", "execall": "1.0.0", "file-entry-cache": "2.0.0", @@ -5212,17 +4802,17 @@ "globby": "6.1.0", "globjoin": "0.1.4", "html-tags": "2.0.0", - "ignore": "3.3.3", + "ignore": "3.3.8", "imurmurhash": "0.1.4", "known-css-properties": "0.2.0", - "lodash": "4.17.4", + "lodash": "4.17.10", "log-symbols": "1.0.2", - "mathml-tag-names": "2.0.1", + "mathml-tag-names": "2.1.0", "meow": "3.7.0", "micromatch": "2.3.11", "normalize-selector": "0.2.0", "pify": "2.3.0", - "postcss": "5.2.17", + "postcss": "5.2.18", "postcss-less": "0.14.0", "postcss-media-query-parser": "0.2.3", "postcss-reporter": "3.0.0", @@ -5231,77 +4821,24 @@ "postcss-selector-parser": "2.2.3", "postcss-value-parser": "3.3.0", "resolve-from": "3.0.0", - "specificity": "0.3.1", + "specificity": "0.3.2", "string-width": "2.1.1", "style-search": "0.1.0", "stylehacks": "2.3.2", "sugarss": "0.2.0", "svg-tags": "1.0.0", - "table": "4.0.1" - } - }, - "sugarss": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-0.2.0.tgz", - "integrity": "sha1-rDQjdWMyfG/4l7ZHQr9q7BkK054=", - "dev": true, - "requires": { - "postcss": "5.2.17" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - }, - "table": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.1.tgz", - "integrity": "sha1-qBFsEz+sLGH0pCCrbN9cTWHw5DU=", - "dev": true, - "requires": { - "ajv": "4.11.8", - "ajv-keywords": "1.5.1", - "chalk": "1.1.3", - "lodash": "4.17.4", - "slice-ansi": "0.0.4", - "string-width": "2.1.1" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "table": "4.0.3" } } } }, "sugarss": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-1.0.0.tgz", - "integrity": "sha1-ZeUbOVhDL7cNVFGmi7M+MtDPHvc=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-0.2.0.tgz", + "integrity": "sha1-rDQjdWMyfG/4l7ZHQr9q7BkK054=", "dev": true, "requires": { - "postcss": "6.0.7" + "postcss": "5.2.18" } }, "supports-color": { @@ -5326,17 +4863,31 @@ } }, "table": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.1.tgz", - "integrity": "sha1-qBFsEz+sLGH0pCCrbN9cTWHw5DU=", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", + "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "dev": true, "requires": { - "ajv": "4.11.8", - "ajv-keywords": "1.5.1", - "chalk": "1.1.3", - "lodash": "4.17.4", - "slice-ansi": "0.0.4", + "ajv": "6.4.0", + "ajv-keywords": "3.2.0", + "chalk": "2.4.1", + "lodash": "4.17.10", + "slice-ansi": "1.0.0", "string-width": "2.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", + "integrity": "sha1-06/3jpJ3VJdx2vAWTP9ISCt1T8Y=", + "dev": true, + "requires": { + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1", + "uri-js": "3.0.2" + } + } } }, "text-table": { @@ -5402,8 +4953,8 @@ "body-parser": "1.14.2", "debug": "2.2.0", "faye-websocket": "0.10.0", - "livereload-js": "2.2.2", - "parseurl": "1.3.1", + "livereload-js": "2.3.0", + "parseurl": "1.3.2", "qs": "5.1.0" }, "dependencies": { @@ -5431,22 +4982,31 @@ } }, "tmp": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", - "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { "os-tmpdir": "1.0.2" } }, "tough-cookie": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "dev": true, "optional": true, "requires": { "punycode": "1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true, + "optional": true + } } }, "trim-newlines": { @@ -5455,12 +5015,6 @@ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true }, - "tryit": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", - "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", - "dev": true - }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -5468,7 +5022,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "5.0.1" + "safe-buffer": "5.1.2" } }, "tweetnacl": { @@ -5488,13 +5042,13 @@ } }, "type-is": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", "dev": true, "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.15" + "mime-types": "2.1.18" } }, "typedarray": { @@ -5510,7 +5064,7 @@ "dev": true, "requires": { "async": "0.2.10", - "source-map": "0.5.6", + "source-map": "0.5.7", "uglify-to-browserify": "1.0.2", "yargs": "3.10.0" }, @@ -5553,6 +5107,15 @@ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, + "uri-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", + "integrity": "sha1-+QuFhQf4HepNz7s8TD2/orVX+qo=", + "dev": true, + "requires": { + "punycode": "2.1.0" + } + }, "uri-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz", @@ -5566,36 +5129,38 @@ "dev": true }, "uuid": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", - "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", "dev": true, "optional": true }, "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", "dev": true, "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" } }, "verror": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", - "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "optional": true, "requires": { - "extsprintf": "1.0.2" + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" } }, "walkdir": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz", - "integrity": "sha1-oW0CXrkxvQO1LzCMrtD0D86+lTI=", + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.12.tgz", + "integrity": "sha512-HFhaD4mMWPzFSqhpyDG48KDdrjfn409YQuVW7ckZYhW4sE87mYtWifdB/+73RA7+p4s4K18n5Jfx1kHthE1gBw==", "dev": true }, "watch": { @@ -5605,24 +5170,25 @@ "dev": true }, "websocket-driver": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", - "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", + "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", "dev": true, "requires": { - "websocket-extensions": "0.1.1" + "http-parser-js": "0.4.12", + "websocket-extensions": "0.1.3" } }, "websocket-extensions": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz", - "integrity": "sha1-domUmcGEtu91Q3fC27DNbLVdKec=", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "dev": true }, "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "dev": true, "requires": { "isexe": "2.0.0" @@ -5737,7 +5303,7 @@ "integrity": "sha1-bMzZd7gBMqB3MdbozljCyDA8+a8=", "dev": true, "requires": { - "abbrev": "1.1.0" + "abbrev": "1.1.1" } }, "uglify-js": { From 68f2241414bca7b854da5e49299612a79c2ca4f8 Mon Sep 17 00:00:00 2001 From: Derick Turner Date: Fri, 18 May 2018 11:10:45 +0100 Subject: [PATCH 25/35] IOMAD: Warning emails should not be sent to managers when the user email is also to the manager. closes #936 --- local/email_reports/lib.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/local/email_reports/lib.php b/local/email_reports/lib.php index 23eb9e760a55..75622ee72b36 100644 --- a/local/email_reports/lib.php +++ b/local/email_reports/lib.php @@ -201,7 +201,9 @@ function email_reports_cron() { } $managerusers = $DB->get_records_sql("SELECT * FROM {" . $tempcomptablename . "} WHERE userid IN (" . $departmentids . ") - $companysql"); + AND userid != :managerid + $companysql", + array('managerid' => $manager->userid)); $summary = "" . "" . @@ -429,7 +431,9 @@ function email_reports_cron() { } $managerusers = $DB->get_records_sql("SELECT * FROM {" . $tempcomptablename . "} WHERE userid IN (" . $departmentids . ") - $companysql"); + AND userid != :managerid + $companysql", + array('managerid' => $manager->userid)); $summary = "
" . get_string('firstname') . "" . get_string('lastname') . "
" . "" . "" . @@ -562,9 +566,10 @@ function email_reports_cron() { JOIN {company_users} cu ON (u.id = cu.userid) JOIN {department} d ON (cu.departmentid = d.id) WHERE cc.userid IN (" . $departmentids . ") - $companyusql + AND cc.userid != :managerid + $companysql AND cc.timecompleted > :weekago", - array('weekago' => $runtime - (60 * 60 * 24 * 7))); + array('managerid' => $manager->userid, 'weekago' => $runtime - (60 * 60 * 24 * 7))); $summary = "
" . get_string('firstname') . "" . get_string('lastname') . "" . get_string('email') . "
" . "" . "" . From 63e64aa6cb88ddbe60b972707c775533fcf3ca1c Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Sun, 20 May 2018 15:14:50 +0800 Subject: [PATCH 26/35] MDL-62500 tag: Update checkbox label when updating tag --- lib/amd/build/tag.min.js | 2 +- lib/amd/src/tag.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/amd/build/tag.min.js b/lib/amd/build/tag.min.js index 586dcbc8e8b0..ab06031a490e 100644 --- a/lib/amd/build/tag.min.js +++ b/lib/amd/build/tag.min.js @@ -1 +1 @@ -define(["jquery","core/ajax","core/templates","core/notification","core/str","core/yui"],function(a,b,c,d,e,f){return{initTagindexPage:function(){a("body").delegate(".tagarea[data-ta] a[data-quickload=1]","click",function(d){d.preventDefault();var e=a(this),f=e[0].search.replace(/^\?/,""),g=e.closest(".tagarea[data-ta]"),h=f.split("&").reduce(function(a,b){var c=b.split("=");return a[c[0]]=decodeURIComponent(c[1]),a},{}),i=b.call([{methodname:"core_tag_get_tagindex",args:{tagindex:h}}],!0);a.when.apply(a,i).done(function(a){c.render("core_tag/index",a).done(function(a){g.replaceWith(a)})})})},initManagePage:function(){a("body").on("updated","[data-inplaceeditable]",function(b){if(e.get_string("now").done(function(c){a(b.target).closest("tr").find("td.col-timemodified").html(c)}),"tagflag"===b.ajaxreturn.itemtype){var c=a(b.target).closest("tr");"0"===b.ajaxreturn.value?c.removeClass("flagged-tag"):c.addClass("flagged-tag")}}),a(".tag-management-table").delegate("a.tagdelete","click",function(b){b.preventDefault();var c=a(this).attr("href");e.get_strings([{key:"delete"},{key:"confirmdeletetag",component:"tag"},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],function(){window.location.href=c})})}),a("#tag-management-delete").click(function(b){var c=a(this).closest("form").get(0),f=a(c).find("input[type=checkbox]:checked").length;if(f){var g=a("").attr("name",this.name);b.preventDefault(),e.get_strings([{key:"delete"},{key:"confirmdeletetags",component:"tag"},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],function(){g.appendTo(c),c.submit()})})}}),a("#tag-management-combine").click(function(b){b.preventDefault();var c=a(this).closest("form").get(0),g=a(c).find("input[type=checkbox]:checked");if(g.length<=1)return void e.get_strings([{key:"combineselected",component:"tag"},{key:"selectmultipletags",component:"tag"},{key:"ok"}]).done(function(a){d.alert(a[0],a[1],a[2])});var h=a("").attr("name",this.name);e.get_strings([{key:"combineselected",component:"tag"},{key:"selectmaintag",component:"tag"},{key:"continue"},{key:"cancel"}]).done(function(b){var d=a('

');d.find(".description").html(b[1]),d.find("#combinetags_submit").attr("value",b[2]),d.find("#combinetags_cancel").attr("value",b[3]);var e=d.find(".options");g.each(function(){var b=a(this).val(),c=a(".inplaceeditable[data-itemtype=tagname][data-itemid="+b+"]").attr("data-value");e.append(a('
"))}),f.use("moodle-core-notification-dialogue",function(){var e=new M.core.dialogue({draggable:!0,modal:!0,closeButton:!0,headerContent:b[0],bodyContent:d.html()});e.show(),a("#combinetags_form input[type=radio]").first().focus().prop("checked",!0),a("#combinetags_form #combinetags_cancel").on("click",function(){e.destroy()}),a("#combinetags_form").on("submit",function(){h.appendTo(c);var b=a("input[name=maintag]:checked","#combinetags_form").val();return a("").attr("name","maintag").attr("value",b).appendTo(c),c.submit(),!1})})})}),a("body").on("updatefailed","[data-inplaceeditable][data-itemtype=tagname]",function(b){var c=b.exception,f=b.newvalue,g=a(b.target).attr("data-itemid");"namesalreadybeeingused"===c.errorcode&&(b.preventDefault(),e.get_strings([{key:"nameuseddocombine",component:"tag"},{key:"yes"},{key:"cancel"}]).done(function(a){d.confirm(b.message,a[0],a[1],a[2],function(){window.location.href=window.location.href+"&newname="+encodeURIComponent(f)+"&tagid="+encodeURIComponent(g)+"&action=renamecombine&sesskey="+M.cfg.sesskey})}))}),a("body").on("click","a[data-action=addstandardtag]",function(b){b.preventDefault(),e.get_strings([{key:"addotags",component:"tag"},{key:"inputstandardtags",component:"tag"},{key:"continue"},{key:"cancel"}]).done(function(b){var c=a('

');c.find("#addtags_form").attr("action",window.location.href),c.find("#addtags_submit").attr("value",b[2]),c.find("#addtags_cancel").attr("value",b[3]),f.use("moodle-core-notification-dialogue",function(){var d=new M.core.dialogue({draggable:!0,modal:!0,closeButton:!0,headerContent:b[0],bodyContent:c.html()});d.show(),a("#addtags_form input[type=text]").focus(),a("#addtags_form #addtags_cancel").on("click",function(){d.destroy()})})})})},initManageCollectionsPage:function(){a("body").on("updated","[data-inplaceeditable]",function(b){var c,d,e,f=b.ajaxreturn;"core_tag"===f.component&&"tagareaenable"===f.itemtype&&(c=a(this).attr("data-itemid"),a(".tag-collections-table ul[data-collectionid] li[data-areaid="+c+"]").hide(),e=f.value,"1"===e?(a(this).closest("tr").removeClass("dimmed_text"),d=a(this).closest("tr").find('[data-itemtype="tagareacollection"]').attr("data-value"),a(".tag-collections-table ul[data-collectionid="+d+"] li[data-areaid="+c+"]").show()):a(this).closest("tr").addClass("dimmed_text")),"core_tag"===f.component&&"tagareacollection"===f.itemtype&&(c=a(this).attr("data-itemid"),a(".tag-collections-table ul[data-collectionid] li[data-areaid="+c+"]").hide(),d=a(this).attr("data-value"),e=a(this).closest("tr").find('[data-itemtype="tagareaenable"]').attr("data-value"),"1"===e&&a(".tag-collections-table ul[data-collectionid="+d+"] li[data-areaid="+c+"]").show())}),a("body").on("click",".addtagcoll > a",function(b){b.preventDefault();var c=a(this).attr("data-url")+"&sesskey="+M.cfg.sesskey;e.get_strings([{key:"addtagcoll",component:"tag"},{key:"name"},{key:"searchable",component:"tag"},{key:"create"},{key:"cancel"}]).done(function(b){var d=a('

:

:

');d.find('label[for="addtagcoll_name"]').html(b[1]),d.find('label[for="addtagcoll_searchable"]').html(b[2]),d.find("#addtagcoll_submit").attr("value",b[3]),d.find("#addtagcoll_cancel").attr("value",b[4]),f.use("moodle-core-notification-dialogue",function(){var e=new M.core.dialogue({draggable:!0,modal:!0,closeButton:!0,headerContent:b[0],bodyContent:d.html()});e.show(),a("#addtagcoll_form #addtagcoll_name").focus(),a("#addtagcoll_form #addtagcoll_cancel").on("click",function(){e.destroy()}),a("#addtagcoll_form").on("submit",function(){var b=a("#addtagcoll_form #addtagcoll_name").val(),d=a("#addtagcoll_form #addtagcoll_searchable").prop("checked")?1:0;return String(b).length>0&&(window.location.href=c+"&name="+encodeURIComponent(b)+"&searchable="+d),!1})})})}),a("body").on("click",".tag-collections-table .action_delete",function(b){b.preventDefault();var c=a(this).attr("data-url")+"&sesskey="+M.cfg.sesskey;e.get_strings([{key:"delete"},{key:"suredeletecoll",component:"tag",param:a(this).attr("data-collname")},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],function(){window.location.href=c})})})}}}); \ No newline at end of file +define(["jquery","core/ajax","core/templates","core/notification","core/str","core/yui"],function(a,b,c,d,e,f){return{initTagindexPage:function(){a("body").delegate(".tagarea[data-ta] a[data-quickload=1]","click",function(d){d.preventDefault();var e=a(this),f=e[0].search.replace(/^\?/,""),g=e.closest(".tagarea[data-ta]"),h=f.split("&").reduce(function(a,b){var c=b.split("=");return a[c[0]]=decodeURIComponent(c[1]),a},{}),i=b.call([{methodname:"core_tag_get_tagindex",args:{tagindex:h}}],!0);a.when.apply(a,i).done(function(a){c.render("core_tag/index",a).done(function(a){g.replaceWith(a)})})})},initManagePage:function(){a("body").on("updated","[data-inplaceeditable]",function(b){if(e.get_string("selecttag","core_tag",b.ajaxreturn.value).then(function(c){return a('label[for="tagselect'+b.ajaxreturn.itemid+'"]').html(c)}).fail(d.exception),e.get_string("now").done(function(c){a(b.target).closest("tr").find("td.col-timemodified").html(c)}),"tagflag"===b.ajaxreturn.itemtype){var c=a(b.target).closest("tr");"0"===b.ajaxreturn.value?c.removeClass("flagged-tag"):c.addClass("flagged-tag")}}),a(".tag-management-table").delegate("a.tagdelete","click",function(b){b.preventDefault();var c=a(this).attr("href");e.get_strings([{key:"delete"},{key:"confirmdeletetag",component:"tag"},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],function(){window.location.href=c})})}),a("#tag-management-delete").click(function(b){var c=a(this).closest("form").get(0),f=a(c).find("input[type=checkbox]:checked").length;if(f){var g=a("").attr("name",this.name);b.preventDefault(),e.get_strings([{key:"delete"},{key:"confirmdeletetags",component:"tag"},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],function(){g.appendTo(c),c.submit()})})}}),a("#tag-management-combine").click(function(b){b.preventDefault();var c=a(this).closest("form").get(0),g=a(c).find("input[type=checkbox]:checked");if(g.length<=1)return void e.get_strings([{key:"combineselected",component:"tag"},{key:"selectmultipletags",component:"tag"},{key:"ok"}]).done(function(a){d.alert(a[0],a[1],a[2])});var h=a("").attr("name",this.name);e.get_strings([{key:"combineselected",component:"tag"},{key:"selectmaintag",component:"tag"},{key:"continue"},{key:"cancel"}]).done(function(b){var d=a('

');d.find(".description").html(b[1]),d.find("#combinetags_submit").attr("value",b[2]),d.find("#combinetags_cancel").attr("value",b[3]);var e=d.find(".options");g.each(function(){var b=a(this).val(),c=a(".inplaceeditable[data-itemtype=tagname][data-itemid="+b+"]").attr("data-value");e.append(a('
"))}),f.use("moodle-core-notification-dialogue",function(){var e=new M.core.dialogue({draggable:!0,modal:!0,closeButton:!0,headerContent:b[0],bodyContent:d.html()});e.show(),a("#combinetags_form input[type=radio]").first().focus().prop("checked",!0),a("#combinetags_form #combinetags_cancel").on("click",function(){e.destroy()}),a("#combinetags_form").on("submit",function(){h.appendTo(c);var b=a("input[name=maintag]:checked","#combinetags_form").val();return a("").attr("name","maintag").attr("value",b).appendTo(c),c.submit(),!1})})})}),a("body").on("updatefailed","[data-inplaceeditable][data-itemtype=tagname]",function(b){var c=b.exception,f=b.newvalue,g=a(b.target).attr("data-itemid");"namesalreadybeeingused"===c.errorcode&&(b.preventDefault(),e.get_strings([{key:"nameuseddocombine",component:"tag"},{key:"yes"},{key:"cancel"}]).done(function(a){d.confirm(b.message,a[0],a[1],a[2],function(){window.location.href=window.location.href+"&newname="+encodeURIComponent(f)+"&tagid="+encodeURIComponent(g)+"&action=renamecombine&sesskey="+M.cfg.sesskey})}))}),a("body").on("click","a[data-action=addstandardtag]",function(b){b.preventDefault(),e.get_strings([{key:"addotags",component:"tag"},{key:"inputstandardtags",component:"tag"},{key:"continue"},{key:"cancel"}]).done(function(b){var c=a('

');c.find("#addtags_form").attr("action",window.location.href),c.find("#addtags_submit").attr("value",b[2]),c.find("#addtags_cancel").attr("value",b[3]),f.use("moodle-core-notification-dialogue",function(){var d=new M.core.dialogue({draggable:!0,modal:!0,closeButton:!0,headerContent:b[0],bodyContent:c.html()});d.show(),a("#addtags_form input[type=text]").focus(),a("#addtags_form #addtags_cancel").on("click",function(){d.destroy()})})})})},initManageCollectionsPage:function(){a("body").on("updated","[data-inplaceeditable]",function(b){var c,d,e,f=b.ajaxreturn;"core_tag"===f.component&&"tagareaenable"===f.itemtype&&(c=a(this).attr("data-itemid"),a(".tag-collections-table ul[data-collectionid] li[data-areaid="+c+"]").hide(),e=f.value,"1"===e?(a(this).closest("tr").removeClass("dimmed_text"),d=a(this).closest("tr").find('[data-itemtype="tagareacollection"]').attr("data-value"),a(".tag-collections-table ul[data-collectionid="+d+"] li[data-areaid="+c+"]").show()):a(this).closest("tr").addClass("dimmed_text")),"core_tag"===f.component&&"tagareacollection"===f.itemtype&&(c=a(this).attr("data-itemid"),a(".tag-collections-table ul[data-collectionid] li[data-areaid="+c+"]").hide(),d=a(this).attr("data-value"),e=a(this).closest("tr").find('[data-itemtype="tagareaenable"]').attr("data-value"),"1"===e&&a(".tag-collections-table ul[data-collectionid="+d+"] li[data-areaid="+c+"]").show())}),a("body").on("click",".addtagcoll > a",function(b){b.preventDefault();var c=a(this).attr("data-url")+"&sesskey="+M.cfg.sesskey;e.get_strings([{key:"addtagcoll",component:"tag"},{key:"name"},{key:"searchable",component:"tag"},{key:"create"},{key:"cancel"}]).done(function(b){var d=a('

:

:

');d.find('label[for="addtagcoll_name"]').html(b[1]),d.find('label[for="addtagcoll_searchable"]').html(b[2]),d.find("#addtagcoll_submit").attr("value",b[3]),d.find("#addtagcoll_cancel").attr("value",b[4]),f.use("moodle-core-notification-dialogue",function(){var e=new M.core.dialogue({draggable:!0,modal:!0,closeButton:!0,headerContent:b[0],bodyContent:d.html()});e.show(),a("#addtagcoll_form #addtagcoll_name").focus(),a("#addtagcoll_form #addtagcoll_cancel").on("click",function(){e.destroy()}),a("#addtagcoll_form").on("submit",function(){var b=a("#addtagcoll_form #addtagcoll_name").val(),d=a("#addtagcoll_form #addtagcoll_searchable").prop("checked")?1:0;return String(b).length>0&&(window.location.href=c+"&name="+encodeURIComponent(b)+"&searchable="+d),!1})})})}),a("body").on("click",".tag-collections-table .action_delete",function(b){b.preventDefault();var c=a(this).attr("data-url")+"&sesskey="+M.cfg.sesskey;e.get_strings([{key:"delete"},{key:"suredeletecoll",component:"tag",param:a(this).attr("data-collname")},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],function(){window.location.href=c})})})}}}); \ No newline at end of file diff --git a/lib/amd/src/tag.js b/lib/amd/src/tag.js index 8e546f5859c0..1f1d3fba2f2a 100644 --- a/lib/amd/src/tag.js +++ b/lib/amd/src/tag.js @@ -67,6 +67,11 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' // Set cell 'time modified' to 'now' when any of the element is updated in this row. $('body').on('updated', '[data-inplaceeditable]', function(e) { + str.get_string('selecttag', 'core_tag', e.ajaxreturn.value) + .then(function(s) { + return $('label[for="tagselect' + e.ajaxreturn.itemid + '"]').html(s); + }) + .fail(notification.exception); str.get_string('now').done(function(s) { $(e.target).closest('tr').find('td.col-timemodified').html(s); }); From 841b8c5e34daa8effd4f2b8026ad856f91675817 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Mon, 21 May 2018 10:13:41 +0800 Subject: [PATCH 27/35] MDL-62516 mod_forum: Only delete files for requested user --- mod/forum/classes/privacy/provider.php | 10 ++-- mod/forum/tests/privacy_provider_test.php | 62 +++++++++++++++++------ 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/mod/forum/classes/privacy/provider.php b/mod/forum/classes/privacy/provider.php index bf4b5e3bc7fd..c26c307ae929 100644 --- a/mod/forum/classes/privacy/provider.php +++ b/mod/forum/classes/privacy/provider.php @@ -795,6 +795,7 @@ public static function delete_data_for_all_users_in_context(\context $context) { // Delete all files from the posts. $fs = get_file_storage(); $fs->delete_area_files($context->id, 'mod_forum', 'post'); + $fs->delete_area_files($context->id, 'mod_forum', 'attachment'); // Delete all ratings in the context. \core_rating\privacy\provider::delete_ratings($context, 'mod_forum', 'post'); @@ -879,13 +880,14 @@ public static function delete_data_for_user(approved_contextlist $contextlist) { // Delete all Tags. \core_tag\privacy\provider::delete_item_tags_select($context, 'mod_forum', 'forum_posts', "IN ($postidsql)", $postparams); + + // Delete all files from the posts. + $fs = get_file_storage(); + $fs->delete_area_files_select($context->id, 'mod_forum', 'post', "IN ($postidsql)", $postparams); + $fs->delete_area_files_select($context->id, 'mod_forum', 'attachment', "IN ($postidsql)", $postparams); } $uniquediscussions->close(); - - // Delete all files from the posts. - $fs = get_file_storage(); - $fs->delete_area_files($context->id, 'mod_forum', 'post'); } } } diff --git a/mod/forum/tests/privacy_provider_test.php b/mod/forum/tests/privacy_provider_test.php index fc6ac263ae45..b5cbba3a4d85 100644 --- a/mod/forum/tests/privacy_provider_test.php +++ b/mod/forum/tests/privacy_provider_test.php @@ -887,6 +887,15 @@ public function test_all_users_deleted_from_context() { 'filepath' => '/', 'filename' => 'example.jpg', ], 'image contents (not really)'); + // And an attachment. + $fs->create_file_from_string([ + 'contextid' => $context->id, + 'component' => 'mod_forum', + 'filearea' => 'attachment', + 'itemid' => $post->id, + 'filepath' => '/', + 'filename' => 'example.jpg', + ], 'image contents (not really)'); } } @@ -962,6 +971,7 @@ public function test_all_users_deleted_from_context() { // All uploaded files relating to this context should have been deleted (post content). foreach ($postsinforum as $post) { $this->assertEmpty($fs->get_area_files($context->id, 'mod_forum', 'post', $post->id)); + $this->assertEmpty($fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id)); } // All ratings should have been deleted. @@ -1091,6 +1101,15 @@ public function test_delete_data_for_user() { 'filepath' => '/', 'filename' => 'example.jpg', ], 'image contents (not really)'); + // And a fake attachment. + $fs->create_file_from_string([ + 'contextid' => $context->id, + 'component' => 'mod_forum', + 'filearea' => 'attachment', + 'itemid' => $post->id, + 'filepath' => '/', + 'filename' => 'example.jpg', + ], 'image contents (not really)'); } } @@ -1137,12 +1156,26 @@ public function test_delete_data_for_user() { // Delete for one of the forums for the first user. $firstcontext = reset($contexts); - list($postinsql, $postinparams) = $DB->get_in_or_equal( - array_keys($postsbyforum[$user1->id][$firstcontext->id]), SQL_PARAMS_NAMED); - $othercontext = next($contexts); - list($otherpostinsql, $otherpostinparams) = $DB->get_in_or_equal( - array_keys($postsbyforum[$user1->id][$othercontext->id]), SQL_PARAMS_NAMED); + $deletedpostids = []; + $otherpostids = []; + foreach ($postsbyforum as $user => $contexts) { + foreach ($contexts as $thiscontextid => $theseposts) { + $thesepostids = array_map(function($post) { + return $post->id; + }, $theseposts); + + if ($user == $user1->id && $thiscontextid == $firstcontext->id) { + // This post is in the deleted context and by the target user. + $deletedpostids = array_merge($deletedpostids, $thesepostids); + } else { + // This post is by another user, or in a non-target context. + $otherpostids = array_merge($otherpostids, $thesepostids); + } + } + } + list($postinsql, $postinparams) = $DB->get_in_or_equal($deletedpostids, SQL_PARAMS_NAMED); + list($otherpostinsql, $otherpostinparams) = $DB->get_in_or_equal($otherpostids, SQL_PARAMS_NAMED); $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( \core_user::get_user($user1->id), @@ -1195,28 +1228,25 @@ public function test_delete_data_for_user() { // Ratings should have been removed from the affected posts. $this->assertCount(0, $DB->get_records_select('rating', "itemid {$postinsql}", $postinparams)); - // Ratings should remain on posts in the other context. - $this->assertCount(16, $DB->get_records_select('rating', "itemid {$otherpostinsql}", $otherpostinparams)); + // Ratings should remain on posts in the other context, and posts not belonging to the affected user. + $this->assertCount(144, $DB->get_records_select('rating', "itemid {$otherpostinsql}", $otherpostinparams)); // Ratings should remain where the user has rated another person's post. $this->assertCount(32, $DB->get_records('rating', ['userid' => $user1->id])); // Tags for the affected posts should be removed. - $this->assertCount(8, $DB->get_records_select('tag_instance', "itemid {$otherpostinsql}", $otherpostinparams)); - - // Tags should remain for the other posts by this user. $this->assertCount(0, $DB->get_records_select('tag_instance', "itemid {$postinsql}", $postinparams)); - // Tags should remain for others. - // Original total: 5 users * 2 forums * 4 posts * 2 tags - // Deleted posts: 8 - // New total: 72. - $this->assertCount(72, $DB->get_records('tag_instance')); + // Tags should remain for the other posts by this user, and all posts by other users. + $this->assertCount(72, $DB->get_records_select('tag_instance', "itemid {$otherpostinsql}", $otherpostinparams)); // Files for the affected posts should be removed. + // 5 users * 2 forums * 1 file in each forum + // Original total: 10 + // One post with file removed. $this->assertCount(0, $DB->get_records_select('files', "itemid {$postinsql}", $postinparams)); // Files for the other posts should remain. - $this->assertCount(2, $DB->get_records_select('files', "itemid {$otherpostinsql}", $otherpostinparams)); + $this->assertCount(18, $DB->get_records_select('files', "filename <> '.' AND itemid {$otherpostinsql}", $otherpostinparams)); } } From eeb84ca38157f5bac4444912637f7bace963b543 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Mon, 9 Apr 2018 14:51:31 +0800 Subject: [PATCH 28/35] MDL-61015 course: consistently display hidden sections When section is hidden but the course setting says to show hidden sections display the hidden sections consistently with how we display the sections with access restriction. The only difference is that we didn't previously display the summary of the hidden section in this case, the new logic repeats this behavior --- course/format/renderer.php | 51 +++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/course/format/renderer.php b/course/format/renderer.php index b630ad8bef40..4d065217ce2a 100644 --- a/course/format/renderer.php +++ b/course/format/renderer.php @@ -231,7 +231,12 @@ protected function section_header($section, $course, $onsectionpage, $sectionret $o .= $this->section_availability($section); $o .= html_writer::start_tag('div', array('class' => 'summary')); - $o .= $this->format_summary_text($section); + if ($section->uservisible || $section->visible) { + // Show summary if section is available or has availability restriction information. + // Do not show summary if section is hidden but we still display it because of course setting + // "Hidden sections are shown in collapsed form". + $o .= $this->format_summary_text($section); + } $o .= html_writer::end_tag('div'); return $o; @@ -444,7 +449,12 @@ protected function section_summary($section, $course, $mods) { $o .= $this->output->heading($title, 3, 'section-title'); $o.= html_writer::start_tag('div', array('class' => 'summarytext')); - $o.= $this->format_summary_text($section); + if ($section->uservisible || $section->visible) { + // Show summary if section is available or has availability restriction information. + // Do not show summary if section is hidden but we still display it because of course setting + // "Hidden sections are shown in collapsed form". + $o .= $this->format_summary_text($section); + } $o.= html_writer::end_tag('div'); $o.= $this->section_activity_summary($section, $course, null); @@ -552,6 +562,10 @@ protected function section_availability_message($section, $canviewhidden) { if (!$section->visible) { if ($canviewhidden) { $o .= $this->courserenderer->availability_info(get_string('hiddenfromstudents'), 'ishidden'); + } else { + // We are here because of the setting "Hidden sections are shown in collapsed form". + // Student can not see the section contents but can see its name. + $o .= $this->courserenderer->availability_info(get_string('notavailable'), 'ishidden'); } } else if (!$section->uservisible) { if ($section->availableinfo) { @@ -769,20 +783,11 @@ public function print_single_section_page($course, $sections, $mods, $modnames, $course = course_get_format($course)->get_course(); // Can we view the section in question? - if (!($sectioninfo = $modinfo->get_section_info($displaysection))) { - // This section doesn't exist - print_error('unknowncoursesection', 'error', null, $course->fullname); - return; - } - - if (!$sectioninfo->uservisible) { - if (!$course->hiddensections) { - echo $this->start_section_list(); - echo $this->section_hidden($displaysection, $course->id); - echo $this->end_section_list(); - } - // Can't view this section. - return; + if (!($sectioninfo = $modinfo->get_section_info($displaysection)) || !$sectioninfo->uservisible) { + // This section doesn't exist or is not available for the user. + // We actually already check this in course/view.php but just in case exit from this function as well. + print_error('unknowncoursesection', 'error', course_get_url($course), + format_string($course->fullname)); } // Copy activity clipboard.. @@ -891,18 +896,12 @@ public function print_multiple_section_page($course, $sections, $mods, $modnames continue; } // Show the section if the user is permitted to access it, OR if it's not available - // but there is some available info text which explains the reason & should display. + // but there is some available info text which explains the reason & should display, + // OR it is hidden but the course has a setting to display hidden sections as unavilable. $showsection = $thissection->uservisible || - ($thissection->visible && !$thissection->available && - !empty($thissection->availableinfo)); + ($thissection->visible && !$thissection->available && !empty($thissection->availableinfo)) || + (!$thissection->visible && !$course->hiddensections); if (!$showsection) { - // If the hiddensections option is set to 'show hidden sections in collapsed - // form', then display the hidden section message - UNLESS the section is - // hidden by the availability system, which is set to hide the reason. - if (!$course->hiddensections && $thissection->available) { - echo $this->section_hidden($section, $course->id); - } - continue; } From 32211803c301af42b9ed7c0dca3273b7e74751a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luca=20B=C3=B6sch?= Date: Tue, 1 May 2018 21:43:11 +0200 Subject: [PATCH 29/35] MDL-61015 course: consistently display hidden sections This commit moves the availability info on top. --- course/format/renderer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/course/format/renderer.php b/course/format/renderer.php index 4d065217ce2a..b12e4c6cbc18 100644 --- a/course/format/renderer.php +++ b/course/format/renderer.php @@ -448,7 +448,9 @@ protected function section_summary($section, $course, $mods) { } $o .= $this->output->heading($title, 3, 'section-title'); + $o .= $this->section_availability($section); $o.= html_writer::start_tag('div', array('class' => 'summarytext')); + if ($section->uservisible || $section->visible) { // Show summary if section is available or has availability restriction information. // Do not show summary if section is hidden but we still display it because of course setting @@ -458,8 +460,6 @@ protected function section_summary($section, $course, $mods) { $o.= html_writer::end_tag('div'); $o.= $this->section_activity_summary($section, $course, null); - $o .= $this->section_availability($section); - $o .= html_writer::end_tag('div'); $o .= html_writer::end_tag('li'); From e306188732d8596f28ccd5d15af8d16cf95defbe Mon Sep 17 00:00:00 2001 From: John Beedell Date: Mon, 14 May 2018 12:37:23 +0100 Subject: [PATCH 30/35] MDL-62239 QTYPE: iOS 11.3 broke Moodle drag-drop question types --- .../moodle-qtype_ddimageortext-dd-debug.js | 48 ++++++++++-------- .../moodle-qtype_ddimageortext-dd-min.js | 4 +- .../moodle-qtype_ddimageortext-dd.js | 48 ++++++++++-------- .../yui/src/ddimageortext/js/ddimageortext.js | 48 ++++++++++-------- .../moodle-qtype_ddmarker-dd-debug.js | 47 ++++++++++-------- .../moodle-qtype_ddmarker-dd-min.js | 4 +- .../moodle-qtype_ddmarker-dd.js | 47 ++++++++++-------- .../ddmarker/yui/src/ddmarker/js/ddmarker.js | 47 ++++++++++-------- .../moodle-qtype_ddwtos-dd-debug.js | 48 ++++++++++-------- .../moodle-qtype_ddwtos-dd-min.js | 4 +- .../moodle-qtype_ddwtos-dd.js | 48 ++++++++++-------- .../type/ddwtos/yui/src/ddwtos/js/ddwtos.js | 49 +++++++++++-------- 12 files changed, 255 insertions(+), 187 deletions(-) diff --git a/question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd-debug.js b/question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd-debug.js index 1048e63a829a..022e28e35caf 100644 --- a/question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd-debug.js +++ b/question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd-debug.js @@ -249,7 +249,7 @@ var DDIMAGEORTEXT_QUESTION = function() { * This is the code for question rendering. */ Y.extend(DDIMAGEORTEXT_QUESTION, M.qtype_ddimageortext.dd_base_class, { - touchscrolldisable: null, + passiveSupported: false, pendingid: '', initializer: function() { this.pendingid = 'qtype_ddimageortext-' + Math.random().toString(36).slice(2); // Random string. @@ -267,6 +267,7 @@ Y.extend(DDIMAGEORTEXT_QUESTION, M.qtype_ddimageortext.dd_base_class, { this.reposition_drags_for_question(); }, this); } + this.checkPassiveSupported(); }, /** @@ -277,29 +278,36 @@ Y.extend(DDIMAGEORTEXT_QUESTION, M.qtype_ddimageortext.dd_base_class, { * draggable items. */ prevent_touchmove_from_scrolling: function(drag) { - var touchstart = (Y.UA.ie) ? 'MSPointerStart' : 'touchstart'; - var touchend = (Y.UA.ie) ? 'MSPointerEnd' : 'touchend'; var touchmove = (Y.UA.ie) ? 'MSPointerMove' : 'touchmove'; + var eventHandler = function(event) { + event.preventDefault(); + }; + var dragId = drag.get('id'); + var el = document.getElementById(dragId); + // Note do not dynamically add events within another event, as this causes issues on iOS11.3. + // See https://github.com/atlassian/react-beautiful-dnd/issues/413 and + // https://bugs.webkit.org/show_bug.cgi?id=184250 for fuller explanation. + el.addEventListener(touchmove, eventHandler, this.passiveSupported ? {passive: false, capture: true} : false); + }, - // Disable scrolling when touching the draggable items. - drag.on(touchstart, function() { - if (this.touchscrolldisable) { - return; // Already disabled. - } - this.touchscrolldisable = Y.one('body').on(touchmove, function(e) { - e = e || window.event; - e.preventDefault(); + /** + * Some older browsers do not support passing an options object to addEventListener. + * This is a check from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener. + */ + checkPassiveSupported: function() { + try { + var options = Object.defineProperty({}, 'passive', { + get: function() { + this.passiveSupported = true; + }.bind(this) }); - }, this); - - // Allow scrolling after releasing the draggable items. - drag.on(touchend, function() { - if (this.touchscrolldisable) { - this.touchscrolldisable.detach(); - this.touchscrolldisable = null; - } - }, this); + window.addEventListener('test', options, options); + window.removeEventListener('test', options, options); + } catch (err) { + this.passiveSupported = false; + } }, + create_all_drag_and_drops: function() { this.init_drops(); this.update_padding_sizes_all(); diff --git a/question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd-min.js b/question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd-min.js index 7ebee06fc2c0..f8ab7a6a59ae 100644 --- a/question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd-min.js +++ b/question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd-min.js @@ -1,2 +1,2 @@ -YUI.add("moodle-qtype_ddimageortext-dd",function(e,t){var n="ddimageortext_dd",r=function(){r.superclass.constructor.apply(this,arguments)};e.extend(r,e.Base,{doc:null,polltimer:null,afterimageloaddone:!1,poll_for_image_load:function(t,n,r,i){if(this.afterimageloaddone)return;var s=this.doc.bg_img().get("complete");n&&(s=s&&this.doc.bg_img().hasClass("constrained"));var o=!this.doc.drag_item_homes().some(function(e){if(e.get("tagName")!=="IMG")return!1;var t=e.get("complete");return n&&(t=t&&e.hasClass("constrained")),!t});if(s&&o)this.polltimer!==null&&(this.polltimer.cancel(),this.polltimer=null),this.doc.drag_item_homes().detach("load",this.poll_for_image_load),this.doc.bg_img().detach("load",this.poll_for_image_load),r!==0?e.later(r,this,i):i.call(this),this.afterimageloaddone=!0;else if(this.polltimer===null){var u=[null,n,r,i];this.polltimer=e.later(1e3,this,this.poll_for_image_load,u,!0)}},doc_structure:function(t){var n=e.one(this.get("topnode")),r=n.one("div.dragitems"),i=n.one("div.droparea");return{top_node:function(){return n},drag_items:function(){return r.all(".drag")},drop_zones:function(){return n.all("div.dropzones div.dropzone")},drop_zone_group:function(e){return n.all("div.dropzones div.group"+e)},drag_items_cloned_from:function(e){return r.all(".dragitems"+e)},drag_item:function(e){return r.one(".draginstance"+e)},drag_items_in_group:function(e){return r.all(".drag.group"+e)},drag_item_homes:function(){return r.all(".draghome")},bg_img:function(){return n.one(".dropbackground")},load_bg_img:function(e){i.setContent(''),this.bg_img().on("load",this.on_image_load,this,"bg_image")},add_or_update_drag_item_home:function(e,t,n,i){var s=this.drag_item_home(e),o="draghome dragitemhomes"+e+" group"+i,u=''+n+'',a='
'+n+"
";s===null?t?r.append(u):n!==""&&r.append(a):(t?r.insert(u,s):n!==""&&r.insert(a,s),s.remove(!0));var f=r.one(".dragitemhomes"+e);f!==null&&(f.setData("groupno",i),f.setData("dragitemno",e))},drag_item_home:function(e){return r.one(".dragitemhomes"+e)},get_classname_numeric_suffix:function(e,t){var n=e.getAttribute("class");if(n!==""){var r=n.split(" ");for(var i=0;i0)o=this.doc.clone_new_drag_item -(e,n),e++,this.get("readonly")||(this.doc.draggable_for_question(o,i,r),this.prevent_touchmove_from_scrolling(o)),u--}},this),this.reposition_drags_for_question(),this.get("readonly")||(this.doc.drop_zones().set("tabIndex",0),this.doc.drop_zones().each(function(e){e.on("dragchange",this.drop_zone_key_press,this)},this)),M.util.js_complete(this.pendingid)},drop_zone_key_press:function(e){switch(e.direction){case"next":this.place_next_drag_in(e.target);break;case"previous":this.place_previous_drag_in(e.target);break;case"remove":this.remove_drag_from_drop(e.target)}e.preventDefault(),this.reposition_drags_for_question()},place_next_drag_in:function(e){this.search_for_unplaced_drop_choice(e,1)},place_previous_drag_in:function(e){this.search_for_unplaced_drop_choice(e,-1)},search_for_unplaced_drop_choice:function(e,t){var n,r=this.current_drag_in_drop(e);if(""===r)if(t===1)n=1;else{n=1;var i=e.getData("group");this.doc.drag_items_in_group(i).each(function(e){n=Math.max(n,e.getData("choice"))},this)}else n=+r+t;var s;do{if(this.get_choices_for_drop(n,e).size()===0){this.remove_drag_from_drop(e);return}s=this.get_unplaced_choice_for_drop(n,e),n+=t}while(s===null);this.place_drag_in_drop(s,e)},current_drag_in_drop:function(t){var n=t.getData("inputid"),r=e.one("input#"+n);return r.get("value")},remove_drag_from_drop:function(e){this.place_drag_in_drop(null,e)},place_drag_in_drop:function(t,n){var r=n.getData("inputid"),i=e.one("input#"+r);t!==null?i.set("value",t.getData("choice")):i.set("value","")},reposition_drags_for_question:function(t){this.doc.drag_items().removeClass("placed"),this.doc.drag_items().each(function(e){e.dd!==undefined&&e.dd.detachAll("drag:start")},this),this.doc.drop_zones().each(function(e){var t=e.getData("xy");e.setXY(this.convert_to_window_xy(t));var n="input#"+e.getData("inputid"),r=this.doc.top_node().one(n),i=r.get("value");if(i!==""){var s=this.get_unplaced_choice_for_drop(i,e);s!==null&&(s.setXY(e.getXY()),s.addClass("placed"),s.dd!==undefined&&s.dd.once("drag:start",function(e,t){t.set("value",""),e.target.get("node").removeClass("placed")},this,r))}},this),this.doc.drag_items().each(function(e){if(!e.hasClass("placed")&&!e.hasClass("yui3-dd-dragging")){var t=this.doc.drag_item_home(e.getData("dragitemno"));e.setXY(t.getXY())}},this),t&&e.later(500,this,this.reposition_drags_for_question,!0)},get_choices_for_drop:function(e,t){var n=t.getData("group");return this.doc.top_node().all("div.dragitemgroup"+n+" .choice"+e+".drag")},get_unplaced_choice_for_drop:function(e,t){var n=this.get_choices_for_drop(e,t),r=null;return n.some(function(e){return this.get("readonly")||!e.hasClass("placed")&&!e.hasClass("yui3-dd-dragging")?(r=e,!0):!1}),r},init_drops:function(){var t=this.doc.top_node().one("div.dropzones"),n={};for(var r=1;r<=8;r++){var i=e.Node.create('
');t.append(i),n[r]=i}var s=function(e){var t=e.drag.get("node"),n=e.drop.get("node");Number(n.getData("group"))===t.getData("group")&&this.place_drag_in_drop(t,n)};for(var o in this.get("drops")){var u=this.get("drops")[o],a="dropzone group"+u.group+" place"+o,f=u.text.replace('"','"');f||(f=M.util.get_string("blank","qtype_ddimageortext"));var l='
'+''+f+" 
",c=e.Node.create(l);n[u.group].append(c),c.setStyles({opacity:.5}),c.setData("xy",u.xy),c.setData("place",o),c.setData("inputid",u.fieldname.replace(":","_")),c.setData("group",u.group);var h=new e.DD.Drop({node:c,groups:[u.group]});h.on("drop:hit",s,this)}}},{NAME:i,ATTRS:{}}),e.Event.define("dragchange",{_event:e.UA.webkit||e.UA.ie?"keydown":"keypress",_keys:{32:"next",37:"previous",38:"previous",39:"next",40:"next",27:"remove"},_keyHandler:function(e,t){this._keys[e.keyCode]&&(e.direction=this._keys[e.keyCode],t.fire(e))},on:function(e,t,n){t._detacher=e.on(this._event,this._keyHandler,this,n)}}),M.qtype_ddimageortext.init_question=function(e){return new s(e)}},"@VERSION@",{requires:["node","dd","dd-drop","dd-constrain"]}); +YUI.add("moodle-qtype_ddimageortext-dd",function(e,t){var n="ddimageortext_dd",r=function(){r.superclass.constructor.apply(this,arguments)};e.extend(r,e.Base,{doc:null,polltimer:null,afterimageloaddone:!1,poll_for_image_load:function(t,n,r,i){if(this.afterimageloaddone)return;var s=this.doc.bg_img().get("complete");n&&(s=s&&this.doc.bg_img().hasClass("constrained"));var o=!this.doc.drag_item_homes().some(function(e){if(e.get("tagName")!=="IMG")return!1;var t=e.get("complete");return n&&(t=t&&e.hasClass("constrained")),!t});if(s&&o)this.polltimer!==null&&(this.polltimer.cancel(),this.polltimer=null),this.doc.drag_item_homes().detach("load",this.poll_for_image_load),this.doc.bg_img().detach("load",this.poll_for_image_load),r!==0?e.later(r,this,i):i.call(this),this.afterimageloaddone=!0;else if(this.polltimer===null){var u=[null,n,r,i];this.polltimer=e.later(1e3,this,this.poll_for_image_load,u,!0)}},doc_structure:function(t){var n=e.one(this.get("topnode")),r=n.one("div.dragitems"),i=n.one("div.droparea");return{top_node:function(){return n},drag_items:function(){return r.all(".drag")},drop_zones:function(){return n.all("div.dropzones div.dropzone")},drop_zone_group:function(e){return n.all("div.dropzones div.group"+e)},drag_items_cloned_from:function(e){return r.all(".dragitems"+e)},drag_item:function(e){return r.one(".draginstance"+e)},drag_items_in_group:function(e){return r.all(".drag.group"+e)},drag_item_homes:function(){return r.all(".draghome")},bg_img:function(){return n.one(".dropbackground")},load_bg_img:function(e){i.setContent(''),this.bg_img().on("load",this.on_image_load,this,"bg_image")},add_or_update_drag_item_home:function(e,t,n,i){var s=this.drag_item_home(e),o="draghome dragitemhomes"+e+" group"+i,u=''+n+'',a='
'+n+"
";s===null?t?r.append(u):n!==""&&r.append(a):(t?r.insert(u,s):n!==""&&r.insert(a,s),s.remove(!0));var f=r.one(".dragitemhomes"+e);f!==null&&(f.setData("groupno",i),f.setData("dragitemno",e))},drag_item_home:function(e){return r.one(".dragitemhomes"+e)},get_classname_numeric_suffix:function(e,t){var n=e.getAttribute("class");if(n!==""){var r=n.split(" ");for(var i=0;i0)o=this.doc.clone_new_drag_item(e,n),e++,this.get("readonly")||(this.doc.draggable_for_question(o,i,r),this.prevent_touchmove_from_scrolling(o)),u--}},this),this.reposition_drags_for_question(),this.get("readonly")||(this.doc.drop_zones().set("tabIndex",0),this.doc.drop_zones().each(function(e){e.on("dragchange",this.drop_zone_key_press,this)},this)),M.util.js_complete(this.pendingid)},drop_zone_key_press:function(e){switch(e.direction){case"next":this.place_next_drag_in(e.target);break;case"previous":this.place_previous_drag_in(e.target);break;case"remove":this.remove_drag_from_drop(e.target)}e.preventDefault(),this.reposition_drags_for_question()},place_next_drag_in:function(e){this.search_for_unplaced_drop_choice(e,1)},place_previous_drag_in:function(e){this.search_for_unplaced_drop_choice(e,-1)},search_for_unplaced_drop_choice:function(e,t){var n,r=this.current_drag_in_drop(e);if(""===r)if(t===1)n=1;else{n=1;var i=e.getData("group");this.doc.drag_items_in_group(i).each(function(e){n=Math.max(n,e.getData("choice"))},this)}else n=+r+t;var s;do{if(this.get_choices_for_drop(n,e).size()===0){this.remove_drag_from_drop(e);return}s=this.get_unplaced_choice_for_drop(n,e),n+=t}while(s===null);this.place_drag_in_drop(s,e)},current_drag_in_drop:function(t){var n=t.getData("inputid"),r=e.one("input#"+n);return r.get("value")},remove_drag_from_drop:function(e){this.place_drag_in_drop(null,e)},place_drag_in_drop:function(t,n){var r=n.getData("inputid"),i=e.one("input#"+r);t!==null?i.set("value",t.getData("choice")):i.set("value","")},reposition_drags_for_question:function(t){this.doc.drag_items().removeClass("placed"),this.doc.drag_items().each(function(e){e.dd!==undefined&&e.dd.detachAll("drag:start")},this),this.doc.drop_zones().each(function(e){var t=e.getData("xy");e.setXY(this.convert_to_window_xy(t));var n="input#"+e.getData("inputid"),r=this.doc.top_node().one(n),i=r.get("value");if(i!==""){var s=this.get_unplaced_choice_for_drop(i,e);s!==null&&(s.setXY(e.getXY()),s.addClass("placed"),s.dd!==undefined&&s.dd.once("drag:start",function(e,t){t.set("value",""),e.target.get("node").removeClass("placed")},this,r))}},this),this.doc.drag_items().each(function(e){if(!e.hasClass("placed")&&!e.hasClass("yui3-dd-dragging")){var t=this.doc.drag_item_home(e.getData("dragitemno"));e.setXY(t.getXY())}},this),t&&e.later(500,this,this.reposition_drags_for_question,!0)},get_choices_for_drop:function(e,t){var n=t.getData("group");return this.doc.top_node().all("div.dragitemgroup"+n+" .choice"+e+".drag")},get_unplaced_choice_for_drop:function(e,t){var n=this.get_choices_for_drop(e,t),r=null;return n.some(function(e){return this.get("readonly")||!e.hasClass("placed")&&!e.hasClass("yui3-dd-dragging")?(r=e,!0):!1}),r},init_drops:function(){var t=this.doc.top_node().one("div.dropzones"),n={};for(var r=1;r<=8;r++){var i=e.Node.create('
');t.append(i),n[r]=i}var s=function(e){var t=e.drag.get("node"),n=e.drop.get("node");Number(n.getData("group"))===t.getData("group")&&this.place_drag_in_drop(t,n)};for(var o in this.get("drops")){var u=this.get("drops")[o],a="dropzone group"+u.group+" place"+o,f=u.text.replace('"','"');f||(f=M.util.get_string("blank","qtype_ddimageortext"));var l='
'+''+f+" 
",c=e.Node.create(l);n[u.group].append(c),c.setStyles({opacity:.5}),c.setData("xy",u.xy),c.setData("place",o),c.setData("inputid",u.fieldname.replace(":","_")),c.setData("group",u.group);var h=new e.DD.Drop({node:c,groups:[u.group]});h.on("drop:hit",s,this)}}},{NAME:i,ATTRS:{}}),e.Event.define("dragchange",{_event:e.UA.webkit||e.UA.ie?"keydown":"keypress",_keys:{32:"next",37:"previous",38:"previous",39:"next",40:"next",27:"remove"},_keyHandler:function(e,t){this._keys[e.keyCode]&&(e.direction=this._keys[e.keyCode],t.fire(e))},on:function(e,t,n){t._detacher=e.on(this._event,this._keyHandler,this,n)}}),M.qtype_ddimageortext.init_question=function(e){return new s(e)}},"@VERSION@",{requires:["node","dd","dd-drop","dd-constrain"]}); diff --git a/question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd.js b/question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd.js index 1048e63a829a..022e28e35caf 100644 --- a/question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd.js +++ b/question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd.js @@ -249,7 +249,7 @@ var DDIMAGEORTEXT_QUESTION = function() { * This is the code for question rendering. */ Y.extend(DDIMAGEORTEXT_QUESTION, M.qtype_ddimageortext.dd_base_class, { - touchscrolldisable: null, + passiveSupported: false, pendingid: '', initializer: function() { this.pendingid = 'qtype_ddimageortext-' + Math.random().toString(36).slice(2); // Random string. @@ -267,6 +267,7 @@ Y.extend(DDIMAGEORTEXT_QUESTION, M.qtype_ddimageortext.dd_base_class, { this.reposition_drags_for_question(); }, this); } + this.checkPassiveSupported(); }, /** @@ -277,29 +278,36 @@ Y.extend(DDIMAGEORTEXT_QUESTION, M.qtype_ddimageortext.dd_base_class, { * draggable items. */ prevent_touchmove_from_scrolling: function(drag) { - var touchstart = (Y.UA.ie) ? 'MSPointerStart' : 'touchstart'; - var touchend = (Y.UA.ie) ? 'MSPointerEnd' : 'touchend'; var touchmove = (Y.UA.ie) ? 'MSPointerMove' : 'touchmove'; + var eventHandler = function(event) { + event.preventDefault(); + }; + var dragId = drag.get('id'); + var el = document.getElementById(dragId); + // Note do not dynamically add events within another event, as this causes issues on iOS11.3. + // See https://github.com/atlassian/react-beautiful-dnd/issues/413 and + // https://bugs.webkit.org/show_bug.cgi?id=184250 for fuller explanation. + el.addEventListener(touchmove, eventHandler, this.passiveSupported ? {passive: false, capture: true} : false); + }, - // Disable scrolling when touching the draggable items. - drag.on(touchstart, function() { - if (this.touchscrolldisable) { - return; // Already disabled. - } - this.touchscrolldisable = Y.one('body').on(touchmove, function(e) { - e = e || window.event; - e.preventDefault(); + /** + * Some older browsers do not support passing an options object to addEventListener. + * This is a check from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener. + */ + checkPassiveSupported: function() { + try { + var options = Object.defineProperty({}, 'passive', { + get: function() { + this.passiveSupported = true; + }.bind(this) }); - }, this); - - // Allow scrolling after releasing the draggable items. - drag.on(touchend, function() { - if (this.touchscrolldisable) { - this.touchscrolldisable.detach(); - this.touchscrolldisable = null; - } - }, this); + window.addEventListener('test', options, options); + window.removeEventListener('test', options, options); + } catch (err) { + this.passiveSupported = false; + } }, + create_all_drag_and_drops: function() { this.init_drops(); this.update_padding_sizes_all(); diff --git a/question/type/ddimageortext/yui/src/ddimageortext/js/ddimageortext.js b/question/type/ddimageortext/yui/src/ddimageortext/js/ddimageortext.js index a16582926719..979efc6af197 100644 --- a/question/type/ddimageortext/yui/src/ddimageortext/js/ddimageortext.js +++ b/question/type/ddimageortext/yui/src/ddimageortext/js/ddimageortext.js @@ -247,7 +247,7 @@ var DDIMAGEORTEXT_QUESTION = function() { * This is the code for question rendering. */ Y.extend(DDIMAGEORTEXT_QUESTION, M.qtype_ddimageortext.dd_base_class, { - touchscrolldisable: null, + passiveSupported: false, pendingid: '', initializer: function() { this.pendingid = 'qtype_ddimageortext-' + Math.random().toString(36).slice(2); // Random string. @@ -265,6 +265,7 @@ Y.extend(DDIMAGEORTEXT_QUESTION, M.qtype_ddimageortext.dd_base_class, { this.reposition_drags_for_question(); }, this); } + this.checkPassiveSupported(); }, /** @@ -275,29 +276,36 @@ Y.extend(DDIMAGEORTEXT_QUESTION, M.qtype_ddimageortext.dd_base_class, { * draggable items. */ prevent_touchmove_from_scrolling: function(drag) { - var touchstart = (Y.UA.ie) ? 'MSPointerStart' : 'touchstart'; - var touchend = (Y.UA.ie) ? 'MSPointerEnd' : 'touchend'; var touchmove = (Y.UA.ie) ? 'MSPointerMove' : 'touchmove'; + var eventHandler = function(event) { + event.preventDefault(); + }; + var dragId = drag.get('id'); + var el = document.getElementById(dragId); + // Note do not dynamically add events within another event, as this causes issues on iOS11.3. + // See https://github.com/atlassian/react-beautiful-dnd/issues/413 and + // https://bugs.webkit.org/show_bug.cgi?id=184250 for fuller explanation. + el.addEventListener(touchmove, eventHandler, this.passiveSupported ? {passive: false, capture: true} : false); + }, - // Disable scrolling when touching the draggable items. - drag.on(touchstart, function() { - if (this.touchscrolldisable) { - return; // Already disabled. - } - this.touchscrolldisable = Y.one('body').on(touchmove, function(e) { - e = e || window.event; - e.preventDefault(); + /** + * Some older browsers do not support passing an options object to addEventListener. + * This is a check from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener. + */ + checkPassiveSupported: function() { + try { + var options = Object.defineProperty({}, 'passive', { + get: function() { + this.passiveSupported = true; + }.bind(this) }); - }, this); - - // Allow scrolling after releasing the draggable items. - drag.on(touchend, function() { - if (this.touchscrolldisable) { - this.touchscrolldisable.detach(); - this.touchscrolldisable = null; - } - }, this); + window.addEventListener('test', options, options); + window.removeEventListener('test', options, options); + } catch (err) { + this.passiveSupported = false; + } }, + create_all_drag_and_drops: function() { this.init_drops(); this.update_padding_sizes_all(); diff --git a/question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd-debug.js b/question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd-debug.js index cd19469b453c..c58108dd7b35 100644 --- a/question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd-debug.js +++ b/question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd-debug.js @@ -310,7 +310,7 @@ var DDMARKER_QUESTION = function() { * This is the code for question rendering. */ Y.extend(DDMARKER_QUESTION, M.qtype_ddmarker.dd_base_class, { - touchscrolldisable: null, + passiveSupported: false, pendingid: '', initializer: function() { this.pendingid = 'qtype_ddmarker-' + Math.random().toString(36).slice(2); // Random string. @@ -319,6 +319,7 @@ Y.extend(DDMARKER_QUESTION, M.qtype_ddmarker.dd_base_class, { this.poll_for_image_load(null, false, 0, this.after_image_load); this.doc.bg_img().after('load', this.poll_for_image_load, this, false, 0, this.after_image_load); + this.checkPassiveSupported(); }, after_image_load: function() { this.redraw_drags_and_drops(); @@ -346,28 +347,34 @@ Y.extend(DDMARKER_QUESTION, M.qtype_ddmarker.dd_base_class, { * draggable items. */ prevent_touchmove_from_scrolling: function(drag) { - var touchstart = (Y.UA.ie) ? 'MSPointerStart' : 'touchstart'; - var touchend = (Y.UA.ie) ? 'MSPointerEnd' : 'touchend'; var touchmove = (Y.UA.ie) ? 'MSPointerMove' : 'touchmove'; + var eventHandler = function(event) { + event.preventDefault(); + }; + var dragId = drag.get('id'); + var el = document.getElementById(dragId); + // Note do not dynamically add events within another event, as this causes issues on iOS11.3. + // See https://github.com/atlassian/react-beautiful-dnd/issues/413 and + // https://bugs.webkit.org/show_bug.cgi?id=184250 for fuller explanation. + el.addEventListener(touchmove, eventHandler, this.passiveSupported ? {passive: false, capture: true} : false); + }, - // Disable scrolling when touching the draggable items. - drag.on(touchstart, function() { - if (this.touchscrolldisable) { - return; // Already disabled. - } - this.touchscrolldisable = Y.one('body').on(touchmove, function(e) { - e = e || window.event; - e.preventDefault(); + /** + * Some older browsers do not support passing an options object to addEventListener. + * This is a check from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener. + */ + checkPassiveSupported: function() { + try { + var options = Object.defineProperty({}, 'passive', { + get: function() { + this.passiveSupported = true; + }.bind(this) }); - }, this); - - // Allow scrolling after releasing the draggable items. - drag.on(touchend, function() { - if (this.touchscrolldisable) { - this.touchscrolldisable.detach(); - this.touchscrolldisable = null; - } - }, this); + window.addEventListener('test', options, options); + window.removeEventListener('test', options, options); + } catch (err) { + this.passiveSupported = false; + } }, draggable: function(drag) { diff --git a/question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd-min.js b/question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd-min.js index c15de87a2017..ca407f1d17a1 100644 --- a/question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd-min.js +++ b/question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd-min.js @@ -1,2 +1,2 @@ -YUI.add("moodle-qtype_ddmarker-dd",function(e,t){var n="moodle-qtype_ddmarker-dd",r=function(){r.superclass.constructor.apply(this,arguments)};e.extend(r,e.Base,{doc:null,polltimer:null,afterimageloaddone:!1,graphics:null,poll_for_image_load:function(t,n,r,i){if(this.afterimageloaddone)return;var s=this.doc.bg_img().get("complete");n&&(s=s&&this.doc.bg_img().hasClass("constrained"));if(s)this.polltimer!==null&&(this.polltimer.cancel(),this.polltimer=null),this.doc.bg_img().detach("load",this.poll_for_image_load),r!==0?e.later(r,this,i):i.call(this),this.afterimageloaddone=!0;else if(this.polltimer===null){var o=[null,n,r,i];this.polltimer=e.later(1e3,this,this.poll_for_image_load,o,!0)}},doc_structure:function(){var t=e.one(this.get("topnode")),n=t.one("div.dragitems"),r=t.one("div.droparea");return{top_node:function(){return t},bg_img:function(){return t.one(".dropbackground")},load_bg_img:function(e){r.setContent(''),this.bg_img().on("load",this.on_image_load,this,"bg_image")},drag_items:function(){return n.all(".dragitem")},drag_items_for_choice:function(e){return n.all("span.dragitem.choice"+e)},drag_item_for_choice:function(e,t){return n.one("span.dragitem.choice"+e+".item"+t)},drag_item_being_dragged:function(e){return n.one("span.dragitem.beingdragged.choice"+e)},drag_item_home:function(e){return n.one("span.draghome.choice"+e)},drag_item_homes:function(){return n.all("span.draghome")},get_classname_numeric_suffix:function(e,t){var n=e.getAttribute("class");if(n!==""){var r=n.split(" ");for(var i=0;i'+t+""):this.doc.marker_texts().append(''+t+"")}var a="draw_shape_"+n;if(this[a]instanceof Function){var f=this[a](e,r,i);if(f!==null){var l=this.doc.top_node().one("div.ddarea div.markertexts span.markertext"+e);if(l!==null){l.setStyle("opacity","0.6"),f[0]-=l.get("offsetWidth")/2,f[1]-=l.get("offsetHeight")/2,l.setXY(this.convert_to_window_xy(f));var c=l.one("a");c!==null&&(c.once("click",function(e,t){var n=this.shapes[t].get("fill");n.opacity=1,this.shapes[t].set("fill",n)},this,e),c.set("tabIndex",0))}}}},draw_shape_circle:function(e,t,n){var r=t.match(/(\d+),(\d+);(\d+)/);if(r&&r.length===4){var i=[Number(r[1])-r[3],Number(r[2])-r[3]];if(this.coords_in_img(i)){var s=[Number(r[3])*2,Number(r[3])*2],o=this.graphics.addShape({type:"circle",width:s[0],height:s[1],fill:{color:n,opacity:"0.5"},stroke:{weight:1,color:"black"}});return o.setXY(this.convert_to_window_xy(i)),this.shapes[e]=o,[Number(r[1]),Number(r[2])]}}return null},draw_shape_rectangle:function(e,t,n){var r=t.match(/(\d+),(\d+);(\d+),(\d+)/);if(r&&r.length===5){var i=[Number(r[1]),Number(r[2])],s=[Number(r[3]),Number(r[4])];if(this.coords_in_img([i[0]+s[0],i[1]+s[1]])){var o=this.graphics.addShape({type:"rect",width:s[0],height:s[1],fill:{color:n,opacity:"0.5"},stroke:{weight:1,color:"black"}});return o.setXY(this.convert_to_window_xy(i)),this.shapes[e]=o,[Number(i[0])+s[0]/2,Number(i[1])+s[1]/2]}}return null},draw_shape_polygon:function(e,t,n){var r=t.split(";"),i=[];for(var s in r){var o=r[s].match(/^(\d+),(\d+)$/);o!==null&&this.coords_in_img([o[1],o[2]])&&(i[i.length]=[o[1],o[2]])}if(i.length>2){var u=this.graphics.addShape({type:"path",stroke:{weight:1,color:"black"},fill:{color:n,opacity:"0.5"}}),a=[0,0],f=[this.doc.bg_img().get("width"),this.doc.bg_img().get("height")];for(s=0;sthis.doc.bg_img().get("width")||e[1]>this.doc.bg_img().get("height")?!1:!0},constrain_to_bgimg:function(e){var t=this.convert_to_bg_img_xy(e);return t[0]=Math.max(0,t[0]),t[1]=Math.max(0,t[1]),t[0]=Math.min(this.doc.bg_img().get("width"),t[0]),t[1]=Math.min(this.doc.bg_img().get("height"),t[1]),this.convert_to_window_xy(t)},convert_to_bg_img_xy:function(e){return[Number(e[0])-this.doc.bg_img().getX()-1,Number(e[1])-this.doc.bg_img().getY()-1]},redraw_drags_and_drops:function(){this.doc.drag_items().each(function(e){e.addClass("unneeded")},this),this.doc.inputs_for_choices().each(function(e){var t=this.get_choiceno_for_node(e),n=this.get_coords(e),r=this.doc.drag_item_home(t);for(var i=0;i'),this.bg_img().on("load",this.on_image_load,this,"bg_image")},drag_items:function(){return n.all(".dragitem")},drag_items_for_choice:function(e){return n.all("span.dragitem.choice"+e)},drag_item_for_choice:function(e,t){return n.one("span.dragitem.choice"+e+".item"+t)},drag_item_being_dragged:function(e){return n.one("span.dragitem.beingdragged.choice"+e)},drag_item_home:function(e){return n.one("span.draghome.choice"+e)},drag_item_homes:function(){return n.all("span.draghome")},get_classname_numeric_suffix:function(e,t){var n=e.getAttribute("class");if(n!==""){var r=n.split(" ");for(var i=0;i'+t+""):this.doc.marker_texts().append(''+t+"")}var a="draw_shape_"+n;if(this[a]instanceof Function){var f=this[a](e,r,i);if(f!==null){var l=this.doc.top_node().one("div.ddarea div.markertexts span.markertext"+e);if(l!==null){l.setStyle("opacity","0.6"),f[0]-=l.get("offsetWidth")/2,f[1]-=l.get("offsetHeight")/2,l.setXY(this.convert_to_window_xy(f));var c=l.one("a");c!==null&&(c.once("click",function(e,t){var n=this.shapes[t].get("fill");n.opacity=1,this.shapes[t].set("fill",n)},this,e),c.set("tabIndex",0))}}}},draw_shape_circle:function(e,t,n){var r=t.match(/(\d+),(\d+);(\d+)/);if(r&&r.length===4){var i=[Number(r[1])-r[3],Number(r[2])-r[3]];if(this.coords_in_img(i)){var s=[Number(r[3])*2,Number(r[3])*2],o=this.graphics.addShape({type:"circle",width:s[0],height:s[1],fill:{color:n,opacity:"0.5"},stroke:{weight:1,color:"black"}});return o.setXY(this.convert_to_window_xy(i)),this.shapes[e]=o,[Number(r[1]),Number(r[2])]}}return null},draw_shape_rectangle:function(e,t,n){var r=t.match(/(\d+),(\d+);(\d+),(\d+)/);if(r&&r.length===5){var i=[Number(r[1]),Number(r[2])],s=[Number(r[3]),Number(r[4])];if(this.coords_in_img([i[0]+s[0],i[1]+s[1]])){var o=this.graphics.addShape({type:"rect",width:s[0],height:s[1],fill:{color:n,opacity:"0.5"},stroke:{weight:1,color:"black"}});return o.setXY(this.convert_to_window_xy(i)),this.shapes[e]=o,[Number(i[0])+s[0]/2,Number(i[1])+s[1]/2]}}return null},draw_shape_polygon:function(e,t,n){var r=t.split(";"),i=[];for(var s in r){var o=r[s].match(/^(\d+),(\d+)$/);o!==null&&this.coords_in_img([o[1],o[2]])&&(i[i.length]=[o[1],o[2]])}if(i.length>2){var u=this.graphics.addShape({type:"path",stroke:{weight:1,color:"black"},fill:{color:n,opacity:"0.5"}}),a=[0,0],f=[this.doc.bg_img().get("width"),this.doc.bg_img().get("height")];for(s=0;sthis.doc.bg_img().get("width")||e[1]>this.doc.bg_img().get("height")?!1:!0},constrain_to_bgimg:function(e){var t=this.convert_to_bg_img_xy(e);return t[0]=Math.max(0,t[0]),t[1]=Math.max(0,t[1]),t[0]=Math.min(this.doc.bg_img().get("width"),t[0]),t[1]=Math.min(this.doc.bg_img().get("height"),t[1]),this.convert_to_window_xy(t)},convert_to_bg_img_xy:function(e){return[Number(e[0])-this.doc.bg_img().getX()-1,Number(e[1])-this.doc.bg_img().getY()-1]},redraw_drags_and_drops:function(){this.doc.drag_items().each(function(e){e.addClass("unneeded")},this),this.doc.inputs_for_choices().each(function(e){var t=this.get_choiceno_for_node(e),n=this.get_coords(e),r=this.doc.drag_item_home(t);for(var i=0;i0)n!==!0&&(n-=1),e.later(500,this,this.position_drag_items,[t,n])},position_drag_item:function(t){if(!t.hasClass("yui3-dd-dragging"))if(!this.placed[this.get_no(t)]){var n=this.get_group(t),r=this.get_choice(t),i=e.one(this.selectors.drag_home(n,r));t.setXY(i.getXY()),t.addClass("unplaced")}else{var s=this.placed[this.get_no(t)],o=e.one(this.selectors.drop_for_place(s));t.setXY([ -o.getX()+2,o.getY()+2]),t.removeClass("unplaced")}},drop_zone_key_press:function(e){switch(e.direction){case"next":this.place_next_drag_in(e.target);break;case"previous":this.place_previous_drag_in(e.target);break;case"remove":this.remove_drag_from_drop(e.target)}e.preventDefault()},place_next_drag_in:function(e){this.choose_next_choice_for_drop(e,1)},place_previous_drag_in:function(e){this.choose_next_choice_for_drop(e,-1)},choose_next_choice_for_drop:function(t,n){var r,i=this.get_group(t),s=this.current_choice_in_drop(t),o=e.all(this.selectors.unplaced_drags_in_group(i));if(0===s)if(n===1)r=1;else{var u=o.pop();r=this.get_choice(u)}else r=s+n;var a;do{a=e.one(this.selectors.unplaced_drags_for_choice_in_group(r,i));if(e.one(this.selectors.drags_for_choice_in_group(r,i))===null){this.remove_drag_from_drop(t);return}r+=n}while(a===null);this.place_drag_in_drop(a,t)},current_choice_in_drop:function(t){var n=this.get("inputids")[this.get_place(t)],r=e.one("input#"+n);return Number(r.get("value"))}},{NAME:n,ATTRS:{readonly:{value:!1},topnode:{value:null},inputids:{value:null}}}),e.Event.define("dragchange",{_event:e.UA.webkit||e.UA.ie?"keydown":"keypress",_keys:{32:"next",37:"previous",38:"previous",39:"next",40:"next",27:"remove"},_keyHandler:function(e,t){this._keys[e.keyCode]&&(e.direction=this._keys[e.keyCode],t.fire(e))},on:function(e,t,n){t._detacher=e.on(this._event,this._keyHandler,this,n)}}),M.qtype_ddwtos=M.qtype_ddwtos||{},M.qtype_ddwtos.init_question=function(e){return new r(e)}},"@VERSION@",{requires:["node","dd","dd-drop","dd-constrain"]}); +YUI.add("moodle-qtype_ddwtos-dd",function(e,t){var n="ddwtos_dd",r=function(){r.superclass.constructor.apply(this,arguments)};e.extend(r,e.Base,{selectors:null,passiveSupported:!1,initializer:function(){var t="qtype_ddwtos-"+Math.random().toString(36).slice(2);M.util.js_pending(t),this.selectors=this.css_selectors(this.get("topnode")),this.set_padding_sizes_all(),this.clone_drag_items(),this.initial_place_of_drag_items(),this.make_drop_zones(),this.get("readonly")?(e.later(500,this,this.position_drag_items,[t,3]),e.one("window").on("resize",function(){this.position_drag_items(t)},this)):e.later(500,this,this.position_drag_items,[t,!0]),this.checkPassiveSupported()},css_selectors:function(e){return{top_node:function(){return e},drag_container:function(){return e+" div.drags"},drags:function(){return this.drag_container()+" span.drag"},drag:function(e){return this.drags()+".no"+e},drags_in_group:function(e){return this.drags()+".group"+e},unplaced_drags_in_group:function(e){return this.drags_in_group(e)+".unplaced"},drags_for_choice_in_group:function(e,t){return this.drags_in_group(t)+".choice"+e},unplaced_drags_for_choice_in_group:function(e,t){return this.unplaced_drags_in_group(t)+".choice"+e},drops:function(){return e+" span.drop"},drop_for_place:function(e){return this.drops()+".place"+e},drops_in_group:function(e){return this.drops()+".group"+e},drag_homes:function(){return e+" span.draghome"},drag_homes_group:function(t){return e+" .draggrouphomes"+t+" span.draghome"},drag_home:function(t,n){return e+" .draggrouphomes"+t+" span.draghome.choice"+n},drops_group:function(t){return e+" span.drop.group"+t}}},set_padding_sizes_all:function(){for(var e=1;e<=8;e++)this.set_padding_size_for_group(e)},set_padding_size_for_group:function(t){var n=e.all(this.selectors.drag_homes_group(t));if(n.size()!==0){var r=0,i=0;n.each(function(e){r=Math.max(r,Math.ceil(e.get("offsetWidth"))),i=Math.max(i,Math.ceil(e.get("offsetHeight")))},this),r+=8,i+=2,n.each(function(e){this.pad_to_width_height(e,r,i)},this),e.all(this.selectors.drops_group(t)).each(function(e){this.pad_to_width_height(e,r+2,i+2)},this)}},pad_to_width_height:function(e,t,n){e.setStyle("width",t+"px").setStyle("height",n+"px").setStyle("lineHeight",n+"px")},clone_drag_items:function(){e.all(this.selectors.drag_homes()).each(this.clone_drag_items_for_one_choice,this)},clone_drag_items_for_one_choice:function(t){if(t.hasClass("infinite")){var n=this.get_group(t),r=e.all(this.selectors.drops_in_group(n)).size();for(var i=0;i0)n!==!0&&(n-=1),e.later(500,this,this.position_drag_items,[t,n])},position_drag_item:function(t){if(!t.hasClass("yui3-dd-dragging"))if(!this.placed[this.get_no(t)]){var n=this.get_group(t),r=this.get_choice(t),i=e.one(this.selectors.drag_home(n,r));t.setXY(i.getXY()),t.addClass("unplaced")}else{var s=this +.placed[this.get_no(t)],o=e.one(this.selectors.drop_for_place(s));t.setXY([o.getX()+2,o.getY()+2]),t.removeClass("unplaced")}},drop_zone_key_press:function(e){switch(e.direction){case"next":this.place_next_drag_in(e.target);break;case"previous":this.place_previous_drag_in(e.target);break;case"remove":this.remove_drag_from_drop(e.target)}e.preventDefault()},place_next_drag_in:function(e){this.choose_next_choice_for_drop(e,1)},place_previous_drag_in:function(e){this.choose_next_choice_for_drop(e,-1)},choose_next_choice_for_drop:function(t,n){var r,i=this.get_group(t),s=this.current_choice_in_drop(t),o=e.all(this.selectors.unplaced_drags_in_group(i));if(0===s)if(n===1)r=1;else{var u=o.pop();r=this.get_choice(u)}else r=s+n;var a;do{a=e.one(this.selectors.unplaced_drags_for_choice_in_group(r,i));if(e.one(this.selectors.drags_for_choice_in_group(r,i))===null){this.remove_drag_from_drop(t);return}r+=n}while(a===null);this.place_drag_in_drop(a,t)},current_choice_in_drop:function(t){var n=this.get("inputids")[this.get_place(t)],r=e.one("input#"+n);return Number(r.get("value"))}},{NAME:n,ATTRS:{readonly:{value:!1},topnode:{value:null},inputids:{value:null}}}),e.Event.define("dragchange",{_event:e.UA.webkit||e.UA.ie?"keydown":"keypress",_keys:{32:"next",37:"previous",38:"previous",39:"next",40:"next",27:"remove"},_keyHandler:function(e,t){this._keys[e.keyCode]&&(e.direction=this._keys[e.keyCode],t.fire(e))},on:function(e,t,n){t._detacher=e.on(this._event,this._keyHandler,this,n)}}),M.qtype_ddwtos=M.qtype_ddwtos||{},M.qtype_ddwtos.init_question=function(e){return new r(e)}},"@VERSION@",{requires:["node","dd","dd-drop","dd-constrain"]}); diff --git a/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd.js b/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd.js index 88f3cb6d2d83..eaad12d19a32 100644 --- a/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd.js +++ b/question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd.js @@ -34,7 +34,7 @@ var DDWTOS_DD = function() { */ Y.extend(DDWTOS_DD, Y.Base, { selectors: null, - touchscrolldisable: null, + passiveSupported: false, initializer: function() { var pendingid = 'qtype_ddwtos-' + Math.random().toString(36).slice(2); // Random string. M.util.js_pending(pendingid); @@ -51,6 +51,7 @@ Y.extend(DDWTOS_DD, Y.Base, { this.position_drag_items(pendingid); }, this); } + this.checkPassiveSupported(); }, /** * put all our selectors in the same place so we can quickly find and change them later @@ -231,28 +232,34 @@ Y.extend(DDWTOS_DD, Y.Base, { * draggable items. */ prevent_touchmove_from_scrolling: function(drag) { - var touchstart = (Y.UA.ie) ? 'MSPointerStart' : 'touchstart'; - var touchend = (Y.UA.ie) ? 'MSPointerEnd' : 'touchend'; var touchmove = (Y.UA.ie) ? 'MSPointerMove' : 'touchmove'; + var eventHandler = function(event) { + event.preventDefault(); + }; + var dragId = drag.get('id'); + var el = document.getElementById(dragId); + // Note do not dynamically add events within another event, as this causes issues on iOS11.3. + // See https://github.com/atlassian/react-beautiful-dnd/issues/413 and + // https://bugs.webkit.org/show_bug.cgi?id=184250 for fuller explanation. + el.addEventListener(touchmove, eventHandler, this.passiveSupported ? {passive: false, capture: true} : false); + }, - // Disable scrolling when touching the draggable items. - drag.on(touchstart, function() { - if (this.touchscrolldisable) { - return; // Already disabled. - } - this.touchscrolldisable = Y.one('body').on(touchmove, function(e) { - e = e || window.event; - e.preventDefault(); + /** + * Some older browsers do not support passing an options object to addEventListener. + * This is a check from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener. + */ + checkPassiveSupported: function() { + try { + var options = Object.defineProperty({}, 'passive', { + get: function() { + this.passiveSupported = true; + }.bind(this) }); - }, this); - - // Allow scrolling after releasing the draggable items. - drag.on(touchend, function() { - if (this.touchscrolldisable) { - this.touchscrolldisable.detach(); - this.touchscrolldisable = null; - } - }, this); + window.addEventListener('test', options, options); + window.removeEventListener('test', options, options); + } catch (err) { + this.passiveSupported = false; + } }, make_drop_zones: function() { @@ -434,4 +441,5 @@ M.qtype_ddwtos.init_question = function(config) { return new DDWTOS_DD(config); }; + }, '@VERSION@', {"requires": ["node", "dd", "dd-drop", "dd-constrain"]}); diff --git a/question/type/ddwtos/yui/src/ddwtos/js/ddwtos.js b/question/type/ddwtos/yui/src/ddwtos/js/ddwtos.js index b02168e3fb6f..79fd922e0005 100644 --- a/question/type/ddwtos/yui/src/ddwtos/js/ddwtos.js +++ b/question/type/ddwtos/yui/src/ddwtos/js/ddwtos.js @@ -32,7 +32,7 @@ var DDWTOS_DD = function() { */ Y.extend(DDWTOS_DD, Y.Base, { selectors: null, - touchscrolldisable: null, + passiveSupported: false, initializer: function() { var pendingid = 'qtype_ddwtos-' + Math.random().toString(36).slice(2); // Random string. M.util.js_pending(pendingid); @@ -49,6 +49,7 @@ Y.extend(DDWTOS_DD, Y.Base, { this.position_drag_items(pendingid); }, this); } + this.checkPassiveSupported(); }, /** * put all our selectors in the same place so we can quickly find and change them later @@ -229,28 +230,34 @@ Y.extend(DDWTOS_DD, Y.Base, { * draggable items. */ prevent_touchmove_from_scrolling: function(drag) { - var touchstart = (Y.UA.ie) ? 'MSPointerStart' : 'touchstart'; - var touchend = (Y.UA.ie) ? 'MSPointerEnd' : 'touchend'; var touchmove = (Y.UA.ie) ? 'MSPointerMove' : 'touchmove'; + var eventHandler = function(event) { + event.preventDefault(); + }; + var dragId = drag.get('id'); + var el = document.getElementById(dragId); + // Note do not dynamically add events within another event, as this causes issues on iOS11.3. + // See https://github.com/atlassian/react-beautiful-dnd/issues/413 and + // https://bugs.webkit.org/show_bug.cgi?id=184250 for fuller explanation. + el.addEventListener(touchmove, eventHandler, this.passiveSupported ? {passive: false, capture: true} : false); + }, - // Disable scrolling when touching the draggable items. - drag.on(touchstart, function() { - if (this.touchscrolldisable) { - return; // Already disabled. - } - this.touchscrolldisable = Y.one('body').on(touchmove, function(e) { - e = e || window.event; - e.preventDefault(); + /** + * Some older browsers do not support passing an options object to addEventListener. + * This is a check from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener. + */ + checkPassiveSupported: function() { + try { + var options = Object.defineProperty({}, 'passive', { + get: function() { + this.passiveSupported = true; + }.bind(this) }); - }, this); - - // Allow scrolling after releasing the draggable items. - drag.on(touchend, function() { - if (this.touchscrolldisable) { - this.touchscrolldisable.detach(); - this.touchscrolldisable = null; - } - }, this); + window.addEventListener('test', options, options); + window.removeEventListener('test', options, options); + } catch (err) { + this.passiveSupported = false; + } }, make_drop_zones: function() { @@ -430,4 +437,4 @@ Y.Event.define('dragchange', { M.qtype_ddwtos = M.qtype_ddwtos || {}; M.qtype_ddwtos.init_question = function(config) { return new DDWTOS_DD(config); -}; \ No newline at end of file +}; From 0ffd5928bfb3a0d4147fffaceb76666bac46c918 Mon Sep 17 00:00:00 2001 From: Derick Turner Date: Mon, 21 May 2018 13:53:12 +0100 Subject: [PATCH 31/35] IOMAD: user cannot get own certificate if they are a manager. #927 --- mod/iomadcertificate/view.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/mod/iomadcertificate/view.php b/mod/iomadcertificate/view.php index 6d2c852ad04a..40a2a56ab5fa 100644 --- a/mod/iomadcertificate/view.php +++ b/mod/iomadcertificate/view.php @@ -80,8 +80,6 @@ if (empty($action)) { // Initialize $PAGE, compute blocks $PAGE->set_url('/mod/iomadcertificate/view.php', array('id' => $cm->id)); - $PAGE->set_context($context); - $PAGE->set_cm($cm); $PAGE->set_title(format_string($iomadcertificate->name)); $PAGE->set_heading(format_string($course->fullname)); } From a8662028ab30b75f5ef406344062c5bdc38f1e36 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Wed, 11 Apr 2018 16:39:04 +0800 Subject: [PATCH 32/35] MDL-61932 mod_glossary: Display site-level glossaries on section 1 * Glossary activities created on the front page by importing entries are being added to section 0, but the front page only shows activities on section 1. --- mod/glossary/db/upgrade.php | 58 ++++++++++++++++++++++++++++++++++++- mod/glossary/import.php | 11 +++++-- mod/glossary/version.php | 2 +- 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/mod/glossary/db/upgrade.php b/mod/glossary/db/upgrade.php index 5272f6f1124b..4da3823382dd 100644 --- a/mod/glossary/db/upgrade.php +++ b/mod/glossary/db/upgrade.php @@ -20,7 +20,7 @@ // before any action that may take longer time to finish. function xmldb_glossary_upgrade($oldversion) { - global $CFG; + global $CFG, $DB; // Moodle v3.1.0 release upgrade line. // Put any upgrade step following this. @@ -34,5 +34,61 @@ function xmldb_glossary_upgrade($oldversion) { // Automatically generated Moodle v3.4.0 release upgrade line. // Put any upgrade step following this. + if ($oldversion < 2017111301) { + + // Fetch the module ID for the glossary module. + $glossarymoduleid = $DB->get_field('modules', 'id', ['name' => 'glossary']); + + // Fetch sections for the frontpage not matching 1. + $sectionselect = 'course = :course AND section <> 1'; + $sitesections = $DB->get_recordset_select('course_sections', $sectionselect, ['course' => SITEID]); + $newsection1cmids = []; + foreach ($sitesections as $section) { + // Fetch the course module IDs of the course modules in the section. + $cmids = explode(',', $section->sequence); + $newcmids = []; + // Update the section in the course_modules table for glossary instances if necessary. + foreach ($cmids as $cmid) { + $params = [ + 'id' => $cmid, + 'module' => $glossarymoduleid + ]; + // Check if the record in the course_modules tables is that of a glossary activity. + if ($DB->record_exists('course_modules', $params)) { + // If so, move it to section 1. + $DB->set_field('course_modules', 'section', 1, $params); + $newsection1cmids[] = $cmid; + } else { + // Otherwise, ignore this course module as we only want to update glossary items. + $newcmids[] = $cmid; + } + } + // Check if we need to update the section record or we can delete it. + if (!empty($newcmids)) { + // Update the section record with a sequence that now excludes the glossary instance(s). + $sequence = implode(',', $newcmids); + $DB->set_field('course_sections', 'section', $sequence, ['id' => $section->id]); + } else { + // This section doesn't contain any items anymore, we can remove this. + $DB->delete_records('course_sections', ['id' => $section->id]); + } + } + $sitesections->close(); + + // Update the sequence field for the site's section 1 if necessary. + if (!empty($newsection1cmids)) { + $section1params = [ + 'course' => 1, + 'section' => 1 + ]; + $section1sequence = $DB->get_field('course_sections', 'sequence', $section1params); + $newsection1sequence = implode(',', array_merge([$section1sequence], $newsection1cmids)); + // Update the sequence field of the first section for the front page. + $DB->set_field('course_sections', 'sequence', $newsection1sequence, $section1params); + } + + upgrade_mod_savepoint(true, 2017111301, 'glossary'); + } + return true; } diff --git a/mod/glossary/import.php b/mod/glossary/import.php index 902455cd2456..783864ef6d5b 100644 --- a/mod/glossary/import.php +++ b/mod/glossary/import.php @@ -141,8 +141,15 @@ $glossary->assessed = 0; $glossary->availability = null; - // New glossary is to be inserted in section 0, it is always visible. - $glossary->section = 0; + // Check if we're creating the new glossary on the front page or inside a course. + if ($cm->course == SITEID) { + // On the front page, activities are in section 1. + $glossary->section = 1; + } else { + // Inside a course, add to the general section (0). + $glossary->section = 0; + } + // New glossary is always visible. $glossary->visible = 1; $glossary->visibleoncoursepage = 1; diff --git a/mod/glossary/version.php b/mod/glossary/version.php index 9a358279a43e..a7eade07e5a0 100644 --- a/mod/glossary/version.php +++ b/mod/glossary/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2017111300; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2017111301; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017110800; // Requires this Moodle version $plugin->component = 'mod_glossary'; // Full name of the plugin (used for diagnostics) $plugin->cron = 0; From 1fc9b813b293e33a21394ac8fb97b26e37e9627a Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Tue, 22 May 2018 12:14:41 +0800 Subject: [PATCH 33/35] MDL-61932 mod_glossary: Fetch the correct front page section 1 ID --- mod/glossary/db/upgrade.php | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/mod/glossary/db/upgrade.php b/mod/glossary/db/upgrade.php index 4da3823382dd..b91443c411ed 100644 --- a/mod/glossary/db/upgrade.php +++ b/mod/glossary/db/upgrade.php @@ -39,14 +39,22 @@ function xmldb_glossary_upgrade($oldversion) { // Fetch the module ID for the glossary module. $glossarymoduleid = $DB->get_field('modules', 'id', ['name' => 'glossary']); + // Get id of section 1 on the frontpage course. + $fpsection1 = $DB->get_field('course_sections', 'id', ['course' => SITEID, 'section' => 1]); + // Fetch sections for the frontpage not matching 1. $sectionselect = 'course = :course AND section <> 1'; $sitesections = $DB->get_recordset_select('course_sections', $sectionselect, ['course' => SITEID]); $newsection1cmids = []; foreach ($sitesections as $section) { + // Check if we have anything to process for this section. + if (empty($section->sequence)) { + // If there's none, ignore. + continue; + } // Fetch the course module IDs of the course modules in the section. $cmids = explode(',', $section->sequence); - $newcmids = []; + $nonglossarycmids = []; // Update the section in the course_modules table for glossary instances if necessary. foreach ($cmids as $cmid) { $params = [ @@ -55,19 +63,21 @@ function xmldb_glossary_upgrade($oldversion) { ]; // Check if the record in the course_modules tables is that of a glossary activity. if ($DB->record_exists('course_modules', $params)) { - // If so, move it to section 1. - $DB->set_field('course_modules', 'section', 1, $params); + // If so, move it to front page's section 1. + $DB->set_field('course_modules', 'section', $fpsection1, $params); $newsection1cmids[] = $cmid; } else { // Otherwise, ignore this course module as we only want to update glossary items. - $newcmids[] = $cmid; + $nonglossarycmids[] = $cmid; } } // Check if we need to update the section record or we can delete it. - if (!empty($newcmids)) { - // Update the section record with a sequence that now excludes the glossary instance(s). - $sequence = implode(',', $newcmids); - $DB->set_field('course_sections', 'section', $sequence, ['id' => $section->id]); + if (!empty($nonglossarycmids)) { + // Update the section record with a sequence that now excludes the glossary instance(s) (if it changed). + $sequence = implode(',', $nonglossarycmids); + if ($sequence != $section->sequence) { + $DB->set_field('course_sections', 'sequence', $sequence, ['id' => $section->id]); + } } else { // This section doesn't contain any items anymore, we can remove this. $DB->delete_records('course_sections', ['id' => $section->id]); @@ -78,7 +88,7 @@ function xmldb_glossary_upgrade($oldversion) { // Update the sequence field for the site's section 1 if necessary. if (!empty($newsection1cmids)) { $section1params = [ - 'course' => 1, + 'course' => SITEID, 'section' => 1 ]; $section1sequence = $DB->get_field('course_sections', 'sequence', $section1params); From 951febb2a88deb81a5614cfadafd38254c8cc26c Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Tue, 22 May 2018 13:48:17 +0800 Subject: [PATCH 34/35] MDL-61189 editor_tinymce: save data before submitting --- lib/editor/tinymce/module.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/editor/tinymce/module.js b/lib/editor/tinymce/module.js index d3118ef0f335..b7b790472a60 100644 --- a/lib/editor/tinymce/module.js +++ b/lib/editor/tinymce/module.js @@ -80,6 +80,16 @@ M.editor_tinymce.init_editor = function(Y, editorid, options) { }); }; } + + // Retain any setup which is already defined. + options.originalSetupFunction = options.setup || function(){}; + options.setup = function(editor) { + options.originalSetupFunction(); + editor.onChange.add(function(ed) { + ed.save(); + }); + }; + tinyMCE.init(options); var item = document.getElementById(editorid+'_filemanager'); From 9cf74e263693dc2d6b792f7ebd9d9c31017f1410 Mon Sep 17 00:00:00 2001 From: David Monllao Date: Thu, 24 May 2018 10:45:59 +0200 Subject: [PATCH 35/35] weekly release 3.4.3+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index a66943d873f6..050404b14591 100644 --- a/version.php +++ b/version.php @@ -29,11 +29,11 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2017111303.00; // 20171113 = branching date YYYYMMDD - do not modify! +$version = 2017111303.01; // 20171113 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '3.4.3 (Build: 20180517)'; // Human-friendly version name +$release = '3.4.3+ (Build: 20180524)'; // Human-friendly version name $branch = '34'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level.
" . get_string('firstname') . "" . get_string('lastname') . "" . get_string('email') . "