PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.10.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.10.0
4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / Filesystem / AbstractFileObject.php
wp-staging / Framework / Filesystem Last commit date
Filters 2 weeks ago Scanning 5 years ago AbstractFileObject.php 6 days ago AbstractFilesystemScanner.php 2 weeks ago DebugLogReader.php 6 days ago DirectoryListing.php 6 days ago DirectorySize.php 1 month ago DiskWriteCheck.php 7 months ago FileObject.php 1 year ago Filesystem.php 8 months ago FilesystemExceptions.php 5 years ago FilesystemScanner.php 1 week ago FilesystemScannerDto.php 1 month ago FilterableDirectoryIterator.php 1 year ago LegacyFileRulesTrait.php 1 month ago LogCleanup.php 7 months ago LogFiles.php 1 year ago MissingFileException.php 3 years ago OPcache.php 7 months ago PartIdentifier.php 10 months ago PathChecker.php 2 years ago PathIdentifier.php 1 week ago Permissions.php 1 month ago WpUploadsFolderSymlinker.php 1 month ago
AbstractFileObject.php
336 lines
1 <?php
2
3 namespace WPStaging\Framework\Filesystem;
4
5 /**
6 * This is an abstract class of WP Staging implementation of SplFileObject class
7 * Which doesn't depend upon core plugin, so it can be used with wpstg-restorer standalone tool
8 * Don't import core PHP namespaces as it is not required for standalone tool as it will be bundled into single file
9 * When building in standalone tool, this class will be renamed to FileObject and changed to final class
10 * Also this class should not use any wp core functions or classes.
11 */
12 abstract class AbstractFileObject extends \SplFileObject
13 {
14 const MODE_READ = 'rb'; // read only, binary
15 const MODE_WRITE = 'wb'; // write only, binary
16 const MODE_APPEND = 'ab'; // append with create, binary
17 const MODE_APPEND_AND_READ = 'ab+'; // append with read and create if not exists, binary
18 const MODE_WRITE_SAFE = 'xb'; // write if exists E_WARNING & return false, binary
19 const MODE_WRITE_UNSAFE = 'cb'; // append, if exists cursor to top, binary
20
21 /** @var int */
22 protected $totalLines = null;
23
24 /** @var bool */
25 protected $fgetsUsedOnKey0 = false;
26
27 /** @var bool */
28 protected $fseekUsed = false;
29
30 public function __construct(string $fullPath, string $openMode = self::MODE_READ)
31 {
32 try {
33 parent::__construct($fullPath, $openMode);
34 } catch (\Throwable $e) {
35 throw $e;
36 }
37 }
38
39 /**
40 * The line count of a file being restored does not change between requests, so a
41 * caller that already knows it can supply it here and skip the scan in totalLines(),
42 * which otherwise walks the whole file on every request.
43 *
44 * @param int $totalLines
45 * @return void
46 */
47 public function setTotalLines(int $totalLines)
48 {
49 $this->totalLines = $totalLines;
50 }
51
52 /** @return int */
53 public function totalLines(bool $useParent = false): int
54 {
55 if ($this->totalLines !== null) {
56 return $this->totalLines;
57 }
58
59 if ($useParent) {
60 $currentKey = $this->keyUseParent();
61 $this->seekUseParent(PHP_INT_MAX);
62 $this->totalLines = $this->keyUseParent();
63
64 if ($currentKey < 0) {
65 $currentKey = 0;
66 }
67
68 $this->seekUseParent($currentKey);
69 } else {
70 $currentKey = $this->key();
71 if ($currentKey < 0) {
72 $currentKey = 0;
73 }
74
75 $this->seek(PHP_INT_MAX);
76 $this->totalLines = $this->key();
77 $this->seek($currentKey);
78 }
79
80 // Asking for the total means seeking to PHP_INT_MAX and reading key(), so key() has to
81 // report how many records the file holds rather than the index of the last one -
82 // BufferedCache::readBottomLine() hands it to LimitIterator as a count. On the PHP
83 // versions below, the line counter stops on the last record instead of one past it, so
84 // it comes back one short and the difference is added back here. A file ending in a
85 // newline yields a final empty record, and that record counts.
86 //
87 // One of the SplFileObject iterator desync workarounds this class has carried since PHP
88 // 8.0.1. php/php-src#21679 fixes them in PHP 8.6; a runtime probe that bypasses the
89 // workarounds on fixed builds landed in #5085 and was reverted in #5116 until 8.6 ships.
90 if ($this->totalLines > 0) {
91 if (PHP_VERSION === '8.2.0RC3' || version_compare(PHP_VERSION, '8.2.0', '>=')) {
92 $this->totalLines += 1;
93 }
94
95 if (version_compare(PHP_VERSION, '8.1', '>') && version_compare(PHP_VERSION, '8.1.11', '<=')) {
96 $this->totalLines += 1;
97 }
98 }
99
100 return $this->totalLines;
101 }
102
103 /**
104 * Override SplFileObject::seek()
105 * Alternative function for SplFileObject::seek() that behaves identical in all PHP Versions.
106 *
107 * There was a major change in PHP 8.0.1 where after using `SplFileObject::seek($line)`, the first subsequent
108 * call to `SplFileObject::fgets()` does not increase the line pointer anymore as it did in earlier version since PHP 5.x
109 * @see https://bugs.php.net/bug.php?id=81551
110 *
111 * Note: This will remove READ_AHEAD flag while execution to deliver reliable and identical results as READ_AHEAD tells
112 * SplFileObject to read on next() and rewind() too which our custom seek() makes use of.
113 * This would disturb this seek() implementation and would lead to fatal errors if 'cpu load' setting is 'medium' or 'high'
114 *
115 *
116 * @param int $offset The zero-based line number to seek to.
117 * @return void
118 * @throws Exception
119 */
120 #[\ReturnTypeWillChange]
121 public function seek($offset)
122 {
123 if ($offset < 0) {
124 throw new \Exception("Can't seek file: " . $this->getPathname() . " to negative offset: $offset");
125 }
126
127 $this->fseekUsed = false;
128 $this->fgetsUsedOnKey0 = false;
129 if ($offset === 0 || version_compare(PHP_VERSION, '8.0.1', '<')) {
130 parent::seek($offset);
131 return;
132 }
133
134 $offset -= 1;
135
136 if ($this->totalLines !== null && $offset >= $this->totalLines) {
137 $offset += 1;
138 }
139
140 $originalFlags = $this->getFlags();
141 $newFlags = $originalFlags & ~self::READ_AHEAD;
142 $this->setFlags($newFlags);
143
144 parent::seek($offset);
145
146 if ($this->eof()) {
147 $this->current();
148 $this->totalLines = $this->key();
149 return;
150 }
151
152 $this->current();
153 $this->next();
154 $this->current();
155
156 $this->setFlags($originalFlags);
157 }
158
159 /**
160 * SplFileObject::fgets() is not consistent after SplFileObject::fseek() between php 5.x/7.x and php 8.0.1.
161 * We could either make fgets consistent after SplFileObject::seek() or SplFileObject::fseek()
162 * This implementation makes it consistent after SplFileObject::seek across all PHP versions up to 8.0.1.
163 * Use readAndMoveNext() instead if you want to achieve consistent behavior of SplFileObject::fgets after SplFileObject::fseek.
164 *
165 * @deprecated 4.2.13 Use readAndMoveNext instead as it is hard to make fgets against multiple php version after seek(0)
166 *
167 * @return string
168 */
169 public function fgets(): string
170 {
171 if ($this->key() === 0 || version_compare(PHP_VERSION, '8.0.1', '<')) {
172 $this->fgetsUsedOnKey0 = true;
173 return parent::fgets();
174 }
175
176 $originalFlags = $this->getFlags();
177 $newFlags = $originalFlags & ~self::READ_AHEAD;
178 $this->setFlags($newFlags);
179
180 $line = $this->current();
181 $this->next();
182
183 if (version_compare(PHP_VERSION, '8.0.19', '<')) {
184 $line = $this->current();
185 }
186
187 if (version_compare(PHP_VERSION, '8.1', '>') && version_compare(PHP_VERSION, '8.1.6', '<')) {
188 $line = $this->current();
189 }
190
191 if (!$this->fseekUsed) {
192 $line = $this->current();
193 }
194
195 $this->setFlags($originalFlags);
196 return $line;
197 }
198
199 /** @return int */
200 #[\ReturnTypeWillChange]
201 public function key(): int
202 {
203 if (!$this->fgetsUsedOnKey0 || version_compare(PHP_VERSION, '8.0.19', '<')) {
204 return parent::key();
205 }
206
207 if (version_compare(PHP_VERSION, '8.1', '>') && version_compare(PHP_VERSION, '8.1.6', '<')) {
208 return parent::key();
209 }
210
211 return parent::key() - 1;
212 }
213
214 /**
215 * Seek to a position
216 *
217 * @param int $offset The value to start from added to the $whence
218 * @param int $whence values are:
219 * SEEK_SET - Set position equal to offset bytes.
220 * SEEK_CUR - Set position to current location plus offset.
221 * SEEK_END - Set position to end-of-file plus offset.
222 * @return int
223 */
224 #[\ReturnTypeWillChange]
225 public function fseek($offset, $whence = SEEK_SET): int
226 {
227 if (version_compare(PHP_VERSION, '8.0.19', '<')) {
228 return parent::fseek($offset, $whence);
229 }
230
231 if (version_compare(PHP_VERSION, '8.1', '>') && version_compare(PHP_VERSION, '8.1.6', '<')) {
232 return parent::fseek($offset, $whence);
233 }
234
235 // After calling parent::fseek() and $this->fgets() two or three times it starts to act different on PHP >= 8.0.19, PHP >= 8.1.6 and PHP >= 8.2.
236 // Calling it three times helps to write a consistent fseek() for the above mentioned PHP versions.
237 for ($i = 0; $i < 3; $i++) {
238 parent::fseek(0);
239 $this->fgets();
240 }
241
242 $this->fseekUsed = true;
243 return parent::fseek((int)$offset, $whence);
244 }
245
246 /**
247 * SplFileObject::fgets() is not consistent after SplFileObject::fseek() between php 5.x/7.x and php 8.0.1.
248 * Use this method instead if you want to achieve consistent behavior of SplFileObject::fgets after SplFileObject::fseek across all PHP versions up to PHP 8.0.1.
249 * READ_AHEAD flag will not have any affect on this method. It's disabled.
250 *
251 * @var bool $useFgets default false. Setting this to true will use fgets on PHP < 8.0.1
252 *
253 * @return string
254 */
255 public function readAndMoveNext(bool $useFgets = false): string
256 {
257 if ($useFgets && version_compare(PHP_VERSION, '8.0.1', '<')) {
258 return parent::fgets();
259 }
260
261 $originalFlags = $this->getFlags();
262 $newFlags = $originalFlags & ~self::READ_AHEAD;
263 $this->setFlags($newFlags);
264
265 $line = $this->current();
266 $this->next();
267
268 $this->setFlags($originalFlags);
269 return $line;
270 }
271
272 /** @return bool */
273 public function isSqlFile(): bool
274 {
275 return $this->getExtension() === 'sql';
276 }
277
278 public function fgetsUseParent(): string
279 {
280 return parent::fgets();
281 }
282
283 /** @return int */
284 public function keyUseParent(): int
285 {
286 return parent::key();
287 }
288
289 /** @return void */
290 public function seekUseParent(int $offset)
291 {
292 parent::seek($offset);
293 }
294
295 /**
296 * Manage file locking.
297 * On WinOS a file is always locked when using SplFileObject. So, do nothing here and just return true on WinOS.
298 * @param int $operation
299 * @param int|null $wouldBlock
300 * @return bool
301 *
302 * Note: Adding type for $operation throw error in IDE
303 */
304 #[\ReturnTypeWillChange]
305 public function flock($operation, &$wouldBlock = null): bool
306 {
307 if ($this->isWindowsOs()) {
308 return true;
309 }
310
311 $parentMethodFlock = 'parent::flock';
312 if (version_compare(PHP_VERSION, '8.2', '>=')) {
313 // phpcs:ignore SlevomatCodingStandard.PHP.ForbiddenClasses.ForbiddenClass
314 $parentMethodFlock = \SplFileObject::class . '::flock';
315 }
316
317 if (!is_callable($parentMethodFlock)) {
318 return false;
319 }
320
321 return parent::flock($operation, $wouldBlock);
322 }
323
324 /**
325 * @return bool
326 */
327 protected function isWindowsOs(): bool
328 {
329 if (function_exists('wpstgIsWindowsOs')) {
330 return wpstgIsWindowsOs();
331 }
332
333 return false;
334 }
335 }
336