Passed
Branch main (7dbc49)
by Rafael
04:08
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
eloc 4
dl 0
loc 6
ccs 0
cts 5
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace ApacheSolrForTypo3\Solr\Domain\Search\Statistics;
17
18
use ApacheSolrForTypo3\Solr\Domain\Search\Query\Query;
19
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResultSet;
20
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResultSetProcessor;
21
use ApacheSolrForTypo3\Solr\Domain\Site\SiteRepository;
22
use ApacheSolrForTypo3\Solr\HtmlContentExtractor;
23
use ApacheSolrForTypo3\Solr\Util;
24
use function inet_pton;
25
use function pack;
26
use TYPO3\CMS\Core\Context\Context;
27
use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
30
31
/**
32
 * Writes statistics after searches have been conducted.
33
 *
34
 * @author Ingo Renner <[email protected]>
35
 * @author Dimitri Ebert <[email protected]>
36
 * @author Timo Hund <[email protected]>
37
 */
38
class StatisticsWriterProcessor implements SearchResultSetProcessor
39
{
40
    /**
41
     * @var StatisticsRepository
42
     */
43
    protected $statisticsRepository;
44
45
    /**
46
     * @var SiteRepository
47
     */
48
    protected $siteRepository;
49
50
    /**
51
     * @param StatisticsRepository|null $statisticsRepository
52
     * @param SiteRepository|null $siteRepository
53
     */
54 1
    public function __construct(
55
        StatisticsRepository $statisticsRepository = null,
56
        SiteRepository $siteRepository = null
57
    ) {
58 1
        $this->statisticsRepository = $statisticsRepository ?? GeneralUtility::makeInstance(StatisticsRepository::class);
59 1
        $this->siteRepository = $siteRepository ?? GeneralUtility::makeInstance(SiteRepository::class);
60
    }
61
62
    /**
63
     * @param SearchResultSet $resultSet
64
     * @return SearchResultSet
65
     * @throws AspectNotFoundException
66
     */
67 1
    public function process(SearchResultSet $resultSet): SearchResultSet
68
    {
69 1
        $searchRequest = $resultSet->getUsedSearchRequest();
70 1
        $response = $resultSet->getResponse();
71 1
        $configuration = $searchRequest->getContextTypoScriptConfiguration();
72 1
        $keywords = $this->getProcessedKeywords($resultSet->getUsedQuery(), $configuration->getSearchFrequentSearchesUseLowercaseKeywords());
0 ignored issues
show
Bug introduced by
It seems like $resultSet->getUsedQuery() can also be of type null; however, parameter $query of ApacheSolrForTypo3\Solr\...:getProcessedKeywords() does only seem to accept ApacheSolrForTypo3\Solr\Domain\Search\Query\Query, maybe add an additional type check? ( Ignorable by Annotation )

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

72
        $keywords = $this->getProcessedKeywords(/** @scrutinizer ignore-type */ $resultSet->getUsedQuery(), $configuration->getSearchFrequentSearchesUseLowercaseKeywords());
Loading history...
73
74 1
        if (empty($keywords)) {
75
            // do not track empty queries
76
            return $resultSet;
77
        }
78
79 1
        $filters = $searchRequest->getActiveFacets();
80 1
        $sorting = $this->sanitizeString($searchRequest->getSorting());
81 1
        $page = (int)$searchRequest->getPage();
82 1
        $ipMaskLength = $configuration->getStatisticsAnonymizeIP();
83
84 1
        $TSFE = $this->getTSFE();
85 1
        $root_pid = $this->siteRepository->getSiteByPageId($TSFE->id)->getRootPageId();
0 ignored issues
show
Bug introduced by
It seems like $TSFE->id can also be of type string; however, parameter $pageId of ApacheSolrForTypo3\Solr\...tory::getSiteByPageId() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

85
        $root_pid = $this->siteRepository->getSiteByPageId(/** @scrutinizer ignore-type */ $TSFE->id)->getRootPageId();
Loading history...
86 1
        $statisticData = [
87 1
            'pid' => $TSFE->id,
88
            'root_pid' => $root_pid,
89 1
            'tstamp' => $this->getTime(),
90 1
            'language' => Util::getLanguageUid(),
91
            // @extensionScannerIgnoreLine
92 1
            'num_found' => $resultSet->getAllResultCount(),
93 1
            'suggestions_shown' => is_object($response->spellcheck->suggestions ?? null) ? (int)get_object_vars($response->spellcheck->suggestions) : 0,
94
            // @extensionScannerIgnoreLine
95 1
            'time_total' => $response->debug->timing->time ?? 0,
96
            // @extensionScannerIgnoreLine
97 1
            'time_preparation' => $response->debug->timing->prepare->time ?? 0,
98
            // @extensionScannerIgnoreLine
99 1
            'time_processing' => $response->debug->timing->process->time ?? 0,
100 1
            'feuser_id' => isset($TSFE->fe_user->user) ? (int)$TSFE->fe_user->user['uid'] ?? 0 : 0,
101 1
            'cookie' => $TSFE->fe_user->id ?? '',
102 1
            'ip' => $this->applyIpMask($this->getUserIp(), $ipMaskLength),
103
            'page' => $page,
104
            'keywords' => $keywords,
105 1
            'filters' => serialize($filters),
106
            'sorting' => $sorting,
107 1
            'parameters' => isset($response->responseHeader->params) ? serialize($response->responseHeader->params) : '',
108
        ];
109
110 1
        $this->statisticsRepository->saveStatisticsRecord($statisticData);
111
112 1
        return $resultSet;
113
    }
114
115
    /**
116
     * @param Query $query
117
     * @param bool $lowerCaseQuery
118
     * @return string
119
     */
120 1
    protected function getProcessedKeywords(
121
        Query $query,
122
        bool $lowerCaseQuery = false
123
    ): string {
124 1
        $keywords = $query->getQuery();
125 1
        $keywords = $this->sanitizeString($keywords);
0 ignored issues
show
Bug introduced by
It seems like $keywords can also be of type null; however, parameter $string of ApacheSolrForTypo3\Solr\...essor::sanitizeString() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

125
        $keywords = $this->sanitizeString(/** @scrutinizer ignore-type */ $keywords);
Loading history...
126 1
        if ($lowerCaseQuery) {
127
            $keywords = mb_strtolower($keywords);
128
        }
129
130 1
        return $keywords;
131
    }
132
133
    /**
134
     * Sanitizes a string
135
     *
136
     * @param $string String to sanitize
137
     * @return string Sanitized string
138
     */
139 1
    protected function sanitizeString(string $string): string
140
    {
141
        // clean content
142 1
        $string = HtmlContentExtractor::cleanContent($string);
143 1
        $string = htmlspecialchars(strip_tags($string), ENT_QUOTES); // after entity decoding we might have tags again
144 1
        return trim($string);
145
    }
146
147
    /**
148
     * Internal function to mask portions of the visitor IP address
149
     *
150
     * @param string $ip IP address in network address format
151
     * @param int $maskLength Number of octets to reset
152
     * @return string
153
     */
154 1
    protected function applyIpMask(string $ip, int $maskLength): string
155
    {
156 1
        if (empty($ip) || $maskLength === 0) {
157 1
            return $ip;
158
        }
159
160
        // IPv4 or mapped IPv4 in IPv6
161
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
162
            return $this->applyIpV4Mask($ip, $maskLength);
163
        }
164
165
        return $this->applyIpV6Mask($ip, $maskLength);
166
    }
167
168
    /**
169
     * Apply a mask filter on the ip v4 address.
170
     *
171
     * @param string $ip
172
     * @param int $maskLength
173
     * @return string
174
     */
175
    protected function applyIpV4Mask(string $ip, int $maskLength): string
176
    {
177
        $i = strlen($ip);
178
        if ($maskLength > $i) {
179
            $maskLength = $i;
180
        }
181
182
        while ($maskLength-- > 0) {
183
            $ip[--$i] = chr(0);
184
        }
185
        return $ip;
186
    }
187
188
    /**
189
     * Apply a mask filter on the ip v6 address.
190
     *
191
     * @param string $ip
192
     * @param int $maskLength
193
     * @return string
194
     */
195
    protected function applyIpV6Mask(string $ip, int $maskLength): string
196
    {
197
        $masks = ['ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'ffff:ffff:ffff:ffff::', 'ffff:ffff:ffff:0000::', 'ffff:ff00:0000:0000::'];
198
        $packedAddress = inet_pton($masks[$maskLength]);
199
        $binaryString = pack('a16', $packedAddress);
200
        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...
201
    }
202
203
    /**
204
     * @return TypoScriptFrontendController
205
     */
206
    protected function getTSFE(): ?TypoScriptFrontendController
207
    {
208
        return $GLOBALS['TSFE'];
209
    }
210
211
    /**
212
     * @return string
213
     */
214
    protected function getUserIp(): string
215
    {
216
        return GeneralUtility::getIndpEnv('REMOTE_ADDR');
217
    }
218
219
    /**
220
     * @return mixed
221
     * @throws AspectNotFoundException
222
     */
223
    protected function getTime()
224
    {
225
        return GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('date', 'timestamp');
226
    }
227
}
228