{"action":"created","comment":{"url":"https://api.github.com/repos/vanilla/addons/pulls/comments/34191334","id":34191334,"diff_hunk":"@@ -0,0 +1,432 @@\n+<?php\n+\n+class GeoipQuery {\n+\n+    public  $localCache = [];\n+    private $localCacheMax = 100;\n+\n+    private static $blockTableName       = 'geoip_block';\n+    private static $locationTableName    = 'geoip_location';\n+\n+    public  $geoExpTime = 604800; // 604800 = 1 week\n+    const   cachePre    = 'GeoIP-Plugin_';\n+\n+\n+    /**\n+     * Query MySQL database for info on one or many given IPs.\n+     *\n+     * @param $input array|string IP address or array of IP addresses we want to query for.\n+     * @param bool $caching Enables and disables caching.\n+     * @return array|bool Returns array of data on success, false on failure.\n+     */\n+    public function get($input, $caching=true) {\n+        if (empty($input)) {\n+            return false;\n+        }\n+\n+        // Make sure we always have input as array:\n+        if (!is_array($input)) {\n+            $input = [$input];\n+        }\n+\n+        // Get Cached Records:\n+        if ($caching == true) {\n+\n+            $cached = $this->getCache($input);\n+\n+            // Remove Cached IPs from queryList:\n+            $queryList = $this->getQueryList($input, array_keys($cached));\n+\n+        } else {\n+            $queryList = $input;\n+        }\n+\n+        if (!empty($queryList)) {\n+\n+            // Get SQL Query:\n+            $sql      = $this->getSQL($queryList);\n+            $results  = GDN::Database()->Connection()->query($sql)->fetchAll(PDO::FETCH_ASSOC);\n+            $results  = $this->assocResultsToIps($results, $queryList); // @todo with associate results to IPs for result, not other way around.\n+\n+            // Merge Query Results to Cached Results:\n+            $output = array_merge($cached, $results);\n+\n+        } else {\n+            $output = $cached;\n+        }\n+\n+        // Store to cache:\n+        if ($caching == true) {\n+            $this->setResponseToCache($output);\n+        }\n+\n+        $this->addLocalCache($output);\n+\n+        return $output;\n+    }\n+\n+    /**\n+     * Generates SQL query to load GeoIP info.\n+     *\n+     * @param $input Array list of IPs\n+     * @return bool|string\n+     */\n+    private function getSQL($input) {\n+        if (empty($input)) {\n+            return false;\n+        }\n+        if (!is_array($input)) {\n+            $input = [$input];\n+        }\n+\n+        $sql  = \"SELECT\\n\";\n+        $sql .= \"  B.network,\\n\";\n+        $sql .= \"  L.*,\\n\";\n+        $sql .= \"  B.latitude,\\n\";\n+        $sql .= \"  B.longitude\\n\";\n+        $sql .= \"FROM \".self::$blockTableName.\" AS B\\n\";\n+        $sql .= \"  LEFT JOIN \".self::$locationTableName.\" AS L ON B.geoname_id=L.geoname_id\\n\";\n+        $sql .= \"WHERE\\n\";\n+        foreach ($input AS $i => $ip) {\n+            $sql .= ($i==0) ? '   ' : 'OR ';\n+            $sql .= \"inet_aton('{$ip}') BETWEEN B.start AND B.end\\n\";\n+        }\n+        $sql .= \";\\n\";\n+        // echo \"<pre>SQL:\\n{$sql}</pre>\\n\";\n+\n+        return $sql;\n+    }\n+\n+\n+    /**\n+     * Checks given if given IP is part of given subnet range.\n+     *\n+     * @param $ip Given IP to be verified.\n+     * @param $range Subnet to be verified agains.\n+     * @return bool Returns true if IP is in subnet. False if not.\n+     */\n+    public  static function isInSubnet($ip, $range) {\n+        if (!self::isIP($ip)) {\n+            return false;\n+        }\n+\n+        list ($subnet, $bits) = explode('/', $range);\n+\n+        $ip      = ip2long($ip);\n+        $subnet  = ip2long($subnet);\n+        $mask    = -1 << (32 - $bits);\n+        $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned\n+\n+        return ($ip & $mask) == $subnet;\n+    }\n+\n+    /**\n+     * Verifies that given IP is an actual IP.\n+     *\n+     * @param $ip IP being verified\n+     * @param int $version IP version we are verifying\n+     * @return bool Returns true if given IP is a proper IP. False if not.\n+     */\n+    public  static function isIP($ip, $version=4) {\n+        if (empty($ip)) {\n+            return false;\n+        } else if (!in_array($version,[4,6])) {\n+            return false;\n+        }\n+\n+        if (strlen($ip) < 7 OR strlen($ip) > 15) {\n+            return false;\n+        }\n+\n+        if ($version==4) {\n+            $parts = explode('.', $ip);\n+            if (empty($parts) OR count($parts) != 4) {\n+                return false;\n+            }\n+\n+            foreach ($parts AS $part) {\n+                if ($part > 255 OR $part < 0) {\n+                    return false;\n+                }\n+            }\n+        }\n+        else {\n+            trigger_error(\"Only IPv4 supported in \".__METHOD__.\"()\");\n+            return false;\n+        }\n+\n+        return true;\n+    }\n+\n+    /**\n+     * Add IP information to local cache.\n+     *\n+     * Merges given input with this->localCache.\n+     *\n+     * @todo Verify size of local cache is smaller than this->localCacheMax.\n+     *\n+     * @param $input array Data being added to localCache.\n+     * @return bool Returns true/false upon success.\n+     */\n+    private function addLocalCache($input) {\n+        if (empty($input) || !is_array($input)) {\n+            return false;\n+        }\n+\n+        $this->localCache = array_merge($this->localCache, $input);\n+\n+        return $this->localCache;\n+    }\n+\n+    /**\n+     * Takes result data array of GeoIP results and adds it's associated\n+     * ip from the IP list.\n+     *\n+     * NOTE: Since some IPs in IP list might be part of same network. We therefore\n+     * need to build the output of this method by adding data to ip list and not\n+     * the other way around.\n+     *\n+     * @param $data Array of GeoIP result data.\n+     * @param $ips Array of IPs used to produce dataset\n+     * @return array Returns original data array with associated IP from ips list.\n+     */\n+    private function assocResultsToIps($data, $ips) {\n+\n+        $output = [];\n+        foreach ($ips AS $ip) {\n+            if (empty($ip) || !$this->isIP($ip)) {\n+                continue;\n+            }\n+\n+            $targetItem = false;\n+            foreach ($data AS $item) {","path":"plugins/GeoIP/class.geoip.query.php","position":202,"original_position":202,"commit_id":"a56060a18e46f5abf120f1e752ce3d24d38ef53c","original_commit_id":"a56060a18e46f5abf120f1e752ce3d24d38ef53c","user":{"login":"kaecyra","id":248212,"avatar_url":"https://avatars.githubusercontent.com/u/248212?v=3","gravatar_id":"","url":"https://api.github.com/users/kaecyra","html_url":"https://github.com/kaecyra","followers_url":"https://api.github.com/users/kaecyra/followers","following_url":"https://api.github.com/users/kaecyra/following{/other_user}","gists_url":"https://api.github.com/users/kaecyra/gists{/gist_id}","starred_url":"https://api.github.com/users/kaecyra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaecyra/subscriptions","organizations_url":"https://api.github.com/users/kaecyra/orgs","repos_url":"https://api.github.com/users/kaecyra/repos","events_url":"https://api.github.com/users/kaecyra/events{/privacy}","received_events_url":"https://api.github.com/users/kaecyra/received_events","type":"User","site_admin":false},"body":"Caps","created_at":"2015-07-08T19:58:15Z","updated_at":"2015-07-08T19:58:15Z","html_url":"https://github.com/vanilla/addons/pull/231#discussion_r34191334","pull_request_url":"https://api.github.com/repos/vanilla/addons/pulls/231","_links":{"self":{"href":"https://api.github.com/repos/vanilla/addons/pulls/comments/34191334"},"html":{"href":"https://github.com/vanilla/addons/pull/231#discussion_r34191334"},"pull_request":{"href":"https://api.github.com/repos/vanilla/addons/pulls/231"}}},"pull_request":{"url":"https://api.github.com/repos/vanilla/addons/pulls/231","id":39511274,"html_url":"https://github.com/vanilla/addons/pull/231","diff_url":"https://github.com/vanilla/addons/pull/231.diff","patch_url":"https://github.com/vanilla/addons/pull/231.patch","issue_url":"https://api.github.com/repos/vanilla/addons/issues/231","number":231,"state":"open","locked":false,"title":"Feature/geoip","user":{"login":"dericdd","id":223064,"avatar_url":"https://avatars.githubusercontent.com/u/223064?v=3","gravatar_id":"","url":"https://api.github.com/users/dericdd","html_url":"https://github.com/dericdd","followers_url":"https://api.github.com/users/dericdd/followers","following_url":"https://api.github.com/users/dericdd/following{/other_user}","gists_url":"https://api.github.com/users/dericdd/gists{/gist_id}","starred_url":"https://api.github.com/users/dericdd/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dericdd/subscriptions","organizations_url":"https://api.github.com/users/dericdd/orgs","repos_url":"https://api.github.com/users/dericdd/repos","events_url":"https://api.github.com/users/dericdd/events{/privacy}","received_events_url":"https://api.github.com/users/dericdd/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-07-08T19:02:09Z","updated_at":"2015-07-08T19:58:15Z","closed_at":null,"merged_at":null,"merge_commit_sha":"8b7f1b51f8db4a82d421f3827a5b819ae148eb77","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/vanilla/addons/pulls/231/commits","review_comments_url":"https://api.github.com/repos/vanilla/addons/pulls/231/comments","review_comment_url":"https://api.github.com/repos/vanilla/addons/pulls/comments{/number}","comments_url":"https://api.github.com/repos/vanilla/addons/issues/231/comments","statuses_url":"https://api.github.com/repos/vanilla/addons/statuses/a56060a18e46f5abf120f1e752ce3d24d38ef53c","head":{"label":"vanilla:feature/geoip","ref":"feature/geoip","sha":"a56060a18e46f5abf120f1e752ce3d24d38ef53c","user":{"login":"vanilla","id":7443552,"avatar_url":"https://avatars.githubusercontent.com/u/7443552?v=3","gravatar_id":"","url":"https://api.github.com/users/vanilla","html_url":"https://github.com/vanilla","followers_url":"https://api.github.com/users/vanilla/followers","following_url":"https://api.github.com/users/vanilla/following{/other_user}","gists_url":"https://api.github.com/users/vanilla/gists{/gist_id}","starred_url":"https://api.github.com/users/vanilla/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vanilla/subscriptions","organizations_url":"https://api.github.com/users/vanilla/orgs","repos_url":"https://api.github.com/users/vanilla/repos","events_url":"https://api.github.com/users/vanilla/events{/privacy}","received_events_url":"https://api.github.com/users/vanilla/received_events","type":"Organization","site_admin":false},"repo":{"id":633372,"name":"addons","full_name":"vanilla/addons","owner":{"login":"vanilla","id":7443552,"avatar_url":"https://avatars.githubusercontent.com/u/7443552?v=3","gravatar_id":"","url":"https://api.github.com/users/vanilla","html_url":"https://github.com/vanilla","followers_url":"https://api.github.com/users/vanilla/followers","following_url":"https://api.github.com/users/vanilla/following{/other_user}","gists_url":"https://api.github.com/users/vanilla/gists{/gist_id}","starred_url":"https://api.github.com/users/vanilla/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vanilla/subscriptions","organizations_url":"https://api.github.com/users/vanilla/orgs","repos_url":"https://api.github.com/users/vanilla/repos","events_url":"https://api.github.com/users/vanilla/events{/privacy}","received_events_url":"https://api.github.com/users/vanilla/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/vanilla/addons","description":"Official addons for Vanilla Forums","fork":false,"url":"https://api.github.com/repos/vanilla/addons","forks_url":"https://api.github.com/repos/vanilla/addons/forks","keys_url":"https://api.github.com/repos/vanilla/addons/keys{/key_id}","collaborators_url":"https://api.github.com/repos/vanilla/addons/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/vanilla/addons/teams","hooks_url":"https://api.github.com/repos/vanilla/addons/hooks","issue_events_url":"https://api.github.com/repos/vanilla/addons/issues/events{/number}","events_url":"https://api.github.com/repos/vanilla/addons/events","assignees_url":"https://api.github.com/repos/vanilla/addons/assignees{/user}","branches_url":"https://api.github.com/repos/vanilla/addons/branches{/branch}","tags_url":"https://api.github.com/repos/vanilla/addons/tags","blobs_url":"https://api.github.com/repos/vanilla/addons/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/vanilla/addons/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/vanilla/addons/git/refs{/sha}","trees_url":"https://api.github.com/repos/vanilla/addons/git/trees{/sha}","statuses_url":"https://api.github.com/repos/vanilla/addons/statuses/{sha}","languages_url":"https://api.github.com/repos/vanilla/addons/languages","stargazers_url":"https://api.github.com/repos/vanilla/addons/stargazers","contributors_url":"https://api.github.com/repos/vanilla/addons/contributors","subscribers_url":"https://api.github.com/repos/vanilla/addons/subscribers","subscription_url":"https://api.github.com/repos/vanilla/addons/subscription","commits_url":"https://api.github.com/repos/vanilla/addons/commits{/sha}","git_commits_url":"https://api.github.com/repos/vanilla/addons/git/commits{/sha}","comments_url":"https://api.github.com/repos/vanilla/addons/comments{/number}","issue_comment_url":"https://api.github.com/repos/vanilla/addons/issues/comments{/number}","contents_url":"https://api.github.com/repos/vanilla/addons/contents/{+path}","compare_url":"https://api.github.com/repos/vanilla/addons/compare/{base}...{head}","merges_url":"https://api.github.com/repos/vanilla/addons/merges","archive_url":"https://api.github.com/repos/vanilla/addons/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/vanilla/addons/downloads","issues_url":"https://api.github.com/repos/vanilla/addons/issues{/number}","pulls_url":"https://api.github.com/repos/vanilla/addons/pulls{/number}","milestones_url":"https://api.github.com/repos/vanilla/addons/milestones{/number}","notifications_url":"https://api.github.com/repos/vanilla/addons/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/vanilla/addons/labels{/name}","releases_url":"https://api.github.com/repos/vanilla/addons/releases{/id}","created_at":"2010-04-28T14:04:03Z","updated_at":"2015-07-08T08:13:02Z","pushed_at":"2015-07-08T19:36:19Z","git_url":"git://github.com/vanilla/addons.git","ssh_url":"git@github.com:vanilla/addons.git","clone_url":"https://github.com/vanilla/addons.git","svn_url":"https://github.com/vanilla/addons","homepage":"http://vanillaforums.org","size":12312,"stargazers_count":90,"watchers_count":90,"language":"PHP","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":163,"mirror_url":null,"open_issues_count":37,"forks":163,"open_issues":37,"watchers":90,"default_branch":"master"}},"base":{"label":"vanilla:master","ref":"master","sha":"7cd648026a166632b9680a732554c25157de487c","user":{"login":"vanilla","id":7443552,"avatar_url":"https://avatars.githubusercontent.com/u/7443552?v=3","gravatar_id":"","url":"https://api.github.com/users/vanilla","html_url":"https://github.com/vanilla","followers_url":"https://api.github.com/users/vanilla/followers","following_url":"https://api.github.com/users/vanilla/following{/other_user}","gists_url":"https://api.github.com/users/vanilla/gists{/gist_id}","starred_url":"https://api.github.com/users/vanilla/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vanilla/subscriptions","organizations_url":"https://api.github.com/users/vanilla/orgs","repos_url":"https://api.github.com/users/vanilla/repos","events_url":"https://api.github.com/users/vanilla/events{/privacy}","received_events_url":"https://api.github.com/users/vanilla/received_events","type":"Organization","site_admin":false},"repo":{"id":633372,"name":"addons","full_name":"vanilla/addons","owner":{"login":"vanilla","id":7443552,"avatar_url":"https://avatars.githubusercontent.com/u/7443552?v=3","gravatar_id":"","url":"https://api.github.com/users/vanilla","html_url":"https://github.com/vanilla","followers_url":"https://api.github.com/users/vanilla/followers","following_url":"https://api.github.com/users/vanilla/following{/other_user}","gists_url":"https://api.github.com/users/vanilla/gists{/gist_id}","starred_url":"https://api.github.com/users/vanilla/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vanilla/subscriptions","organizations_url":"https://api.github.com/users/vanilla/orgs","repos_url":"https://api.github.com/users/vanilla/repos","events_url":"https://api.github.com/users/vanilla/events{/privacy}","received_events_url":"https://api.github.com/users/vanilla/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/vanilla/addons","description":"Official addons for Vanilla Forums","fork":false,"url":"https://api.github.com/repos/vanilla/addons","forks_url":"https://api.github.com/repos/vanilla/addons/forks","keys_url":"https://api.github.com/repos/vanilla/addons/keys{/key_id}","collaborators_url":"https://api.github.com/repos/vanilla/addons/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/vanilla/addons/teams","hooks_url":"https://api.github.com/repos/vanilla/addons/hooks","issue_events_url":"https://api.github.com/repos/vanilla/addons/issues/events{/number}","events_url":"https://api.github.com/repos/vanilla/addons/events","assignees_url":"https://api.github.com/repos/vanilla/addons/assignees{/user}","branches_url":"https://api.github.com/repos/vanilla/addons/branches{/branch}","tags_url":"https://api.github.com/repos/vanilla/addons/tags","blobs_url":"https://api.github.com/repos/vanilla/addons/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/vanilla/addons/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/vanilla/addons/git/refs{/sha}","trees_url":"https://api.github.com/repos/vanilla/addons/git/trees{/sha}","statuses_url":"https://api.github.com/repos/vanilla/addons/statuses/{sha}","languages_url":"https://api.github.com/repos/vanilla/addons/languages","stargazers_url":"https://api.github.com/repos/vanilla/addons/stargazers","contributors_url":"https://api.github.com/repos/vanilla/addons/contributors","subscribers_url":"https://api.github.com/repos/vanilla/addons/subscribers","subscription_url":"https://api.github.com/repos/vanilla/addons/subscription","commits_url":"https://api.github.com/repos/vanilla/addons/commits{/sha}","git_commits_url":"https://api.github.com/repos/vanilla/addons/git/commits{/sha}","comments_url":"https://api.github.com/repos/vanilla/addons/comments{/number}","issue_comment_url":"https://api.github.com/repos/vanilla/addons/issues/comments{/number}","contents_url":"https://api.github.com/repos/vanilla/addons/contents/{+path}","compare_url":"https://api.github.com/repos/vanilla/addons/compare/{base}...{head}","merges_url":"https://api.github.com/repos/vanilla/addons/merges","archive_url":"https://api.github.com/repos/vanilla/addons/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/vanilla/addons/downloads","issues_url":"https://api.github.com/repos/vanilla/addons/issues{/number}","pulls_url":"https://api.github.com/repos/vanilla/addons/pulls{/number}","milestones_url":"https://api.github.com/repos/vanilla/addons/milestones{/number}","notifications_url":"https://api.github.com/repos/vanilla/addons/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/vanilla/addons/labels{/name}","releases_url":"https://api.github.com/repos/vanilla/addons/releases{/id}","created_at":"2010-04-28T14:04:03Z","updated_at":"2015-07-08T08:13:02Z","pushed_at":"2015-07-08T19:36:19Z","git_url":"git://github.com/vanilla/addons.git","ssh_url":"git@github.com:vanilla/addons.git","clone_url":"https://github.com/vanilla/addons.git","svn_url":"https://github.com/vanilla/addons","homepage":"http://vanillaforums.org","size":12312,"stargazers_count":90,"watchers_count":90,"language":"PHP","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":163,"mirror_url":null,"open_issues_count":37,"forks":163,"open_issues":37,"watchers":90,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/vanilla/addons/pulls/231"},"html":{"href":"https://github.com/vanilla/addons/pull/231"},"issue":{"href":"https://api.github.com/repos/vanilla/addons/issues/231"},"comments":{"href":"https://api.github.com/repos/vanilla/addons/issues/231/comments"},"review_comments":{"href":"https://api.github.com/repos/vanilla/addons/pulls/231/comments"},"review_comment":{"href":"https://api.github.com/repos/vanilla/addons/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/vanilla/addons/pulls/231/commits"},"statuses":{"href":"https://api.github.com/repos/vanilla/addons/statuses/a56060a18e46f5abf120f1e752ce3d24d38ef53c"}}}}