MDL-63531 mod_assign: Update mod assign to use new interface.
authorAdrian Greeve <abgreeve@gmail.com>
Wed, 10 Oct 2018 05:27:23 +0000 (13:27 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Tue, 30 Oct 2018 01:06:12 +0000 (09:06 +0800)
This introduces a new interface for assign sub-plugins and
updates the mod_assign provider to implement the new general
interface for deleting data for users in a context.

mod/assign/classes/privacy/assign_plugin_request_data.php
mod/assign/classes/privacy/provider.php
mod/assign/tests/privacy_test.php

index c935972..4bb664d 100644 (file)
@@ -48,6 +48,15 @@ class assign_plugin_request_data {
     /** @var object If set then only export data related directly to this user. */
     protected $user;
 
+    /** @var array The user IDs of the users that will be affected. */
+    protected $userids;
+
+    /** @var array The submissions related to the users added. */
+    protected $submissions = [];
+
+    /** @var array The grades related to the users added. */
+    protected $grades = [];
+
     /** @var assign The assign object */
     protected $assign;
 
@@ -69,6 +78,16 @@ class assign_plugin_request_data {
         $this->assign = $assign;
     }
 
+    /**
+     * Method for adding an array of user IDs. This will do a query to populate the submissions and grades
+     * for these users.
+     *
+     * @param array $userids User IDs to do something with.
+     */
+    public function set_userids(array $userids) {
+        $this->userids = $userids;
+    }
+
     /**
      * Getter for this attribute.
      *
@@ -113,4 +132,75 @@ class assign_plugin_request_data {
     public function get_assign() {
         return $this->assign;
     }
+
+    /**
+     * A method to conveniently fetch the assign id.
+     *
+     * @return int The assign id.
+     */
+    public function get_assignid() {
+        return $this->assign->get_instance()->id;
+    }
+
+    /**
+     * Get all of the user IDs
+     *
+     * @return array User IDs
+     */
+    public function get_userids() {
+        return $this->userids;
+    }
+
+    /**
+     * Returns all of the submission IDs
+     *
+     * @return array submission IDs
+     */
+    public function get_submissionids() {
+        return array_keys($this->submissions);
+    }
+
+    /**
+     * Returns the submissions related to the user IDs
+     *
+     * @return array User submissions.
+     */
+    public function get_submissions() {
+        return $this->submissions;
+    }
+
+    /**
+     * Returns the grade IDs related to the user IDs
+     *
+     * @return array User grade IDs.
+     */
+    public function get_gradeids() {
+        return array_keys($this->grades);
+    }
+
+    /**
+     * Returns the grades related to the user IDs
+     *
+     * @return array User grades.
+     */
+    public function get_grades() {
+        return $this->grades;
+    }
+
+    /**
+     * Fetches all of the submissions and grades related to the User IDs provided. Use get_grades, get_submissions etc to
+     * retrieve this information.
+     */
+    public function populate_submissions_and_grades() {
+        global $DB;
+
+        if (empty($this->get_userids())) {
+            throw new \coding_exception('Please use set_userids() before calling this method.');
+        }
+
+        list($sql, $params) = $DB->get_in_or_equal($this->get_userids(), SQL_PARAMS_NAMED);
+        $params['assign'] = $this->get_assign()->get_instance()->id;
+        $this->submissions = $DB->get_records_select('assign_submission', "assignment = :assign AND userid $sql", $params);
+        $this->grades = $DB->get_records_select('assign_grades', "assignment = :assign AND userid $sql", $params);
+    }
 }
index 56a7736..fc0e0ab 100644 (file)
@@ -29,14 +29,13 @@ defined('MOODLE_INTERNAL') || die();
 require_once($CFG->dirroot . '/mod/assign/locallib.php');
 
 use \core_privacy\local\metadata\collection;
-use \core_privacy\local\metadata\provider as metadataprovider;
 use \core_privacy\local\request\contextlist;
-use \core_privacy\local\request\plugin\provider as pluginprovider;
-use \core_privacy\local\request\user_preference_provider as preference_provider;
 use \core_privacy\local\request\writer;
 use \core_privacy\local\request\approved_contextlist;
 use \core_privacy\local\request\transform;
 use \core_privacy\local\request\helper;
+use \core_privacy\local\request\userlist;
+use \core_privacy\local\request\approved_userlist;
 use \core_privacy\manager;
 
 /**
@@ -46,11 +45,21 @@ use \core_privacy\manager;
  * @copyright  2018 Adrian Greeve <adrian@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class provider implements metadataprovider, pluginprovider, preference_provider {
+class provider implements
+        \core_privacy\local\metadata\provider,
+        \core_privacy\local\request\plugin\provider,
+        \core_privacy\local\request\user_preference_provider,
+        \core_privacy\local\request\core_userlist_provider {
 
     /** Interface for all assign submission sub-plugins. */
     const ASSIGNSUBMISSION_INTERFACE = 'mod_assign\privacy\assignsubmission_provider';
 
+    /** Interface for all assign submission sub-plugins. This allows for deletion of users with a context. */
+    const ASSIGNSUBMISSION_USER_INTERFACE = 'mod_assign\privacy\assignsubmission_user_provider';
+
+    /** Interface for all assign feedback sub-plugins. This allows for deletion of users with a context. */
+    const ASSIGNFEEDBACK_USER_INTERFACE = 'mod_assign\privacy\assignfeedback_user_provider';
+
     /** Interface for all assign feedback sub-plugins. */
     const ASSIGNFEEDBACK_INTERFACE = 'mod_assign\privacy\assignfeedback_provider';
 
@@ -192,6 +201,76 @@ class provider implements metadataprovider, pluginprovider, preference_provider
         return $contextlist;
     }
 
+    /**
+     * Get the list of contexts that contain user information for the specified user.
+     *
+     * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
+     */
+    public static function get_users_in_context(userlist $userlist) {
+
+        $context = $userlist->get_context();
+        if ($context->contextlevel != CONTEXT_MODULE) {
+            return;
+        }
+
+        $params = [
+            'modulename' => 'assign',
+            'contextid' => $context->id,
+            'contextlevel' => CONTEXT_MODULE
+        ];
+
+        $sql = "SELECT g.userid, g.grader
+                  FROM {context} ctx
+                  JOIN {course_modules} cm ON cm.id = ctx.instanceid
+                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+                  JOIN {assign} a ON a.id = cm.instance
+                  JOIN {assign_grades} g ON a.id = g.assignment
+                 WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
+        $userlist->add_from_sql('userid', $sql, $params);
+        $userlist->add_from_sql('grader', $sql, $params);
+
+        $sql = "SELECT o.userid
+                  FROM {context} ctx
+                  JOIN {course_modules} cm ON cm.id = ctx.instanceid
+                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+                  JOIN {assign} a ON a.id = cm.instance
+                  JOIN {assign_overrides} o ON a.id = o.assignid
+                 WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
+        $userlist->add_from_sql('userid', $sql, $params);
+
+        $sql = "SELECT s.userid
+                  FROM {context} ctx
+                  JOIN {course_modules} cm ON cm.id = ctx.instanceid
+                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+                  JOIN {assign} a ON a.id = cm.instance
+                  JOIN {assign_submission} s ON a.id = s.assignment
+                 WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
+        $userlist->add_from_sql('userid', $sql, $params);
+
+        $sql = "SELECT uf.userid
+                  FROM {context} ctx
+                  JOIN {course_modules} cm ON cm.id = ctx.instanceid
+                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+                  JOIN {assign} a ON a.id = cm.instance
+                  JOIN {assign_user_flags} uf ON a.id = uf.assignment
+                 WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
+        $userlist->add_from_sql('userid', $sql, $params);
+
+        $sql = "SELECT um.userid
+                  FROM {context} ctx
+                  JOIN {course_modules} cm ON cm.id = ctx.instanceid
+                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+                  JOIN {assign} a ON a.id = cm.instance
+                  JOIN {assign_user_mapping} um ON a.id = um.assignment
+                 WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
+        $userlist->add_from_sql('userid', $sql, $params);
+
+        manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_USER_INTERFACE,
+                'get_userids_from_context', [$userlist]);
+        manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_USER_INTERFACE,
+                'get_userids_from_context', [$userlist]);
+    }
+
     /**
      * Write out the user data filtered by contexts.
      *
@@ -265,7 +344,7 @@ class provider implements metadataprovider, pluginprovider, preference_provider
                 }
 
                 // Time to roll my own method for deleting overrides.
-                static::delete_user_overrides($assign);
+                static::delete_overrides_for_users($assign);
                 $DB->delete_records('assign_submission', ['assignment' => $assign->get_instance()->id]);
                 $DB->delete_records('assign_user_flags', ['assignment' => $assign->get_instance()->id]);
                 $DB->delete_records('assign_user_mapping', ['assignment' => $assign->get_instance()->id]);
@@ -311,7 +390,7 @@ class provider implements metadataprovider, pluginprovider, preference_provider
                 }
             }
 
-            static::delete_user_overrides($assign, $user);
+            static::delete_overrides_for_users($assign, [$user->id]);
             $DB->delete_records('assign_user_flags', ['assignment' => $assignid, 'userid' => $user->id]);
             $DB->delete_records('assign_user_mapping', ['assignment' => $assignid, 'userid' => $user->id]);
             $DB->delete_records('assign_grades', ['assignment' => $assignid, 'userid' => $user->id]);
@@ -320,31 +399,86 @@ class provider implements metadataprovider, pluginprovider, preference_provider
     }
 
     /**
-     * Deletes assignment overrides.
+     * Delete multiple users within a single context.
      *
-     * @param  \assign $assign The assignment object
-     * @param  \stdClass $user The user object if we are deleting only the overrides for one user.
+     * @param  approved_userlist $userlist The approved context and user information to delete information for.
      */
-    protected static function delete_user_overrides(\assign $assign, \stdClass $user = null) {
+    public static function delete_data_for_users(approved_userlist $userlist) {
         global $DB;
 
+        $context = $userlist->get_context();
+        if ($context->contextlevel != CONTEXT_MODULE) {
+            return;
+        }
+
+        $userids = $userlist->get_userids();
+
+        $assign = new \assign($context, null, null);
         $assignid = $assign->get_instance()->id;
-        $params = (isset($user)) ? ['assignid' => $assignid, 'userid' => $user->id] : ['assignid' => $assignid];
+        $requestdata = new assign_plugin_request_data($context, $assign);
+        $requestdata->set_userids($userids);
+        $requestdata->populate_submissions_and_grades();
+        manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_USER_INTERFACE, 'delete_submissions',
+                [$requestdata]);
+        manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_USER_INTERFACE, 'delete_feedback_for_grades',
+                [$requestdata]);
+
+        // Update this function to delete advanced grading information.
+        $gradingmanager = get_grading_manager($context, 'mod_assign', 'submissions');
+        $controller = $gradingmanager->get_active_controller();
+        if (isset($controller)) {
+            $gradeids = $requestdata->get_gradeids();
+            // Careful here, if no gradeids are provided then all data is deleted for the context.
+            if (!empty($gradeids)) {
+                \core_grading\privacy\provider::delete_data_for_instances($context, $gradeids);
+            }
+        }
 
-        $overrides = $DB->get_records('assign_overrides', $params);
-        if (!empty($overrides)) {
-            foreach ($overrides as $override) {
+        static::delete_overrides_for_users($assign, $userids);
+        list($sql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
+        $params['assignment'] = $assignid;
+        $DB->delete_records_select('assign_user_flags', "assignment = :assignment AND userid $sql", $params);
+        $DB->delete_records_select('assign_user_mapping', "assignment = :assignment AND userid $sql", $params);
+        $DB->delete_records_select('assign_grades', "assignment = :assignment AND userid $sql", $params);
+        $DB->delete_records_select('assign_submission', "assignment = :assignment AND userid $sql", $params);
+    }
 
-                // First delete calendar events associated with this override.
-                $conditions = ['modulename' => 'assign', 'instance' => $assignid];
-                if (isset($user)) {
-                    $conditions['userid'] = $user->id;
-                }
-                $DB->delete_records('event', $conditions);
+    /**
+     * Deletes assignment overrides in bulk
+     *
+     * @param  \assign $assign  The assignment object
+     * @param  array   $userids An array of user IDs
+     */
+    protected static function delete_overrides_for_users(\assign $assign, array $userids = []) {
+        global $DB;
+        $assignid = $assign->get_instance()->id;
 
-                // Next delete the overrides.
-                $DB->delete_records('assign_overrides', ['id' => $override->id]);
+        $usersql = '';
+        $params = ['assignid' => $assignid];
+        if (!empty($userids)) {
+            list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
+            $params = array_merge($params, $userparams);
+            $overrides = $DB->get_records_select('assign_overrides', "assignid = :assignid AND userid $usersql", $params);
+        } else {
+            $overrides = $DB->get_records('assign_overrides', $params);
+        }
+        if (!empty($overrides)) {
+            $params = ['modulename' => 'assign', 'instance' => $assignid];
+            if (!empty($userids)) {
+                $params = array_merge($params, $userparams);
+                $DB->delete_records_select('event', "modulename = :modulename AND instance = :instance AND userid $usersql",
+                        $params);
+                // Setting up for the next query.
+                $params = $userparams;
+                $usersql = "AND userid $usersql";
+            } else {
+                $DB->delete_records('event', $params);
+                // Setting up for the next query.
+                $params = [];
             }
+            list($overridesql, $overrideparams) = $DB->get_in_or_equal(array_keys($overrides), SQL_PARAMS_NAMED);
+            $params = array_merge($params, $overrideparams);
+            $DB->delete_records_select('assign_overrides', "id $overridesql $usersql", $params);
         }
     }
 
index bcae99d..185d5aa 100644 (file)
@@ -148,6 +148,95 @@ class mod_assign_privacy_testcase extends provider_testcase {
         $this->assertEmpty(array_diff($usercontextids, $contextlist->get_contextids()));
     }
 
+    /**
+     * Test returning a list of user IDs related to a context (assign).
+     */
+    public function test_get_users_in_context() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $course = $this->getDataGenerator()->create_course();
+
+        // Only made a comment on a submission.
+        $user1 = $this->getDataGenerator()->create_user();
+        // User 2 only has information about an activity override.
+        $user2 = $this->getDataGenerator()->create_user();
+        // User 3 made a submission.
+        $user3 = $this->getDataGenerator()->create_user();
+        // User 4 makes a submission and it is marked by the teacher.
+        $user4 = $this->getDataGenerator()->create_user();
+        // Grading and providing feedback as a teacher.
+        $user5 = $this->getDataGenerator()->create_user();
+        // This user has no entries and should not show up.
+        $user6 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user3->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user4->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user5->id, $course->id, 'editingteacher');
+        $this->getDataGenerator()->enrol_user($user6->id, $course->id, 'student');
+
+        $assign1 = $this->create_instance(['course' => $course,
+                'assignsubmission_onlinetext_enabled' => true,
+                'assignfeedback_comments_enabled' => true]);
+        $assign2 = $this->create_instance(['course' => $course]);
+
+        $context = $assign1->get_context();
+
+        // Jam an entry in the comments table for user 1.
+        $comment = (object) [
+            'contextid' => $context->id,
+            'component' => 'assignsubmission_comments',
+            'commentarea' => 'submission_comments',
+            'itemid' => 5,
+            'content' => 'A comment by user 1',
+            'format' => 0,
+            'userid' => $user1->id,
+            'timecreated' => time()
+        ];
+        $DB->insert_record('comments', $comment);
+
+        $this->setUser($user5); // Set the user to the teacher.
+
+        $overridedata = new \stdClass();
+        $overridedata->assignid = $assign1->get_instance()->id;
+        $overridedata->userid = $user2->id;
+        $overridedata->duedate = time();
+        $overridedata->allowsubmissionsfromdate = time();
+        $overridedata->cutoffdate = time();
+        $DB->insert_record('assign_overrides', $overridedata);
+
+        $submissiontext = 'My first submission';
+        $submission = $this->create_submission($assign1, $user3, $submissiontext);
+
+        $submissiontext = 'My first submission';
+        $submission = $this->create_submission($assign1, $user4, $submissiontext);
+
+        $this->setUser($user5);
+
+        $grade = '72.00';
+        $teachercommenttext = 'This is better. Thanks.';
+        $data = new \stdClass();
+        $data->attemptnumber = 1;
+        $data->grade = $grade;
+        $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext, 'format' => FORMAT_MOODLE];
+
+        // Give the submission a grade.
+        $assign1->save_grade($user4->id, $data);
+
+        $userlist = new \core_privacy\local\request\userlist($context, 'assign');
+        provider::get_users_in_context($userlist);
+        $userids = $userlist->get_userids();
+        $this->assertTrue(in_array($user1->id, $userids));
+        $this->assertTrue(in_array($user2->id, $userids));
+        $this->assertTrue(in_array($user3->id, $userids));
+        $this->assertTrue(in_array($user4->id, $userids));
+        $this->assertTrue(in_array($user5->id, $userids));
+        $this->assertFalse(in_array($user6->id, $userids));
+    }
+
     /**
      * Test that a student with multiple submissions and grades is returned with the correct data.
      */
@@ -577,4 +666,150 @@ class mod_assign_privacy_testcase extends provider_testcase {
         // The remaining event should be for user 1.
         $this->assertEquals($user1->id, $record->userid);
     }
+
+    /**
+     * A test for deleting all user data for a bunch of users.
+     */
+    public function test_delete_data_for_users() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $course = $this->getDataGenerator()->create_course();
+
+        // Only made a comment on a submission.
+        $user1 = $this->getDataGenerator()->create_user();
+        // User 2 only has information about an activity override.
+        $user2 = $this->getDataGenerator()->create_user();
+        // User 3 made a submission.
+        $user3 = $this->getDataGenerator()->create_user();
+        // User 4 makes a submission and it is marked by the teacher.
+        $user4 = $this->getDataGenerator()->create_user();
+        // Grading and providing feedback as a teacher.
+        $user5 = $this->getDataGenerator()->create_user();
+        // This user has entries in assignment 2 and should not have their data deleted.
+        $user6 = $this->getDataGenerator()->create_user();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user3->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user4->id, $course->id, 'student');
+        $this->getDataGenerator()->enrol_user($user5->id, $course->id, 'editingteacher');
+        $this->getDataGenerator()->enrol_user($user6->id, $course->id, 'student');
+
+        $assign1 = $this->create_instance(['course' => $course,
+                'assignsubmission_onlinetext_enabled' => true,
+                'assignfeedback_comments_enabled' => true]);
+        $assign2 = $this->create_instance(['course' => $course,
+                'assignsubmission_onlinetext_enabled' => true,
+                'assignfeedback_comments_enabled' => true]);
+
+        $context = $assign1->get_context();
+
+        // Jam an entry in the comments table for user 1.
+        $comment = (object) [
+            'contextid' => $context->id,
+            'component' => 'assignsubmission_comments',
+            'commentarea' => 'submission_comments',
+            'itemid' => 5,
+            'content' => 'A comment by user 1',
+            'format' => 0,
+            'userid' => $user1->id,
+            'timecreated' => time()
+        ];
+        $DB->insert_record('comments', $comment);
+
+        $this->setUser($user5); // Set the user to the teacher.
+
+        $overridedata = new \stdClass();
+        $overridedata->assignid = $assign1->get_instance()->id;
+        $overridedata->userid = $user2->id;
+        $overridedata->duedate = time();
+        $overridedata->allowsubmissionsfromdate = time();
+        $overridedata->cutoffdate = time();
+        $DB->insert_record('assign_overrides', $overridedata);
+
+        $submissiontext = 'My first submission';
+        $submission = $this->create_submission($assign1, $user3, $submissiontext);
+
+        $submissiontext = 'My first submission';
+        $submission = $this->create_submission($assign1, $user4, $submissiontext);
+
+        $submissiontext = 'My first submission';
+        $submission = $this->create_submission($assign2, $user6, $submissiontext);
+
+        $this->setUser($user5);
+
+        $grade = '72.00';
+        $teachercommenttext = 'This is better. Thanks.';
+        $data = new \stdClass();
+        $data->attemptnumber = 1;
+        $data->grade = $grade;
+        $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext, 'format' => FORMAT_MOODLE];
+
+        // Give the submission a grade.
+        $assign1->save_grade($user4->id, $data);
+
+        $this->setUser($user5);
+
+        $grade = '81.00';
+        $teachercommenttext = 'This is nice.';
+        $data = new \stdClass();
+        $data->attemptnumber = 1;
+        $data->grade = $grade;
+        $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext, 'format' => FORMAT_MOODLE];
+
+        // Give the submission a grade.
+        $assign2->save_grade($user6->id, $data);
+
+        // Check data is in place.
+        $data = $DB->get_records('assign_submission');
+        // We should have one entry for user 3 and two entries each for user 4 and 6.
+        $this->assertCount(5, $data);
+        $usercounts = [
+            $user3->id => 0,
+            $user4->id => 0,
+            $user6->id => 0
+        ];
+        foreach ($data as $datum) {
+            $usercounts[$datum->userid]++;
+        }
+        $this->assertEquals(1, $usercounts[$user3->id]);
+        $this->assertEquals(2, $usercounts[$user4->id]);
+        $this->assertEquals(2, $usercounts[$user6->id]);
+
+        $data = $DB->get_records('assign_grades');
+        // Two entries in assign_grades, one for each grade given.
+        $this->assertCount(2, $data);
+
+        $data = $DB->get_records('assign_overrides');
+        $this->assertCount(1, $data);
+
+        $data = $DB->get_records('comments');
+        $this->assertCount(1, $data);
+
+        $userlist = new \core_privacy\local\request\approved_userlist($context, 'assign', [$user1->id, $user2->id]);
+        provider::delete_data_for_users($userlist);
+
+        $data = $DB->get_records('assign_overrides');
+        $this->assertEmpty($data);
+
+        $data = $DB->get_records('comments');
+        $this->assertEmpty($data);
+
+        $data = $DB->get_records('assign_submission');
+        // No change here.
+        $this->assertCount(5, $data);
+
+        $userlist = new \core_privacy\local\request\approved_userlist($context, 'assign', [$user3->id, $user5->id]);
+        provider::delete_data_for_users($userlist);
+
+        $data = $DB->get_records('assign_submission');
+        // Only the record for user 3 has been deleted.
+        $this->assertCount(4, $data);
+
+        $data = $DB->get_records('assign_grades');
+        // Grades should be unchanged.
+        $this->assertCount(2, $data);
+    }
 }