Passed
Push — master ( ee45cd...8aa101 )
by Timo
21:51
created

StatisticsWriterProcessor::applyIpV6Mask()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 5
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
crap 2
1
<?php
2
namespace ApacheSolrForTypo3\Solr\Domain\Search\Statistics;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2009-2015 Ingo Renner <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 3 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use ApacheSolrForTypo3\Solr\Domain\Search\Query\Query;
28
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResultSet;
29
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResultSetProcessor;
30
use ApacheSolrForTypo3\Solr\HtmlContentExtractor;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
33
34
/**
35
 * Writes statistics after searches have been conducted.
36
 *
37
 * @author Ingo Renner <[email protected]>
38
 * @author Dimitri Ebert <[email protected]>
39
 * @author Timo Hund <[email protected]>
40
 */
41
class StatisticsWriterProcessor implements SearchResultSetProcessor
42
{
43
    /**
44
     * @var StatisticsRepository
45
     */
46
    protected $statisticsRepository;
47
48
    /**
49
     * @param StatisticsRepository $statisticsRepository
50
     */
51 32
    public function __construct(StatisticsRepository $statisticsRepository = null)
52
    {
53 32
        $this->statisticsRepository = isset($statisticsRepository) ? $statisticsRepository : GeneralUtility::makeInstance(StatisticsRepository::class);
54 32
    }
55
56
    /**
57
     * @param SearchResultSet $resultSet
58
     * @return SearchResultSet
59
     */
60 32
    public function process(SearchResultSet $resultSet) {
61 32
        $searchRequest = $resultSet->getUsedSearchRequest();
62 32
        $response = $resultSet->getResponse();
63 32
        $configuration = $searchRequest->getContextTypoScriptConfiguration();
64 32
        $keywords = $this->getProcessedKeywords($resultSet->getUsedQuery(), $configuration->getSearchFrequentSearchesUseLowercaseKeywords());
65
66 32
        if (empty($keywords)) {
67
            // do not track empty queries
68 4
            return $resultSet;
69
        }
70
71 28
        $filters = $searchRequest->getActiveFacets();
72 28
        $sorting = $this->sanitizeString($searchRequest->getSorting());
73 28
        $page = (int)$searchRequest->getPage();
74 28
        $ipMaskLength = (int)$configuration->getStatisticsAnonymizeIP();
75
76 28
        $TSFE = $this->getTSFE();
77
        $statisticData = [
78 28
            'pid' => $TSFE->id,
79 28
            'root_pid' => $TSFE->tmpl->rootLine[0]['uid'],
80 28
            'tstamp' => $this->getTime(),
81 28
            'language' => $TSFE->sys_language_uid,
82 28
            'num_found' => isset($response->response->numFound) ? (int)$response->response->numFound : 0,
83 28
            'suggestions_shown' => is_object($response->spellcheck->suggestions) ? (int)get_object_vars($response->spellcheck->suggestions) : 0,
84 28
            'time_total' => isset($response->debug->timing->time) ? $response->debug->timing->time : 0,
85 28
            'time_preparation' => isset($response->debug->timing->prepare->time) ? $response->debug->timing->prepare->time : 0,
86 28
            'time_processing' => isset($response->debug->timing->process->time) ? $response->debug->timing->process->time : 0,
87 28
            'feuser_id' => (int)$TSFE->fe_user->user['uid'],
88 28
            'cookie' => $TSFE->fe_user->id,
89 28
            'ip' => $this->applyIpMask((string)$this->getUserIp(), $ipMaskLength),
90 28
            'page' => (int)$page,
91 28
            'keywords' => $keywords,
92 28
            'filters' => serialize($filters),
93 28
            'sorting' => $sorting,
94 28
            'parameters' => serialize($response->responseHeader->params)
95
        ];
96
97 28
        $this->statisticsRepository->saveStatisticsRecord($statisticData);
98
99 28
        return $resultSet;
100
    }
101
102
    /**
103
     * @param Query $query
104
     * @param boolean $lowerCaseQuery
105
     * @return string
106
     */
107 32
    protected function getProcessedKeywords(Query $query, $lowerCaseQuery = false)
108
    {
109 32
        $keywords = $query->getQueryStringContainer()->getKeywords();
110 32
        $keywords = $this->sanitizeString($keywords);
111 32
        if ($lowerCaseQuery) {
112
            $keywords = mb_strtolower($keywords);
113
        }
114
115 32
        return $keywords;
116
    }
117
118
    /**
119
     * Sanitizes a string
120
     *
121
     * @param $string String to sanitize
122
     * @return string Sanitized string
123
     */
124 32
    protected function sanitizeString($string)
125
    {
126
        // clean content
127 32
        $string = HtmlContentExtractor::cleanContent($string);
128 32
        $string = html_entity_decode($string, ENT_QUOTES, 'UTF-8');
129 32
        $string = filter_var(strip_tags($string), FILTER_SANITIZE_STRING); // after entity decoding we might have tags again
130 32
        $string = trim($string);
131
132 32
        return $string;
133
    }
134
135
    /**
136
     * Internal function to mask portions of the visitor IP address
137
     *
138
     * @param string $ip IP address in network address format
139
     * @param int $maskLength Number of octets to reset
140
     * @return string
141
     */
142 28
    protected function applyIpMask(string $ip, int $maskLength): string
143
    {
144 28
        if (empty($ip) || $maskLength === 0) {
145 28
            return $ip;
146
        }
147
148
        // IPv4 or mapped IPv4 in IPv6
149
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
150
            return $this->applyIpV4Mask($ip, $maskLength);
151
        }
152
153
        return $this->applyIpV6Mask($ip, $maskLength);
154
    }
155
156
    /**
157
     * Apply a mask filter on the ip v4 address.
158
     *
159
     * @param string $ip
160
     * @param int $maskLength
161
     * @return string
162
     */
163
    protected function applyIpV4Mask($ip, $maskLength)
164
    {
165
        $i = strlen($ip);
166
        if ($maskLength > $i) {
167
            $maskLength = $i;
168
        }
169
170
        while ($maskLength-- > 0) {
171
            $ip[--$i] = chr(0);
172
        }
173
        return (string)$ip;
174
    }
175
176
    /**
177
     * Apply a mask filter on the ip v6 address.
178
     *
179
     * @param string $ip
180
     * @param int $maskLength
181
     * @return int
182
     */
183
    protected function applyIpV6Mask($ip, $maskLength):int
184
    {
185
        $masks = ['ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'ffff:ffff:ffff:ffff::', 'ffff:ffff:ffff:0000::', 'ffff:ff00:0000:0000::'];
186
        $packedAddress = inet_pton($masks[$maskLength]);
187
        $binaryString = pack('a16', $packedAddress);
188
        return (string)($ip & $binaryString);
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise & or did you mean &&?
Loading history...
Bug Best Practice introduced by
The expression return (string)$ip & $binaryString returns the type string which is incompatible with the type-hinted return integer.
Loading history...
189
    }
190
191
    /**
192
     * @return TypoScriptFrontendController
193
     */
194 27
    protected function getTSFE()
195
    {
196 27
        return $GLOBALS['TSFE'];
197
    }
198
199
    /**
200
     * @return string
201
     */
202 27
    protected function getUserIp()
203
    {
204 27
        return GeneralUtility::getIndpEnv('REMOTE_ADDR');
205
    }
206
207
    /**
208
     * @return mixed
209
     */
210 27
    protected function getTime()
211
    {
212 27
        return $GLOBALS['EXEC_TIME'];
213
    }
214
}
215