PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.2
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / vendor / prefixed / symfony / http-kernel / Profiler / FileProfilerStorage.php
matomo / app / vendor / prefixed / symfony / http-kernel / Profiler Last commit date
FileProfilerStorage.php 1 year ago Profile.php 2 years ago Profiler.php 1 year ago ProfilerStorageInterface.php 2 years ago
FileProfilerStorage.php
242 lines
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11 namespace Matomo\Dependencies\Symfony\Component\HttpKernel\Profiler;
12
13 /**
14 * Storage for profiler using files.
15 *
16 * @author Alexandre Salomé <alexandre.salome@gmail.com>
17 */
18 class FileProfilerStorage implements ProfilerStorageInterface
19 {
20 /**
21 * Folder where profiler data are stored.
22 *
23 * @var string
24 */
25 private $folder;
26 /**
27 * Constructs the file storage using a "dsn-like" path.
28 *
29 * Example : "file:/path/to/the/storage/folder"
30 *
31 * @throws \RuntimeException
32 */
33 public function __construct(string $dsn)
34 {
35 if (!str_starts_with($dsn, 'file:')) {
36 throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use FileStorage with an invalid dsn "%s". The expected format is "file:/path/to/the/storage/folder".', $dsn));
37 }
38 $this->folder = substr($dsn, 5);
39 if (!is_dir($this->folder) && \false === @mkdir($this->folder, 0777, \true) && !is_dir($this->folder)) {
40 throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder));
41 }
42 }
43 /**
44 * {@inheritdoc}
45 */
46 public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?int $start = null, ?int $end = null, ?string $statusCode = null) : array
47 {
48 $file = $this->getIndexFilename();
49 if (!file_exists($file)) {
50 return [];
51 }
52 $file = fopen($file, 'r');
53 fseek($file, 0, \SEEK_END);
54 $result = [];
55 while (\count($result) < $limit && ($line = $this->readLineFromFile($file))) {
56 $values = str_getcsv($line, ',', '"', '\\');
57 if (7 !== \count($values)) {
58 // skip invalid lines
59 continue;
60 }
61 [$csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode] = $values;
62 $csvTime = (int) $csvTime;
63 if ($ip && !str_contains($csvIp, $ip) || $url && !str_contains($csvUrl, $url) || $method && !str_contains($csvMethod, $method) || $statusCode && !str_contains($csvStatusCode, $statusCode)) {
64 continue;
65 }
66 if (!empty($start) && $csvTime < $start) {
67 continue;
68 }
69 if (!empty($end) && $csvTime > $end) {
70 continue;
71 }
72 $result[$csvToken] = ['token' => $csvToken, 'ip' => $csvIp, 'method' => $csvMethod, 'url' => $csvUrl, 'time' => $csvTime, 'parent' => $csvParent, 'status_code' => $csvStatusCode];
73 }
74 fclose($file);
75 return array_values($result);
76 }
77 /**
78 * {@inheritdoc}
79 */
80 public function purge()
81 {
82 $flags = \FilesystemIterator::SKIP_DOTS;
83 $iterator = new \RecursiveDirectoryIterator($this->folder, $flags);
84 $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
85 foreach ($iterator as $file) {
86 if (is_file($file)) {
87 unlink($file);
88 } else {
89 rmdir($file);
90 }
91 }
92 }
93 /**
94 * {@inheritdoc}
95 */
96 public function read(string $token) : ?Profile
97 {
98 return $this->doRead($token);
99 }
100 /**
101 * {@inheritdoc}
102 *
103 * @throws \RuntimeException
104 */
105 public function write(Profile $profile) : bool
106 {
107 $file = $this->getFilename($profile->getToken());
108 $profileIndexed = is_file($file);
109 if (!$profileIndexed) {
110 // Create directory
111 $dir = \dirname($file);
112 if (!is_dir($dir) && \false === @mkdir($dir, 0777, \true) && !is_dir($dir)) {
113 throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir));
114 }
115 }
116 $profileToken = $profile->getToken();
117 // when there are errors in sub-requests, the parent and/or children tokens
118 // may equal the profile token, resulting in infinite loops
119 $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null;
120 $childrenToken = array_filter(array_map(function (Profile $p) use($profileToken) {
121 return $profileToken !== $p->getToken() ? $p->getToken() : null;
122 }, $profile->getChildren()));
123 // Store profile
124 $data = ['token' => $profileToken, 'parent' => $parentToken, 'children' => $childrenToken, 'data' => $profile->getCollectors(), 'ip' => $profile->getIp(), 'method' => $profile->getMethod(), 'url' => $profile->getUrl(), 'time' => $profile->getTime(), 'status_code' => $profile->getStatusCode()];
125 $data = serialize($data);
126 if (\function_exists('gzencode')) {
127 $data = gzencode($data, 3);
128 }
129 if (\false === file_put_contents($file, $data, \LOCK_EX)) {
130 return \false;
131 }
132 if (!$profileIndexed) {
133 // Add to index
134 if (\false === ($file = fopen($this->getIndexFilename(), 'a'))) {
135 return \false;
136 }
137 fputcsv($file, [$profile->getToken(), $profile->getIp(), $profile->getMethod(), $profile->getUrl(), $profile->getTime(), $profile->getParentToken(), $profile->getStatusCode()], ',', '"', '\\');
138 fclose($file);
139 }
140 return \true;
141 }
142 /**
143 * Gets filename to store data, associated to the token.
144 *
145 * @return string
146 */
147 protected function getFilename(string $token)
148 {
149 // Uses 4 last characters, because first are mostly the same.
150 $folderA = substr($token, -2, 2);
151 $folderB = substr($token, -4, 2);
152 return $this->folder . '/' . $folderA . '/' . $folderB . '/' . $token;
153 }
154 /**
155 * Gets the index filename.
156 *
157 * @return string
158 */
159 protected function getIndexFilename()
160 {
161 return $this->folder . '/index.csv';
162 }
163 /**
164 * Reads a line in the file, backward.
165 *
166 * This function automatically skips the empty lines and do not include the line return in result value.
167 *
168 * @param resource $file The file resource, with the pointer placed at the end of the line to read
169 *
170 * @return mixed
171 */
172 protected function readLineFromFile($file)
173 {
174 $line = '';
175 $position = ftell($file);
176 if (0 === $position) {
177 return null;
178 }
179 while (\true) {
180 $chunkSize = min($position, 1024);
181 $position -= $chunkSize;
182 fseek($file, $position);
183 if (0 === $chunkSize) {
184 // bof reached
185 break;
186 }
187 $buffer = fread($file, $chunkSize);
188 if (\false === ($upTo = strrpos($buffer, "\n"))) {
189 $line = $buffer . $line;
190 continue;
191 }
192 $position += $upTo;
193 $line = substr($buffer, $upTo + 1) . $line;
194 fseek($file, max(0, $position), \SEEK_SET);
195 if ('' !== $line) {
196 break;
197 }
198 }
199 return '' === $line ? null : $line;
200 }
201 protected function createProfileFromData(string $token, array $data, ?Profile $parent = null)
202 {
203 $profile = new Profile($token);
204 $profile->setIp($data['ip']);
205 $profile->setMethod($data['method']);
206 $profile->setUrl($data['url']);
207 $profile->setTime($data['time']);
208 $profile->setStatusCode($data['status_code']);
209 $profile->setCollectors($data['data']);
210 if (!$parent && $data['parent']) {
211 $parent = $this->read($data['parent']);
212 }
213 if ($parent) {
214 $profile->setParent($parent);
215 }
216 foreach ($data['children'] as $token) {
217 if (null !== ($childProfile = $this->doRead($token, $profile))) {
218 $profile->addChild($childProfile);
219 }
220 }
221 return $profile;
222 }
223 private function doRead($token, ?Profile $profile = null) : ?Profile
224 {
225 if (!$token || !file_exists($file = $this->getFilename($token))) {
226 return null;
227 }
228 $h = fopen($file, 'r');
229 flock($h, \LOCK_SH);
230 $data = stream_get_contents($h);
231 flock($h, \LOCK_UN);
232 fclose($h);
233 if (\function_exists('gzdecode')) {
234 $data = @gzdecode($data) ?: $data;
235 }
236 if (!($data = unserialize($data))) {
237 return null;
238 }
239 return $this->createProfileFromData($token, $data, $profile);
240 }
241 }
242