MDL-61899 tool_dataprivacy: Additional fixes to tool
authorJun Pataleta <jun@moodle.com>
Mon, 9 Apr 2018 09:04:32 +0000 (17:04 +0800)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 18 Apr 2018 16:15:09 +0000 (18:15 +0200)
Fixes applied from the following issues:
1. MDL-61618
2. MDL-61901

admin/tool/dataprivacy/classes/api.php
admin/tool/dataprivacy/classes/contextlist.php [new file with mode: 0644]
admin/tool/dataprivacy/classes/contextlist_context.php [new file with mode: 0644]
admin/tool/dataprivacy/classes/request_contextlist.php [new file with mode: 0644]
admin/tool/dataprivacy/classes/task/initiate_data_request_task.php
admin/tool/dataprivacy/classes/task/process_data_request_task.php
admin/tool/dataprivacy/db/install.xml
admin/tool/dataprivacy/lib.php
admin/tool/dataprivacy/templates/request_details.mustache

index 9fca096..4d2f8e9 100644 (file)
@@ -28,6 +28,8 @@ use context_system;
 use core\invalid_persistent_exception;
 use core\message\message;
 use core\task\manager;
+use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\contextlist_collection;
 use core_user;
 use dml_exception;
 use moodle_exception;
@@ -338,6 +340,11 @@ class api {
         // Update the status and the DPO.
         $result = self::update_request_status($requestid, self::DATAREQUEST_STATUS_APPROVED, $USER->id);
 
+        // Approve all the contexts attached to the request.
+        // Currently, approving the request implicitly approves all associated contexts, but this may change in future, allowing
+        // users to selectively approve certain contexts only.
+        self::update_request_contexts_with_status($requestid, contextlist_context::STATUS_APPROVED);
+
         // Fire an ad hoc task to initiate the data request process.
         $task = new process_data_request_task();
         $task->set_custom_data(['requestid' => $requestid]);
@@ -753,4 +760,114 @@ class api {
         $expiredctx->set('status', $status);
         $expiredctx->save();
     }
+
+    /**
+     * Adds the contexts from the contextlist_collection to the request with the status provided.
+     *
+     * @param contextlist_collection $clcollection a collection of contextlists for all components.
+     * @param int $requestid the id of the request.
+     * @param int $status the status to set the contexts to.
+     */
+    public static function add_request_contexts_with_status(contextlist_collection $clcollection, int $requestid, int $status) {
+        foreach ($clcollection as $contextlist) {
+            // Convert the \core_privacy\local\request\contextlist into a contextlist persistent and store it.
+            $clp = \tool_dataprivacy\contextlist::from_contextlist($contextlist);
+            $clp->create();
+            $contextlistid = $clp->get('id');
+
+            // Store the associated contexts in the contextlist.
+            foreach ($contextlist->get_contextids() as $contextid) {
+                $context = new contextlist_context();
+                $context->set('contextid', $contextid)
+                    ->set('contextlistid', $contextlistid)
+                    ->set('status', $status)
+                    ->create();
+            }
+
+            // Create the relation to the request.
+            $requestcontextlist = request_contextlist::create_relation($requestid, $contextlistid);
+            $requestcontextlist->create();
+        }
+    }
+
+    /**
+     * Sets the status of all contexts associated with the request.
+     *
+     * @param int $requestid the requestid to which the contexts belong.
+     * @param int $status the status to set to.
+     * @throws \dml_exception if the requestid is invalid.
+     * @throws \moodle_exception if the status is invalid.
+     */
+    public static function update_request_contexts_with_status(int $requestid, int $status) {
+        // Validate contextlist_context status using the persistent's attribute validation.
+        $contextlistcontext = new contextlist_context();
+        $contextlistcontext->set('status', $status);
+        if (array_key_exists('status', $contextlistcontext->get_errors())) {
+            throw new moodle_exception("Invalid contextlist_context status: $status");
+        }
+
+        // Validate requestid using the persistent's record validation.
+        // A dml_exception is thrown if the record is missing.
+        $datarequest = new data_request($requestid);
+
+        // Bulk update the status of the request contexts.
+        global $DB;
+
+        $select = "SELECT ctx.id as id
+                     FROM {" . request_contextlist::TABLE . "} rcl
+                     JOIN {" . contextlist::TABLE . "} cl ON rcl.contextlistid = cl.id
+                     JOIN {" . contextlist_context::TABLE . "} ctx ON cl.id = ctx.contextlistid
+                    WHERE rcl.requestid = ?";
+
+        $update = "UPDATE {" . contextlist_context::TABLE . "}
+                      SET status = ?
+                    WHERE id IN ({$select})";
+        $DB->execute($update, [$status, $requestid]);
+    }
+
+    /**
+     * Finds all request contextlists having at least on approved context, and returns them as in a contextlist_collection.
+     *
+     * @param data_request $request the data request with which the contextlists are associated.
+     * @return contextlist_collection the collection of approved_contextlist objects.
+     */
+    public static function get_approved_contextlist_collection_for_request(data_request $request) : contextlist_collection {
+        $foruser = core_user::get_user($request->get('userid'));
+
+        // Fetch all approved contextlists and create the core_privacy\local\request\contextlist objects here.
+        global $DB;
+        $sql = "SELECT cl.component, ctx.contextid
+                  FROM {" . request_contextlist::TABLE . "} rcl
+                  JOIN {" . contextlist::TABLE . "} cl ON rcl.contextlistid = cl.id
+                  JOIN {" . contextlist_context::TABLE . "} ctx ON cl.id = ctx.contextlistid
+                 WHERE rcl.requestid = ?
+                   AND ctx.status = ?
+              ORDER BY cl.component, ctx.contextid";
+
+        // Create the approved contextlist collection object.
+        $lastcomponent = null;
+        $approvedcollection = new contextlist_collection($foruser->id);
+
+        $rs = $DB->get_recordset_sql($sql, [$request->get('id'), contextlist_context::STATUS_APPROVED]);
+        foreach ($rs as $record) {
+            // If we encounter a new component, and we've built up contexts for the last, then add the approved_contextlist for the
+            // last (the one we've just finished with) and reset the context array for the next one.
+            if ($lastcomponent != $record->component) {
+                if (!empty($contexts)) {
+                    $approvedcollection->add_contextlist(new approved_contextlist($foruser, $lastcomponent, $contexts));
+                }
+                $contexts = [];
+            }
+            $contexts[] = $record->contextid;
+            $lastcomponent = $record->component;
+        }
+        $rs->close();
+
+        // The data for the last component contextlist won't have been written yet, so write it now.
+        if (!empty($contexts)) {
+            $approvedcollection->add_contextlist(new approved_contextlist($foruser, $lastcomponent, $contexts));
+        }
+
+        return $approvedcollection;
+    }
 }
diff --git a/admin/tool/dataprivacy/classes/contextlist.php b/admin/tool/dataprivacy/classes/contextlist.php
new file mode 100644 (file)
index 0000000..ef25c11
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Contains the contextlist persistent.
+ *
+ * @package    tool_dataprivacy
+ * @copyright  2018 Jake Dallimore <jrhdallimore@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_dataprivacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core\persistent;
+
+/**
+ * The contextlist persistent.
+ *
+ * @copyright  2018 Jake Dallimore <jrhdallimore@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class contextlist extends persistent {
+
+    /** The table name this persistent object maps to. */
+    const TABLE = 'tool_dataprivacy_contextlist';
+
+    /**
+     * Return the definition of the properties of this model.
+     *
+     * @return array
+     */
+    protected static function define_properties() {
+        return [
+            'component' => [
+                'type' => PARAM_TEXT
+            ]
+        ];
+    }
+
+    /**
+     * Create a new contextlist persistent from an instance of \core_privacy\local\request\contextlist.
+     *
+     * @param \core_privacy\local\request\contextlist $contextlist the core privacy contextlist.
+     * @return contextlist a contextlist persistent.
+     */
+    public static function from_contextlist(\core_privacy\local\request\contextlist $contextlist) : contextlist {
+        $contextlistpersistent = new contextlist();
+        return $contextlistpersistent->set('component', $contextlist->get_component());
+    }
+}
diff --git a/admin/tool/dataprivacy/classes/contextlist_context.php b/admin/tool/dataprivacy/classes/contextlist_context.php
new file mode 100644 (file)
index 0000000..0f4ca9f
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Contains the contextlist_context persistent.
+ *
+ * @package    tool_dataprivacy
+ * @copyright  2018 Jake Dallimore <jrhdallimore@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_dataprivacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core\persistent;
+
+/**
+ * The contextlist_context persistent.
+ *
+ * @copyright  2018 Jake Dallimore <jrhdallimore@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class contextlist_context extends persistent {
+
+    /** The table name this persistent object maps to. */
+    const TABLE = 'tool_dataprivacy_ctxlst_ctx';
+
+    /** This context is pending approval. */
+    const STATUS_PENDING = 0;
+
+    /** This context has been approved. */
+    const STATUS_APPROVED = 1;
+
+    /** This context has been rejected. */
+    const STATUS_REJECTED = 2;
+
+    /**
+     * Return the definition of the properties of this model.
+     *
+     * @return array
+     */
+    protected static function define_properties() {
+        return [
+            'contextid' => [
+                'type' => PARAM_INT
+            ],
+            'contextlistid' => [
+                'type' => PARAM_INT
+            ],
+            'status' => [
+                'choices' => [
+                    self::STATUS_PENDING,
+                    self::STATUS_APPROVED,
+                    self::STATUS_REJECTED,
+                ],
+                'default' => self::STATUS_PENDING,
+                'type' => PARAM_INT
+            ]
+        ];
+    }
+}
diff --git a/admin/tool/dataprivacy/classes/request_contextlist.php b/admin/tool/dataprivacy/classes/request_contextlist.php
new file mode 100644 (file)
index 0000000..bd75742
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Contains the request_contextlist persistent.
+ *
+ * @package    tool_dataprivacy
+ * @copyright  2018 Jake Dallimore <jrhdallimore@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_dataprivacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core\persistent;
+
+/**
+ * The request_contextlist persistent.
+ *
+ * @copyright  2018 Jake Dallimore <jrhdallimore@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class request_contextlist extends persistent {
+
+    /** The table name this persistent object maps to. */
+    const TABLE = 'tool_dataprivacy_rqst_ctxlst';
+
+    /**
+     * Return the definition of the properties of this model.
+     *
+     * @return array
+     */
+    protected static function define_properties() {
+        return [
+            'requestid' => [
+                'type' => PARAM_INT
+            ],
+            'contextlistid' => [
+                'type' => PARAM_INT
+            ]
+        ];
+    }
+
+    /**
+     * Creates a new relation, but does not persist it.
+     *
+     * @param $requestid
+     * @param $contextlistid
+     * @return $this
+     * @throws \coding_exception
+     */
+    public static function create_relation($requestid, $contextlistid) {
+        $requestcontextlist = new request_contextlist();
+        return $requestcontextlist->set('requestid', $requestid)
+            ->set('contextlistid', $contextlistid);
+    }
+}
index e6baa04..4e62b74 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Adhoc task that processes a data request and prepares the user's metadata for review.
+ * Adhoc task that processes a data request and prepares the user's relevant contexts for review.
  *
  * @package    tool_dataprivacy
  * @copyright  2018 Jun Pataleta
@@ -28,12 +28,13 @@ use coding_exception;
 use core\task\adhoc_task;
 use moodle_exception;
 use tool_dataprivacy\api;
+use tool_dataprivacy\contextlist_context;
 use tool_dataprivacy\data_request;
 
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Class that processes a data request and prepares the user's metadata for review.
+ * Class that processes a data request and prepares the user's relevant contexts for review.
  *
  * Custom data accepted:
  * - requestid -> The ID of the data request to be processed.
@@ -70,14 +71,17 @@ class initiate_data_request_task extends adhoc_task {
         }
 
         // Update the status of this request as pre-processing.
-        mtrace('Generating user metadata...');
+        mtrace('Generating the contexts containing personal data for the user...');
         api::update_request_status($requestid, api::DATAREQUEST_STATUS_PREPROCESSING);
 
-        // TODO: Add code here to process the request and prepare the metadata to for review.
+        // Add the list of relevant contexts to the request, and mark all as pending approval.
+        $privacymanager = new \core_privacy\manager();
+        $contextlistcollection = $privacymanager->get_contexts_for_userid($datarequest->get('userid'));
+        api::add_request_contexts_with_status($contextlistcollection, $requestid, contextlist_context::STATUS_PENDING);
 
-        // When the preparation of the metadata finishes, update the request status to awaiting approval.
+        // When the preparation of the contexts finishes, update the request status to awaiting approval.
         api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
-        mtrace('User metadata generation complete...');
+        mtrace('Context generation complete...');
 
         // Get the list of the site Data Protection Officers.
         $dpos = api::get_site_dpos();
index 19435a2..1b5a60f 100644 (file)
@@ -82,15 +82,13 @@ class process_data_request_task extends adhoc_task {
         api::update_request_status($requestid, api::DATAREQUEST_STATUS_PROCESSING);
 
         if ($request->type == api::DATAREQUEST_TYPE_EXPORT) {
-            // TODO: Update this code to retrieve the approved_contextlist properly.
+            // Get the collection of approved_contextlist objects needed for core_privacy data export.
+            $approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent);
+
+            // Export the data.
             $manager = new \core_privacy\manager();
-            $contextcollection = $manager->get_contexts_for_userid($foruser->id);
-            $approvedcollection = new \core_privacy\local\request\contextlist_collection($foruser->id);
-            foreach ($contextcollection as $contextlist) {
-                $approvedcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist($foruser,
-                        $contextlist->get_component(), $contextlist->get_contextids()));
-            }
-            $exportedcontent = $manager->export_user_data($approvedcollection);
+            $exportedcontent = $manager->export_user_data($approvedclcollection);
+
             $fs = get_file_storage();
             $filerecord = new \stdClass;
             $filerecord->component = 'tool_dataprivacy';
@@ -106,15 +104,12 @@ class process_data_request_task extends adhoc_task {
             $thing = $fs->create_file_from_pathname($filerecord, $exportedcontent);
 
         } else if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
-            // TODO: Update this code to retrieve the approved_contextlist properly.
+            // Get the collection of approved_contextlist objects needed for core_privacy data deletion.
+            $approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent);
+
+            // Delete the data
             $manager = new \core_privacy\manager();
-            $contextcollection = $manager->get_contexts_for_userid($foruser->id);
-            $approvedcollection = new \core_privacy\local\request\contextlist_collection($foruser->id);
-            foreach ($contextcollection as $contextlist) {
-                $approvedcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist($foruser,
-                        $contextlist->get_component(), $contextlist->get_contextids()));
-            }
-            $manager->delete_user_data($approvedcollection);
+            $manager->delete_user_data($approvedclcollection);
         }
 
         // When the preparation of the metadata finishes, update the request status to awaiting approval.
@@ -150,7 +145,8 @@ class process_data_request_task extends adhoc_task {
                 // Message to the recipient.
                 $messagetextdata['message'] = get_string('resultdownloadready', 'tool_dataprivacy', $SITE->fullname);
                 // Prepare download link.
-                $downloadurl = new moodle_url('#'); // TODO: Replace with the proper download URL.
+                $downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $thing->get_itemid(),
+                    $thing->get_filepath(), $thing->get_filename(), true);
                 $downloadlink = new action_link($downloadurl, get_string('download', 'tool_dataprivacy'));
                 $messagetextdata['downloadlink'] = $downloadlink->export_for_template($output);
                 break;
index be5e889..90abc24 100644 (file)
         <KEY NAME="contextid" TYPE="foreign-unique" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
       </KEYS>
     </TABLE>
+    <TABLE NAME="tool_dataprivacy_contextlist" COMMENT="List of contexts for a component">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="component" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Frankenstyle component name"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+    </TABLE>
+    <TABLE NAME="tool_dataprivacy_ctxlst_ctx" COMMENT="A contextlist context item">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="contextlistid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="status" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Approval status of the context item"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="contextlistid" TYPE="foreign" FIELDS="contextlistid" REFTABLE="tool_dataprivacy_contextlist" REFFIELDS="id" COMMENT="Reference to the contextlist containing this context item"/>
+      </KEYS>
+    </TABLE>
+    <TABLE NAME="tool_dataprivacy_rqst_ctxlst" COMMENT="Association table joining requests and contextlists">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="requestid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="contextlistid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="requestid" TYPE="foreign" FIELDS="requestid" REFTABLE="tool_dataprivacy_request" REFFIELDS="id" COMMENT="Reference to the request"/>
+        <KEY NAME="contextlistid" TYPE="foreign" FIELDS="contextlistid" REFTABLE="tool_dataprivacy_contextlist" REFFIELDS="id" COMMENT="Reference to the contextlist"/>
+        <KEY NAME="request_contextlist" TYPE="unique" FIELDS="requestid, contextlistid" COMMENT="Uniqueness constraint on request and contextlist"/>
+      </KEYS>
+    </TABLE>
   </TABLES>
 </XMLDB>
index 0db8244..3c66485 100644 (file)
@@ -185,7 +185,29 @@ function tool_dataprivacy_output_fragment_contextlevel_form($args) {
  * @return bool Returns false if we don't find a file.
  */
 function tool_dataprivacy_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) {
+    global $USER;
+
     if ($context->contextlevel == CONTEXT_USER) {
+        // Make sure the user is logged in.
+        require_login(null, false);
+
+        // Validate the user downloading this archive.
+        $usercontext = context_user::instance($USER->id);
+        // The user downloading this is not the user the archive has been prepared for. Check if it's the requester (e.g. parent).
+        if ($usercontext->instanceid !== $context->instanceid) {
+            // Get the data request ID. This should be the first element of the $args array.
+            $itemid = $args[0];
+            // Fetch the data request object. An invalid ID will throw an exception.
+            $datarequest = new \tool_dataprivacy\data_request($itemid);
+
+            // Check if the user is the requester and has the capability to make data requests for the target user.
+            $candownloadforuser = has_capability('tool/dataprivacy:makedatarequestsforchildren', $context);
+            if ($USER->id != $datarequest->get('requestedby') || !$candownloadforuser) {
+                return false;
+            }
+        }
+
+        // All good. Serve the exported data.
         $fs = get_file_storage();
         $relativepath = implode('/', $args);
         $fullpath = "/$context->id/tool_dataprivacy/$filearea/$relativepath";
index 6f00e12..531e394 100644 (file)
@@ -71,7 +71,7 @@
             </span>
             </div>
             {{#canreview}}
-                <a href="{{reviewurl}}" class="btn btn-default">{{#str}}reviewdata, tool_dataprivacy{{/str}}</a>
+                <!--a href="{{reviewurl}}" class="btn btn-default">{{#str}}reviewdata, tool_dataprivacy{{/str}}</a-->
             {{/canreview}}
         </div>
     </div>