Failed Conditions
Pull Request — release-11.2.x (#3343)
by Rafael
15:25
created

StatisticsWriterProcessor::applyIpV4Mask()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 11
ccs 0
cts 7
cp 0
rs 10
c 0
b 0
f 0
cc 3
nc 4
nop 2
crap 12
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 ApacheSolrForTypo3\Solr\Domain\Site\SiteRepository;
32
use ApacheSolrForTypo3\Solr\Util;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
35
36
/**
37
 * Writes statistics after searches have been conducted.
38
 *
39
 * @author Ingo Renner <[email protected]>
40
 * @author Dimitri Ebert <[email protected]>
41
 * @author Timo Hund <[email protected]>
42
 */
43
class StatisticsWriterProcessor implements SearchResultSetProcessor
44
{
45
    /**
46
     * @var StatisticsRepository
47
     */
48
    protected $statisticsRepository;
49
50
    /**
51
     * @var SiteRepository
52
     */
53
    protected $siteRepository;
54
55
    /**
56
     * @param StatisticsRepository $statisticsRepository
57
     * @param SiteRepository $siteRepository
58
     */
59 1
    public function __construct(StatisticsRepository $statisticsRepository = null, SiteRepository $siteRepository = null)
60
    {
61 1
        $this->statisticsRepository = $statisticsRepository ?? GeneralUtility::makeInstance(StatisticsRepository::class);
62 1
        $this->siteRepository = $siteRepository ?? GeneralUtility::makeInstance(SiteRepository::class);
63 1
    }
64
65
    /**
66
     * @param SearchResultSet $resultSet
67
     * @return SearchResultSet
68
     */
69 1
    public function process(SearchResultSet $resultSet) {
70 1
        $searchRequest = $resultSet->getUsedSearchRequest();
71 1
        $response = $resultSet->getResponse();
72 1
        $configuration = $searchRequest->getContextTypoScriptConfiguration();
73 1
        $keywords = $this->getProcessedKeywords($resultSet->getUsedQuery(), $configuration->getSearchFrequentSearchesUseLowercaseKeywords());
74
75 1
        if (empty($keywords)) {
76
            // do not track empty queries
77
            return $resultSet;
78
        }
79
80 1
        $filters = $searchRequest->getActiveFacets();
81 1
        $sorting = $this->sanitizeString($searchRequest->getSorting());
82 1
        $page = (int)$searchRequest->getPage();
83 1
        $ipMaskLength = (int)$configuration->getStatisticsAnonymizeIP();
84
85 1
        $TSFE = $this->getTSFE();
86 1
        $root_pid = $this->siteRepository->getSiteByPageId($TSFE->id)->getRootPageId();
0 ignored issues
show
Bug introduced by
$TSFE->id of type string is incompatible with the type integer expected by parameter $pageId of ApacheSolrForTypo3\Solr\...tory::getSiteByPageId(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

86
        $root_pid = $this->siteRepository->getSiteByPageId(/** @scrutinizer ignore-type */ $TSFE->id)->getRootPageId();
Loading history...
87
        $statisticData = [
88 1
            'pid' => $TSFE->id,
89 1
            'root_pid' => $root_pid,
90 1
            'tstamp' => $this->getTime(),
91 1
            'language' => Util::getLanguageUid(),
92
            // @extensionScannerIgnoreLine
93 1
            'num_found' => (int)$resultSet->getAllResultCount(),
94 1
            'suggestions_shown' => is_object($response->spellcheck->suggestions) ? (int)get_object_vars($response->spellcheck->suggestions) : 0,
95
            // @extensionScannerIgnoreLine
96 1
            'time_total' => isset($response->debug->timing->time) ? $response->debug->timing->time : 0,
97
            // @extensionScannerIgnoreLine
98 1
            'time_preparation' => isset($response->debug->timing->prepare->time) ? $response->debug->timing->prepare->time : 0,
99
            // @extensionScannerIgnoreLine
100 1
            'time_processing' => isset($response->debug->timing->process->time) ? $response->debug->timing->process->time : 0,
101 1
            'feuser_id' => (int)$TSFE->fe_user->user['uid'],
102 1
            'cookie' => $TSFE->fe_user->id ?? '',
103 1
            'ip' => $this->applyIpMask((string)$this->getUserIp(), $ipMaskLength),
104 1
            'page' => (int)$page,
105 1
            'keywords' => $keywords,
106 1
            'filters' => serialize($filters),
107 1
            'sorting' => $sorting,
108 1
            'parameters' => serialize($response->responseHeader->params)
109
        ];
110
111 1
        $this->statisticsRepository->saveStatisticsRecord($statisticData);
112
113 1
        return $resultSet;
114
    }
115
116
    /**
117
     * @param Query $query
118
     * @param boolean $lowerCaseQuery
119
     * @return string
120
     */
121 1
    protected function getProcessedKeywords(Query $query, $lowerCaseQuery = false)
122
    {
123 1
        $keywords = $query->getQuery();
124 1
        $keywords = $this->sanitizeString($keywords);
125
        // Ensure string does not exceed database field length
126 1
        $keywords = substr($keywords, 0, 128);
127 1
        if ($lowerCaseQuery) {
128
            $keywords = mb_strtolower($keywords);
129
        }
130
131 1
        return $keywords;
132
    }
133
134
    /**
135
     * Sanitizes a string
136
     *
137
     * @param $string String to sanitize
138
     * @return string Sanitized string
139
     */
140 1
    protected function sanitizeString($string)
141
    {
142
        // clean content
143 1
        $string = HtmlContentExtractor::cleanContent($string);
144 1
        $string = html_entity_decode($string, ENT_QUOTES, 'UTF-8');
145 1
        $string = filter_var(strip_tags($string), FILTER_SANITIZE_STRING); // after entity decoding we might have tags again
146 1
        $string = trim($string);
147
148 1
        return $string;
149
    }
150
151
    /**
152
     * Internal function to mask portions of the visitor IP address
153
     *
154
     * @param string $ip IP address in network address format
155
     * @param int $maskLength Number of octets to reset
156
     * @return string
157
     */
158 1
    protected function applyIpMask(string $ip, int $maskLength): string
159
    {
160 1
        if (empty($ip) || $maskLength === 0) {
161 1
            return $ip;
162
        }
163
164
        // IPv4 or mapped IPv4 in IPv6
165
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
166
            return $this->applyIpV4Mask($ip, $maskLength);
167
        }
168
169
        return $this->applyIpV6Mask($ip, $maskLength);
170
    }
171
172
    /**
173
     * Apply a mask filter on the ip v4 address.
174
     *
175
     * @param string $ip
176
     * @param int $maskLength
177
     * @return string
178
     */
179
    protected function applyIpV4Mask($ip, $maskLength)
180
    {
181
        $i = strlen($ip);
182
        if ($maskLength > $i) {
183
            $maskLength = $i;
184
        }
185
186
        while ($maskLength-- > 0) {
187
            $ip[--$i] = chr(0);
188
        }
189
        return (string)$ip;
190
    }
191
192
    /**
193
     * Apply a mask filter on the ip v6 address.
194
     *
195
     * @param string $ip
196
     * @param int $maskLength
197
     * @return string
198
     */
199
    protected function applyIpV6Mask($ip, $maskLength):string
200
    {
201
        $masks = ['ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'ffff:ffff:ffff:ffff::', 'ffff:ffff:ffff:0000::', 'ffff:ff00:0000:0000::'];
202
        $packedAddress = inet_pton($masks[$maskLength]);
203
        $binaryString = pack('a16', $packedAddress);
204
        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...
205
    }
206
207
    /**
208
     * @return TypoScriptFrontendController
209
     */
210
    protected function getTSFE()
211
    {
212
        return $GLOBALS['TSFE'];
213
    }
214
215
    /**
216
     * @return string
217
     */
218
    protected function getUserIp()
219
    {
220
        return GeneralUtility::getIndpEnv('REMOTE_ADDR');
221
    }
222
223
    /**
224
     * @return mixed
225
     */
226
    protected function getTime()
227
    {
228
        return $GLOBALS['EXEC_TIME'];
229
    }
230
}
231