_binary From d0bc426ea85502a265d074c878d5ec6faa5408ad Mon Sep 17 00:00:00 2001
From: Pragya Rana <pragya.rana9@gmail.com>
Date: Mon, 21 Nov 2016 12:06:44 -0800
Subject: [PATCH] Add differential privacy to number of views of each group's
 thread

* Fuzzifies the number of views of each thread of the group by using epsilon
  differential privacy according to Cynthia Dwork's paper on Differential Privacy
---
 src/configs/Config.php                         |  7 +++++-
 src/controllers/Controller.php                 | 33 ++++++++++++++++++++++++++
 src/controllers/components/SocialComponent.php | 11 +++++++++
 src/library/VersionFunctions.php               | 24 +++++++++++++++++++
 src/models/GroupModel.php                      | 15 +++++++++++-
 src/models/ImpressionModel.php                 |  5 ++--
 src/models/ProfileModel.php                    |  3 ++-
 7 files changed, 93 insertions(+), 5 deletions(-)

diff --git a/src/configs/Config.php b/src/configs/Config.php
index c83aebb..d286e63 100755
--- a/src/configs/Config.php
+++ b/src/configs/Config.php
@@ -112,7 +112,7 @@ function nsconddefine($constant, $value)
  * Version number for upgrade function
  * @var int
  */
-nsdefine('YIOOP_VERSION', 47);
+nsdefine('YIOOP_VERSION', 48);
 /**
  * Minimum Version fo Yioop for which keyword ad script
  * still works with this version
@@ -225,6 +225,11 @@ nsdefine('ONE_HOUR', 3600);
 nsdefine('ONE_MINUTE', 60);
 /** Number of seconds in a second */
 nsdefine('ONE_SECOND', 1);
+/** Value of epsilon */
+nsdefine('EPSILON', 0.01);
+/** Flag to turn on/off differential privacy */
+nsdefine('DIFFERENTIAL_PRIVACY', true);
+
 if (file_exists(BASE_DIR."/configs/LocalConfig.php")) {
     /** Include any locally specified defines (could use as an alternative
         way to set work directory) */
diff --git a/src/controllers/Controller.php b/src/controllers/Controller.php
index 9b8fd28..7f119bf 100755
--- a/src/controllers/Controller.php
+++ b/src/controllers/Controller.php
@@ -924,4 +924,37 @@ abstract class Controller
             }
         }
      }
+  /**
+     * Fuzzifies the data to achieve differential privacy
+     *
+     * @param int actual value
+     * @return int fuzzified value
+     */
+    public function addDifferentialPrivacy($actual_value, $level_privacy = 1)
+    {
+
+        $sigma = 1 / C\EPSILON;
+        $max_value = (2 * $actual_value) + 1;
+        /* Calculation by Integration
+         * f: exp(-1 * ((abs($actual_value - $x)) / $sigma))
+         * Since function consists of absolute value, break the integration into two to remove absolute from the function.
+         * First integral runs from 0 through $actual_value, and second integral runs from $actual_value through $max_value.
+         * Then after using substition rule, first and second integral range change, say [a,b] and [b,c]
+         */
+        $a = -1 * $actual_value;
+        $b = 0;
+        $c = -1 * ($max_value - $actual_value);
+        $integral_value = $sigma * (2 - exp($a / $sigma) - exp($c / $sigma));
+        $random = rand(0, $integral_value);
+        $p = 0;
+        for ($i = 0; $i < $max_value; $i++) {
+            $arg = -1 * (($actual_value - $i) / $sigma);
+            $p +=  exp($arg);
+            if ($p > $random) {
+                break;
+            }
+        }
+        $fuzzy_value = $i;
+        return $fuzzy_value;
+    }
 }
\ No newline at end of file
diff --git a/src/controllers/components/SocialComponent.php b/src/controllers/components/SocialComponent.php
index 2395675..576ec71 100644
--- a/src/controllers/components/SocialComponent.php
+++ b/src/controllers/components/SocialComponent.php
@@ -1525,6 +1525,17 @@ class SocialComponent extends Component implements CrawlConstants
             $user_id);
         foreach ($group_items as $item) {
             $page = $item;
+            if (C\DIFFERENTIAL_PRIVACY) {
+                // Recalculate fuzzy view only if NUM_VIEWS has been updated since last calculation
+                if (($page['NUM_VIEWS'] != $page['TMP_NUM_VIEWS'])) {
+                    // fuzzify the number of views to add privacy
+                    $fuzzy_view = $parent->addDifferentialPrivacy($page['NUM_VIEWS']);
+                    $group_model->updatePrivacyViews($page['ID'], $page['NUM_VIEWS'], $fuzzy_view);
+                    $page['NUM_VIEWS'] = $fuzzy_view;
+                } else {
+                    $page['NUM_VIEWS'] = $page['FUZZY_NUM_VIEWS'];
+                }
+            }
             $page['USER_ICON'] = $user_model->getUserIconUrl($page['USER_ID']);
             $page[self::TITLE] = $page['TITLE'];
             unset($page['TITLE']);
diff --git a/src/library/VersionFunctions.php b/src/library/VersionFunctions.php
index 5d30f03..43d112c 100644
--- a/src/library/VersionFunctions.php
+++ b/src/library/VersionFunctions.php
@@ -1305,3 +1305,27 @@ function upgradeDatabaseVersion47(&$db)
         ") UNIQUE, CALLBACK_URL VARCHAR(" . C\MAX_URL_LEN . "))");
     $role_model->addRole('Bot User');
 }
+/**
+ * Upgrades a Version 47 version of the Yioop! database to a Version 48 version
+ * @param object $db datasource to use to upgrade.
+ */
+function upgradeDatabaseVersion48(&$db)
+{
+    $db->execute("CREATE TABLE ITEM_IMPRESSION_SUMMARY_OLD(
+        USER_ID INTEGER, ITEM_ID INTEGER, ITEM_TYPE INTEGER,
+        UPDATE_PERIOD INTEGER,
+        UPDATE_TIMESTAMP NUMERIC(" . C\TIMESTAMP_LEN . "),
+        NUM_VIEWS INTEGER, PRIMARY KEY(USER_ID, ITEM_ID, ITEM_TYPE,
+        UPDATE_PERIOD, UPDATE_TIMESTAMP))");
+    D\DatasourceManager::copyTable("ITEM_IMPRESSION_SUMMARY", $db,
+        "ITEM_IMPRESSION_SUMMARY_OLD", $db);
+    $db->execute("DROP TABLE ITEM_IMPRESSION_SUMMARY");
+    $db->execute("CREATE TABLE ITEM_IMPRESSION_SUMMARY(
+        USER_ID INTEGER, ITEM_ID INTEGER, ITEM_TYPE INTEGER,
+        UPDATE_PERIOD INTEGER,
+        UPDATE_TIMESTAMP NUMERIC(" . C\TIMESTAMP_LEN . "),
+        NUM_VIEWS INTEGER, FUZZY_NUM_VIEWS INTEGER DEFAULT -1, TMP_NUM_VIEWS INTEGER DEFAULT -1, PRIMARY KEY(USER_ID, ITEM_ID, ITEM_TYPE,
+        UPDATE_PERIOD, UPDATE_TIMESTAMP))");
+    $db->execute("INSERT INTO ITEM_IMPRESSION_SUMMARY (USER_ID, ITEM_ID, ITEM_TYPE, UPDATE_PERIOD, UPDATE_TIMESTAMP, NUM_VIEWS)
+        SELECT USER_ID, ITEM_ID, ITEM_TYPE, UPDATE_PERIOD, UPDATE_TIMESTAMP, NUM_VIEWS FROM ITEM_IMPRESSION_SUMMARY_OLD");
+}
diff --git a/src/models/GroupModel.php b/src/models/GroupModel.php
index 5293844..9cd255c 100644
--- a/src/models/GroupModel.php
+++ b/src/models/GroupModel.php
@@ -806,7 +806,8 @@ class GroupModel extends Model implements MediaConstants
                 I.DESCRIPTION AS DESCRIPTION,
                 I.USER_ID AS USER_ID, II.USER_ID AS LAST_POSTER_ID,
                 U.USER_NAME AS USER_NAME, P.USER_NAME AS LAST_POSTER,
-                IIS.NUM_VIEWS AS NUM_VIEWS";
+                IIS.NUM_VIEWS AS NUM_VIEWS, IIS.FUZZY_NUM_VIEWS AS FUZZY_NUM_VIEWS,
+                IIS.TMP_NUM_VIEWS AS TMP_NUM_VIEWS";
             $sub_select = "SELECT DISTINCT MIN(GI.ID) AS ID,
                 MAX(GI.ID) AS LAST_ID,
                 COUNT(DISTINCT GI.ID) AS NUM_POSTS, GI.PARENT_ID AS PARENT_ID,
@@ -861,6 +862,18 @@ class GroupModel extends Model implements MediaConstants
         return $groups;
     }
     /**
+     * Update privacy_views in item_impression_summary table
+     *
+     * @param int $item_id id of the item
+     * @param int $num_views number of views
+     */
+    public function updatePrivacyViews($item_id, $num_views, $fuzzy_num_views)
+    {
+        $sql = "UPDATE ITEM_IMPRESSION_SUMMARY SET FUZZY_NUM_VIEWS=?, TMP_NUM_VIEWS=?
+            WHERE ITEM_ID=? AND ITEM_TYPE=" .C\THREAD_IMPRESSION;
+        $this->db->execute($sql, [$fuzzy_num_views, $num_views, $item_id]);
+    }
+    /**
      * Gets the number of group feed items visible to a user with $user_id
      * and which match the supplied search criteria found in $search_array
      *
diff --git a/src/models/ImpressionModel.php b/src/models/ImpressionModel.php
index 67406ac..e38cdba 100644
--- a/src/models/ImpressionModel.php
+++ b/src/models/ImpressionModel.php
@@ -166,7 +166,8 @@ class ImpressionModel extends Model
             $sql = "DELETE FROM ITEM_IMPRESSION_SUMMARY
                 WHERE UPDATE_PERIOD = ? AND UPDATE_TIMESTAMP = ?";
             $db->execute($sql, [$period, $timestamp]);
-            $sql = "INSERT INTO ITEM_IMPRESSION_SUMMARY
+            $sql = "INSERT INTO ITEM_IMPRESSION_SUMMARY (USER_ID, ITEM_ID, ITEM_TYPE,
+                UPDATE_PERIOD, UPDATE_TIMESTAMP, NUM_VIEWS)
                 SELECT USER_ID, ITEM_ID, ITEM_TYPE, ? AS UPDATE_PERIOD,
                 ? AS UPDATE_TIMESTAMP, $sum AS NUM_VIEWS
                 FROM  $table
@@ -353,7 +354,7 @@ class ImpressionModel extends Model
     public static function initWithDb($user_id, $item_id, $type_id, $db)
     {
         $sql = "INSERT INTO ITEM_IMPRESSION_SUMMARY VALUES
-            (?, ?, ?, ".C\FOREVER.", 0, 0)";
+            (?, ?, ?, ".C\FOREVER.", 0, 0, -1, -1)";
         $db->execute($sql, [C\PUBLIC_USER_ID, $item_id, $type_id]);
         self::addWithDb($user_id, $item_id, $type_id, $db);
     }
diff --git a/src/models/ProfileModel.php b/src/models/ProfileModel.php
index 96dab05..503cc03 100755
--- a/src/models/ProfileModel.php
+++ b/src/models/ProfileModel.php
@@ -220,7 +220,8 @@ class ProfileModel extends Model
                 USER_ID INTEGER, ITEM_ID INTEGER, ITEM_TYPE INTEGER,
                 UPDATE_PERIOD INTEGER,
                 UPDATE_TIMESTAMP NUMERIC(" . C\TIMESTAMP_LEN . "),
-                NUM_VIEWS INTEGER, PRIMARY KEY(USER_ID, ITEM_ID, ITEM_TYPE,
+                NUM_VIEWS INTEGER, FUZZY_NUM_VIEWS INTEGER DEFAULT -1, TMP_NUM_VIEWS INTEGER DEFAULT -1,
+                PRIMARY KEY(USER_ID, ITEM_ID, ITEM_TYPE,
                 UPDATE_PERIOD, UPDATE_TIMESTAMP))",
             "LOCALE" => "CREATE TABLE LOCALE(LOCALE_ID $serial PRIMARY KEY
                 $auto_increment, LOCALE_TAG VARCHAR(" . C\NAME_LEN . "),
-- 
2.8.4 (Apple Git-73)

