Completed
Push — master ( f3a6aa...8848c4 )
by Naveen
01:08
created
src/modules/common/third-party/vendor/symfony/filesystem/Filesystem.php 1 patch
Indentation   +627 added lines, -627 removed lines patch added patch discarded remove patch
@@ -20,631 +20,631 @@
 block discarded – undo
20 20
  */
21 21
 class Filesystem {
22 22
 
23
-	private static $lastError;
24
-	/**
25
-	 * Copies a file.
26
-	 *
27
-	 * If the target file is older than the origin file, it's always overwritten.
28
-	 * If the target file is newer, it is overwritten only when the
29
-	 * $overwriteNewerFiles option is set to true.
30
-	 *
31
-	 * @param string $originFile          The original filename
32
-	 * @param string $targetFile          The target filename
33
-	 * @param bool   $overwriteNewerFiles If true, target files newer than origin files are overwritten
34
-	 *
35
-	 * @throws FileNotFoundException When originFile doesn't exist
36
-	 * @throws IOException           When copy fails
37
-	 */
38
-	public function copy( $originFile, $targetFile, $overwriteNewerFiles = \false ) {
39
-		$originIsLocal = \stream_is_local( $originFile ) || 0 === \stripos( $originFile, 'file://' );
40
-		if ( $originIsLocal && ! \is_file( $originFile ) ) {
41
-			throw new FileNotFoundException( \sprintf( 'Failed to copy "%s" because file does not exist.', $originFile ), 0, null, $originFile );
42
-		}
43
-		$this->mkdir( \dirname( $targetFile ) );
44
-		$doCopy = \true;
45
-		if ( ! $overwriteNewerFiles && null === \parse_url( $originFile, \PHP_URL_HOST ) && \is_file( $targetFile ) ) {
46
-			$doCopy = \filemtime( $originFile ) > \filemtime( $targetFile );
47
-		}
48
-		if ( $doCopy ) {
49
-			// https://bugs.php.net/64634
50
-			if ( \false === ( $source = @\fopen( $originFile, 'r' ) ) ) {
51
-				throw new IOException( \sprintf( 'Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile ), 0, null, $originFile );
52
-			}
53
-			// Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
54
-			if ( \false === ( $target = @\fopen( $targetFile, 'w', \false, \stream_context_create( array( 'ftp' => array( 'overwrite' => \true ) ) ) ) ) ) {
55
-				throw new IOException( \sprintf( 'Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile ), 0, null, $originFile );
56
-			}
57
-			$bytesCopied = \stream_copy_to_stream( $source, $target );
58
-			\fclose( $source );
59
-			\fclose( $target );
60
-			unset( $source, $target );
61
-			if ( ! \is_file( $targetFile ) ) {
62
-				throw new IOException( \sprintf( 'Failed to copy "%s" to "%s".', $originFile, $targetFile ), 0, null, $originFile );
63
-			}
64
-			if ( $originIsLocal ) {
65
-				// Like `cp`, preserve executable permission bits
66
-				@\chmod( $targetFile, \fileperms( $targetFile ) | \fileperms( $originFile ) & 0111 );
67
-				if ( $bytesCopied !== ( $bytesOrigin = \filesize( $originFile ) ) ) {
68
-					throw new IOException( \sprintf( 'Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin ), 0, null, $originFile );
69
-				}
70
-			}
71
-		}
72
-	}
73
-	/**
74
-	 * Creates a directory recursively.
75
-	 *
76
-	 * @param string|iterable $dirs The directory path
77
-	 * @param int             $mode The directory mode
78
-	 *
79
-	 * @throws IOException On any directory creation failure
80
-	 */
81
-	public function mkdir( $dirs, $mode = 0777 ) {
82
-		foreach ( $this->toIterable( $dirs ) as $dir ) {
83
-			if ( \is_dir( $dir ) ) {
84
-				continue;
85
-			}
86
-			if ( ! self::box( 'mkdir', $dir, $mode, \true ) ) {
87
-				if ( ! \is_dir( $dir ) ) {
88
-					// The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
89
-					if ( self::$lastError ) {
90
-						throw new IOException( \sprintf( 'Failed to create "%s": ', $dir ) . self::$lastError, 0, null, $dir );
91
-					}
92
-					throw new IOException( \sprintf( 'Failed to create "%s".', $dir ), 0, null, $dir );
93
-				}
94
-			}
95
-		}
96
-	}
97
-	/**
98
-	 * Checks the existence of files or directories.
99
-	 *
100
-	 * @param string|iterable $files A filename, an array of files, or a \Traversable instance to check
101
-	 *
102
-	 * @return bool true if the file exists, false otherwise
103
-	 */
104
-	public function exists( $files ) {
105
-		$maxPathLength = \PHP_MAXPATHLEN - 2;
106
-		foreach ( $this->toIterable( $files ) as $file ) {
107
-			if ( \strlen( $file ) > $maxPathLength ) {
108
-				throw new IOException( \sprintf( 'Could not check if file exist because path length exceeds %d characters.', $maxPathLength ), 0, null, $file );
109
-			}
110
-			if ( ! \file_exists( $file ) ) {
111
-				return \false;
112
-			}
113
-		}
114
-		return \true;
115
-	}
116
-	/**
117
-	 * Sets access and modification time of file.
118
-	 *
119
-	 * @param string|iterable $files A filename, an array of files, or a \Traversable instance to create
120
-	 * @param int|null        $time  The touch time as a Unix timestamp, if not supplied the current system time is used
121
-	 * @param int|null        $atime The access time as a Unix timestamp, if not supplied the current system time is used
122
-	 *
123
-	 * @throws IOException When touch fails
124
-	 */
125
-	public function touch( $files, $time = null, $atime = null ) {
126
-		foreach ( $this->toIterable( $files ) as $file ) {
127
-			$touch = $time ? @\touch( $file, $time, $atime ) : @\touch( $file );
128
-			if ( \true !== $touch ) {
129
-				throw new IOException( \sprintf( 'Failed to touch "%s".', $file ), 0, null, $file );
130
-			}
131
-		}
132
-	}
133
-	/**
134
-	 * Removes files or directories.
135
-	 *
136
-	 * @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove
137
-	 *
138
-	 * @throws IOException When removal fails
139
-	 */
140
-	public function remove( $files ) {
141
-		if ( $files instanceof \Traversable ) {
142
-			$files = \iterator_to_array( $files, \false );
143
-		} elseif ( ! \is_array( $files ) ) {
144
-			$files = array( $files );
145
-		}
146
-		$files = \array_reverse( $files );
147
-		foreach ( $files as $file ) {
148
-			if ( \is_link( $file ) ) {
149
-				// See https://bugs.php.net/52176
150
-				if ( ! ( self::box( 'unlink', $file ) || '\\' !== \DIRECTORY_SEPARATOR || self::box( 'rmdir', $file ) ) && \file_exists( $file ) ) {
151
-					throw new IOException( \sprintf( 'Failed to remove symlink "%s": ', $file ) . self::$lastError );
152
-				}
153
-			} elseif ( \is_dir( $file ) ) {
154
-				$this->remove( new \FilesystemIterator( $file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS ) );
155
-				if ( ! self::box( 'rmdir', $file ) && \file_exists( $file ) ) {
156
-					throw new IOException( \sprintf( 'Failed to remove directory "%s": ', $file ) . self::$lastError );
157
-				}
158
-			} elseif ( ! self::box( 'unlink', $file ) && ( \str_contains( self::$lastError, 'Permission denied' ) || \file_exists( $file ) ) ) {
159
-				throw new IOException( \sprintf( 'Failed to remove file "%s": ', $file ) . self::$lastError );
160
-			}
161
-		}
162
-	}
163
-	/**
164
-	 * Change mode for an array of files or directories.
165
-	 *
166
-	 * @param string|iterable $files     A filename, an array of files, or a \Traversable instance to change mode
167
-	 * @param int             $mode      The new mode (octal)
168
-	 * @param int             $umask     The mode mask (octal)
169
-	 * @param bool            $recursive Whether change the mod recursively or not
170
-	 *
171
-	 * @throws IOException When the change fails
172
-	 */
173
-	public function chmod( $files, $mode, $umask = 00, $recursive = \false ) {
174
-		foreach ( $this->toIterable( $files ) as $file ) {
175
-			if ( ( \PHP_VERSION_ID < 80000 || \is_int( $mode ) ) && \true !== @\chmod( $file, $mode & ~$umask ) ) {
176
-				throw new IOException( \sprintf( 'Failed to chmod file "%s".', $file ), 0, null, $file );
177
-			}
178
-			if ( $recursive && \is_dir( $file ) && ! \is_link( $file ) ) {
179
-				$this->chmod( new \FilesystemIterator( $file ), $mode, $umask, \true );
180
-			}
181
-		}
182
-	}
183
-	/**
184
-	 * Change the owner of an array of files or directories.
185
-	 *
186
-	 * @param string|iterable $files     A filename, an array of files, or a \Traversable instance to change owner
187
-	 * @param string|int      $user      A user name or number
188
-	 * @param bool            $recursive Whether change the owner recursively or not
189
-	 *
190
-	 * @throws IOException When the change fails
191
-	 */
192
-	public function chown( $files, $user, $recursive = \false ) {
193
-		foreach ( $this->toIterable( $files ) as $file ) {
194
-			if ( $recursive && \is_dir( $file ) && ! \is_link( $file ) ) {
195
-				$this->chown( new \FilesystemIterator( $file ), $user, \true );
196
-			}
197
-			if ( \is_link( $file ) && \function_exists( 'lchown' ) ) {
198
-				if ( \true !== @\lchown( $file, $user ) ) {
199
-					throw new IOException( \sprintf( 'Failed to chown file "%s".', $file ), 0, null, $file );
200
-				}
201
-			} else {
202
-				if ( \true !== @\chown( $file, $user ) ) {
203
-					throw new IOException( \sprintf( 'Failed to chown file "%s".', $file ), 0, null, $file );
204
-				}
205
-			}
206
-		}
207
-	}
208
-	/**
209
-	 * Change the group of an array of files or directories.
210
-	 *
211
-	 * @param string|iterable $files     A filename, an array of files, or a \Traversable instance to change group
212
-	 * @param string|int      $group     A group name or number
213
-	 * @param bool            $recursive Whether change the group recursively or not
214
-	 *
215
-	 * @throws IOException When the change fails
216
-	 */
217
-	public function chgrp( $files, $group, $recursive = \false ) {
218
-		foreach ( $this->toIterable( $files ) as $file ) {
219
-			if ( $recursive && \is_dir( $file ) && ! \is_link( $file ) ) {
220
-				$this->chgrp( new \FilesystemIterator( $file ), $group, \true );
221
-			}
222
-			if ( \is_link( $file ) && \function_exists( 'lchgrp' ) ) {
223
-				if ( \true !== @\lchgrp( $file, $group ) ) {
224
-					throw new IOException( \sprintf( 'Failed to chgrp file "%s".', $file ), 0, null, $file );
225
-				}
226
-			} else {
227
-				if ( \true !== @\chgrp( $file, $group ) ) {
228
-					throw new IOException( \sprintf( 'Failed to chgrp file "%s".', $file ), 0, null, $file );
229
-				}
230
-			}
231
-		}
232
-	}
233
-	/**
234
-	 * Renames a file or a directory.
235
-	 *
236
-	 * @param string $origin    The origin filename or directory
237
-	 * @param string $target    The new filename or directory
238
-	 * @param bool   $overwrite Whether to overwrite the target if it already exists
239
-	 *
240
-	 * @throws IOException When target file or directory already exists
241
-	 * @throws IOException When origin cannot be renamed
242
-	 */
243
-	public function rename( $origin, $target, $overwrite = \false ) {
244
-		// we check that target does not exist
245
-		if ( ! $overwrite && $this->isReadable( $target ) ) {
246
-			throw new IOException( \sprintf( 'Cannot rename because the target "%s" already exists.', $target ), 0, null, $target );
247
-		}
248
-		if ( \true !== @\rename( $origin, $target ) ) {
249
-			if ( \is_dir( $origin ) ) {
250
-				// See https://bugs.php.net/54097 & https://php.net/rename#113943
251
-				$this->mirror(
252
-					$origin,
253
-					$target,
254
-					null,
255
-					array(
256
-						'override' => $overwrite,
257
-						'delete'   => $overwrite,
258
-					)
259
-				);
260
-				$this->remove( $origin );
261
-				return;
262
-			}
263
-			throw new IOException( \sprintf( 'Cannot rename "%s" to "%s".', $origin, $target ), 0, null, $target );
264
-		}
265
-	}
266
-	/**
267
-	 * Tells whether a file exists and is readable.
268
-	 *
269
-	 * @throws IOException When windows path is longer than 258 characters
270
-	 */
271
-	private function isReadable( string $filename ) : bool {
272
-		$maxPathLength = \PHP_MAXPATHLEN - 2;
273
-		if ( \strlen( $filename ) > $maxPathLength ) {
274
-			throw new IOException( \sprintf( 'Could not check if file is readable because path length exceeds %d characters.', $maxPathLength ), 0, null, $filename );
275
-		}
276
-		return \is_readable( $filename );
277
-	}
278
-	/**
279
-	 * Creates a symbolic link or copy a directory.
280
-	 *
281
-	 * @param string $originDir     The origin directory path
282
-	 * @param string $targetDir     The symbolic link name
283
-	 * @param bool   $copyOnWindows Whether to copy files if on Windows
284
-	 *
285
-	 * @throws IOException When symlink fails
286
-	 */
287
-	public function symlink( $originDir, $targetDir, $copyOnWindows = \false ) {
288
-		self::assertFunctionExists( 'symlink' );
289
-		if ( '\\' === \DIRECTORY_SEPARATOR ) {
290
-			$originDir = \strtr( $originDir, '/', '\\' );
291
-			$targetDir = \strtr( $targetDir, '/', '\\' );
292
-			if ( $copyOnWindows ) {
293
-				$this->mirror( $originDir, $targetDir );
294
-				return;
295
-			}
296
-		}
297
-		$this->mkdir( \dirname( $targetDir ) );
298
-		if ( \is_link( $targetDir ) ) {
299
-			if ( \readlink( $targetDir ) === $originDir ) {
300
-				return;
301
-			}
302
-			$this->remove( $targetDir );
303
-		}
304
-		if ( ! self::box( 'symlink', $originDir, $targetDir ) ) {
305
-			$this->linkException( $originDir, $targetDir, 'symbolic' );
306
-		}
307
-	}
308
-	/**
309
-	 * Creates a hard link, or several hard links to a file.
310
-	 *
311
-	 * @param string          $originFile  The original file
312
-	 * @param string|string[] $targetFiles The target file(s)
313
-	 *
314
-	 * @throws FileNotFoundException When original file is missing or not a file
315
-	 * @throws IOException           When link fails, including if link already exists
316
-	 */
317
-	public function hardlink( $originFile, $targetFiles ) {
318
-		self::assertFunctionExists( 'link' );
319
-		if ( ! $this->exists( $originFile ) ) {
320
-			throw new FileNotFoundException( null, 0, null, $originFile );
321
-		}
322
-		if ( ! \is_file( $originFile ) ) {
323
-			throw new FileNotFoundException( \sprintf( 'Origin file "%s" is not a file.', $originFile ) );
324
-		}
325
-		foreach ( $this->toIterable( $targetFiles ) as $targetFile ) {
326
-			if ( \is_file( $targetFile ) ) {
327
-				if ( \fileinode( $originFile ) === \fileinode( $targetFile ) ) {
328
-					continue;
329
-				}
330
-				$this->remove( $targetFile );
331
-			}
332
-			if ( ! self::box( 'link', $originFile, $targetFile ) ) {
333
-				$this->linkException( $originFile, $targetFile, 'hard' );
334
-			}
335
-		}
336
-	}
337
-	/**
338
-	 * @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
339
-	 */
340
-	private function linkException( string $origin, string $target, string $linkType ) {
341
-		if ( self::$lastError ) {
342
-			if ( '\\' === \DIRECTORY_SEPARATOR && \str_contains( self::$lastError, 'error code(1314)' ) ) {
343
-				throw new IOException( \sprintf( 'Unable to create "%s" link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType ), 0, null, $target );
344
-			}
345
-		}
346
-		throw new IOException( \sprintf( 'Failed to create "%s" link from "%s" to "%s".', $linkType, $origin, $target ), 0, null, $target );
347
-	}
348
-	/**
349
-	 * Resolves links in paths.
350
-	 *
351
-	 * With $canonicalize = false (default)
352
-	 *      - if $path does not exist or is not a link, returns null
353
-	 *      - if $path is a link, returns the next direct target of the link without considering the existence of the target
354
-	 *
355
-	 * With $canonicalize = true
356
-	 *      - if $path does not exist, returns null
357
-	 *      - if $path exists, returns its absolute fully resolved final version
358
-	 *
359
-	 * @param string $path         A filesystem path
360
-	 * @param bool   $canonicalize Whether or not to return a canonicalized path
361
-	 *
362
-	 * @return string|null
363
-	 */
364
-	public function readlink( $path, $canonicalize = \false ) {
365
-		if ( ! $canonicalize && ! \is_link( $path ) ) {
366
-			return null;
367
-		}
368
-		if ( $canonicalize ) {
369
-			if ( ! $this->exists( $path ) ) {
370
-				return null;
371
-			}
372
-			if ( '\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70410 ) {
373
-				$path = \readlink( $path );
374
-			}
375
-			return \realpath( $path );
376
-		}
377
-		if ( '\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70400 ) {
378
-			return \realpath( $path );
379
-		}
380
-		return \readlink( $path );
381
-	}
382
-	/**
383
-	 * Given an existing path, convert it to a path relative to a given starting path.
384
-	 *
385
-	 * @param string $endPath   Absolute path of target
386
-	 * @param string $startPath Absolute path where traversal begins
387
-	 *
388
-	 * @return string Path of target relative to starting path
389
-	 */
390
-	public function makePathRelative( $endPath, $startPath ) {
391
-		if ( ! $this->isAbsolutePath( $startPath ) ) {
392
-			throw new InvalidArgumentException( \sprintf( 'The start path "%s" is not absolute.', $startPath ) );
393
-		}
394
-		if ( ! $this->isAbsolutePath( $endPath ) ) {
395
-			throw new InvalidArgumentException( \sprintf( 'The end path "%s" is not absolute.', $endPath ) );
396
-		}
397
-		// Normalize separators on Windows
398
-		if ( '\\' === \DIRECTORY_SEPARATOR ) {
399
-			$endPath   = \str_replace( '\\', '/', $endPath );
400
-			$startPath = \str_replace( '\\', '/', $startPath );
401
-		}
402
-		$splitDriveLetter                      = function ( $path ) {
403
-			return \strlen( $path ) > 2 && ':' === $path[1] && '/' === $path[2] && \ctype_alpha( $path[0] ) ? array( \substr( $path, 2 ), \strtoupper( $path[0] ) ) : array( $path, null );
404
-		};
405
-		$splitPath                             = function ( $path ) {
406
-			$result = array();
407
-			foreach ( \explode( '/', \trim( $path, '/' ) ) as $segment ) {
408
-				if ( '..' === $segment ) {
409
-					\array_pop( $result );
410
-				} elseif ( '.' !== $segment && '' !== $segment ) {
411
-					$result[] = $segment;
412
-				}
413
-			}
414
-			return $result;
415
-		};
416
-		[ $endPath, $endDriveLetter ]     = $splitDriveLetter( $endPath );
417
-		[ $startPath, $startDriveLetter ] = $splitDriveLetter( $startPath );
418
-		$startPathArr                          = $splitPath( $startPath );
419
-		$endPathArr                            = $splitPath( $endPath );
420
-		if ( $endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter ) {
421
-			// End path is on another drive, so no relative path exists
422
-			return $endDriveLetter . ':/' . ( $endPathArr ? \implode( '/', $endPathArr ) . '/' : '' );
423
-		}
424
-		// Find for which directory the common path stops
425
-		$index = 0;
426
-		while ( isset( $startPathArr[ $index ] ) && isset( $endPathArr[ $index ] ) && $startPathArr[ $index ] === $endPathArr[ $index ] ) {
427
-			++$index;
428
-		}
429
-		// Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
430
-		if ( 1 === \count( $startPathArr ) && '' === $startPathArr[0] ) {
431
-			$depth = 0;
432
-		} else {
433
-			$depth = \count( $startPathArr ) - $index;
434
-		}
435
-		// Repeated "../" for each level need to reach the common path
436
-		$traverser        = \str_repeat( '../', $depth );
437
-		$endPathRemainder = \implode( '/', \array_slice( $endPathArr, $index ) );
438
-		// Construct $endPath from traversing to the common path, then to the remaining $endPath
439
-		$relativePath = $traverser . ( '' !== $endPathRemainder ? $endPathRemainder . '/' : '' );
440
-		return '' === $relativePath ? './' : $relativePath;
441
-	}
442
-	/**
443
-	 * Mirrors a directory to another.
444
-	 *
445
-	 * Copies files and directories from the origin directory into the target directory. By default:
446
-	 *
447
-	 *  - existing files in the target directory will be overwritten, except if they are newer (see the `override` option)
448
-	 *  - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option)
449
-	 *
450
-	 * @param string            $originDir The origin directory
451
-	 * @param string            $targetDir The target directory
452
-	 * @param \Traversable|null $iterator  Iterator that filters which files and directories to copy, if null a recursive iterator is created
453
-	 * @param array             $options   An array of boolean options
454
-	 *                                     Valid options are:
455
-	 *                                     - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false)
456
-	 *                                     - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
457
-	 *                                     - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
458
-	 *
459
-	 * @throws IOException When file type is unknown
460
-	 */
461
-	public function mirror( $originDir, $targetDir, \Traversable $iterator = null, $options = array() ) {
462
-		$targetDir    = \rtrim( $targetDir, '/\\' );
463
-		$originDir    = \rtrim( $originDir, '/\\' );
464
-		$originDirLen = \strlen( $originDir );
465
-		if ( ! $this->exists( $originDir ) ) {
466
-			throw new IOException( \sprintf( 'The origin directory specified "%s" was not found.', $originDir ), 0, null, $originDir );
467
-		}
468
-		// Iterate in destination folder to remove obsolete entries
469
-		if ( $this->exists( $targetDir ) && isset( $options['delete'] ) && $options['delete'] ) {
470
-			$deleteIterator = $iterator;
471
-			if ( null === $deleteIterator ) {
472
-				$flags          = \FilesystemIterator::SKIP_DOTS;
473
-				$deleteIterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $targetDir, $flags ), \RecursiveIteratorIterator::CHILD_FIRST );
474
-			}
475
-			$targetDirLen = \strlen( $targetDir );
476
-			foreach ( $deleteIterator as $file ) {
477
-				$origin = $originDir . \substr( $file->getPathname(), $targetDirLen );
478
-				if ( ! $this->exists( $origin ) ) {
479
-					$this->remove( $file );
480
-				}
481
-			}
482
-		}
483
-		$copyOnWindows = $options['copy_on_windows'] ?? \false;
484
-		if ( null === $iterator ) {
485
-			$flags    = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS;
486
-			$iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $originDir, $flags ), \RecursiveIteratorIterator::SELF_FIRST );
487
-		}
488
-		$this->mkdir( $targetDir );
489
-		$filesCreatedWhileMirroring = array();
490
-		foreach ( $iterator as $file ) {
491
-			if ( $file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset( $filesCreatedWhileMirroring[ $file->getRealPath() ] ) ) {
492
-				continue;
493
-			}
494
-			$target                                = $targetDir . \substr( $file->getPathname(), $originDirLen );
495
-			$filesCreatedWhileMirroring[ $target ] = \true;
496
-			if ( ! $copyOnWindows && \is_link( $file ) ) {
497
-				$this->symlink( $file->getLinkTarget(), $target );
498
-			} elseif ( \is_dir( $file ) ) {
499
-				$this->mkdir( $target );
500
-			} elseif ( \is_file( $file ) ) {
501
-				$this->copy( $file, $target, $options['override'] ?? \false );
502
-			} else {
503
-				throw new IOException( \sprintf( 'Unable to guess "%s" file type.', $file ), 0, null, $file );
504
-			}
505
-		}
506
-	}
507
-	/**
508
-	 * Returns whether the file path is an absolute path.
509
-	 *
510
-	 * @param string $file A file path
511
-	 *
512
-	 * @return bool
513
-	 */
514
-	public function isAbsolutePath( $file ) {
515
-		if ( null === $file ) {
516
-			@\trigger_error( \sprintf( 'Calling "%s()" with a null in the $file argument is deprecated since Symfony 4.4.', __METHOD__ ), \E_USER_DEPRECATED );
517
-		}
518
-		return '' !== (string) $file && ( \strspn( $file, '/\\', 0, 1 ) || \strlen( $file ) > 3 && \ctype_alpha( $file[0] ) && ':' === $file[1] && \strspn( $file, '/\\', 2, 1 ) || null !== \parse_url( $file, \PHP_URL_SCHEME ) );
519
-	}
520
-	/**
521
-	 * Creates a temporary file with support for custom stream wrappers.
522
-	 *
523
-	 * @param string $dir    The directory where the temporary filename will be created
524
-	 * @param string $prefix The prefix of the generated temporary filename
525
-	 *                       Note: Windows uses only the first three characters of prefix
526
-	 *
527
-	 * @return string The new temporary filename (with path), or throw an exception on failure
528
-	 */
529
-	public function tempnam( $dir, $prefix ) {
530
-		[ $scheme, $hierarchy ] = $this->getSchemeAndHierarchy( $dir );
531
-		// If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem
532
-		if ( null === $scheme || 'file' === $scheme || 'gs' === $scheme ) {
533
-			$tmpFile = @\tempnam( $hierarchy, $prefix );
534
-			// If tempnam failed or no scheme return the filename otherwise prepend the scheme
535
-			if ( \false !== $tmpFile ) {
536
-				if ( null !== $scheme && 'gs' !== $scheme ) {
537
-					return $scheme . '://' . $tmpFile;
538
-				}
539
-				return $tmpFile;
540
-			}
541
-			throw new IOException( 'A temporary file could not be created.' );
542
-		}
543
-		// Loop until we create a valid temp file or have reached 10 attempts
544
-		for ( $i = 0; $i < 10; ++$i ) {
545
-			// Create a unique filename
546
-			$tmpFile = $dir . '/' . $prefix . \uniqid( \mt_rand(), \true );
547
-			// Use fopen instead of file_exists as some streams do not support stat
548
-			// Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
549
-			$handle = @\fopen( $tmpFile, 'x+' );
550
-			// If unsuccessful restart the loop
551
-			if ( \false === $handle ) {
552
-				continue;
553
-			}
554
-			// Close the file if it was successfully opened
555
-			@\fclose( $handle );
556
-			return $tmpFile;
557
-		}
558
-		throw new IOException( 'A temporary file could not be created.' );
559
-	}
560
-	/**
561
-	 * Atomically dumps content into a file.
562
-	 *
563
-	 * @param string          $filename The file to be written to
564
-	 * @param string|resource $content  The data to write into the file
565
-	 *
566
-	 * @throws IOException if the file cannot be written to
567
-	 */
568
-	public function dumpFile( $filename, $content ) {
569
-		if ( \is_array( $content ) ) {
570
-			@\trigger_error( \sprintf( 'Calling "%s()" with an array in the $content argument is deprecated since Symfony 4.3.', __METHOD__ ), \E_USER_DEPRECATED );
571
-		}
572
-		$dir = \dirname( $filename );
573
-		if ( ! \is_dir( $dir ) ) {
574
-			$this->mkdir( $dir );
575
-		}
576
-		// Will create a temp file with 0600 access rights
577
-		// when the filesystem supports chmod.
578
-		$tmpFile = $this->tempnam( $dir, \basename( $filename ) );
579
-		try {
580
-			if ( \false === @\file_put_contents( $tmpFile, $content ) ) {
581
-				throw new IOException( \sprintf( 'Failed to write file "%s".', $filename ), 0, null, $filename );
582
-			}
583
-			@\chmod( $tmpFile, \file_exists( $filename ) ? \fileperms( $filename ) : 0666 & ~\umask() );
584
-			$this->rename( $tmpFile, $filename, \true );
585
-		} finally {
586
-			if ( \file_exists( $tmpFile ) ) {
587
-				@\unlink( $tmpFile );
588
-			}
589
-		}
590
-	}
591
-	/**
592
-	 * Appends content to an existing file.
593
-	 *
594
-	 * @param string          $filename The file to which to append content
595
-	 * @param string|resource $content  The content to append
596
-	 *
597
-	 * @throws IOException If the file is not writable
598
-	 */
599
-	public function appendToFile( $filename, $content ) {
600
-		if ( \is_array( $content ) ) {
601
-			@\trigger_error( \sprintf( 'Calling "%s()" with an array in the $content argument is deprecated since Symfony 4.3.', __METHOD__ ), \E_USER_DEPRECATED );
602
-		}
603
-		$dir = \dirname( $filename );
604
-		if ( ! \is_dir( $dir ) ) {
605
-			$this->mkdir( $dir );
606
-		}
607
-		if ( \false === @\file_put_contents( $filename, $content, \FILE_APPEND ) ) {
608
-			throw new IOException( \sprintf( 'Failed to write file "%s".', $filename ), 0, null, $filename );
609
-		}
610
-	}
611
-	private function toIterable( $files ) : iterable {
612
-		return \is_iterable( $files ) ? $files : array( $files );
613
-	}
614
-	/**
615
-	 * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]).
616
-	 */
617
-	private function getSchemeAndHierarchy( string $filename ) : array {
618
-		$components = \explode( '://', $filename, 2 );
619
-		return 2 === \count( $components ) ? array( $components[0], $components[1] ) : array( null, $components[0] );
620
-	}
621
-	private static function assertFunctionExists( string $func ) : void {
622
-		if ( ! \function_exists( $func ) ) {
623
-			throw new IOException( \sprintf( 'Unable to perform filesystem operation because the "%s()" function has been disabled.', $func ) );
624
-		}
625
-	}
626
-	/**
627
-	 * @param mixed ...$args
628
-	 *
629
-	 * @return mixed
630
-	 */
631
-	private static function box( string $func, ...$args ) {
632
-		self::assertFunctionExists( $func );
633
-		self::$lastError = null;
634
-		\set_error_handler( __CLASS__ . '::handleError' );
635
-		try {
636
-			$result = $func( ...$args );
637
-			\restore_error_handler();
638
-			return $result;
639
-		} catch ( \Throwable $e ) {
640
-		}
641
-		\restore_error_handler();
642
-		throw $e;
643
-	}
644
-	/**
645
-	 * @internal
646
-	 */
647
-	public static function handleError( int $type, string $msg ) {
648
-		self::$lastError = $msg;
649
-	}
23
+    private static $lastError;
24
+    /**
25
+     * Copies a file.
26
+     *
27
+     * If the target file is older than the origin file, it's always overwritten.
28
+     * If the target file is newer, it is overwritten only when the
29
+     * $overwriteNewerFiles option is set to true.
30
+     *
31
+     * @param string $originFile          The original filename
32
+     * @param string $targetFile          The target filename
33
+     * @param bool   $overwriteNewerFiles If true, target files newer than origin files are overwritten
34
+     *
35
+     * @throws FileNotFoundException When originFile doesn't exist
36
+     * @throws IOException           When copy fails
37
+     */
38
+    public function copy( $originFile, $targetFile, $overwriteNewerFiles = \false ) {
39
+        $originIsLocal = \stream_is_local( $originFile ) || 0 === \stripos( $originFile, 'file://' );
40
+        if ( $originIsLocal && ! \is_file( $originFile ) ) {
41
+            throw new FileNotFoundException( \sprintf( 'Failed to copy "%s" because file does not exist.', $originFile ), 0, null, $originFile );
42
+        }
43
+        $this->mkdir( \dirname( $targetFile ) );
44
+        $doCopy = \true;
45
+        if ( ! $overwriteNewerFiles && null === \parse_url( $originFile, \PHP_URL_HOST ) && \is_file( $targetFile ) ) {
46
+            $doCopy = \filemtime( $originFile ) > \filemtime( $targetFile );
47
+        }
48
+        if ( $doCopy ) {
49
+            // https://bugs.php.net/64634
50
+            if ( \false === ( $source = @\fopen( $originFile, 'r' ) ) ) {
51
+                throw new IOException( \sprintf( 'Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile ), 0, null, $originFile );
52
+            }
53
+            // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
54
+            if ( \false === ( $target = @\fopen( $targetFile, 'w', \false, \stream_context_create( array( 'ftp' => array( 'overwrite' => \true ) ) ) ) ) ) {
55
+                throw new IOException( \sprintf( 'Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile ), 0, null, $originFile );
56
+            }
57
+            $bytesCopied = \stream_copy_to_stream( $source, $target );
58
+            \fclose( $source );
59
+            \fclose( $target );
60
+            unset( $source, $target );
61
+            if ( ! \is_file( $targetFile ) ) {
62
+                throw new IOException( \sprintf( 'Failed to copy "%s" to "%s".', $originFile, $targetFile ), 0, null, $originFile );
63
+            }
64
+            if ( $originIsLocal ) {
65
+                // Like `cp`, preserve executable permission bits
66
+                @\chmod( $targetFile, \fileperms( $targetFile ) | \fileperms( $originFile ) & 0111 );
67
+                if ( $bytesCopied !== ( $bytesOrigin = \filesize( $originFile ) ) ) {
68
+                    throw new IOException( \sprintf( 'Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin ), 0, null, $originFile );
69
+                }
70
+            }
71
+        }
72
+    }
73
+    /**
74
+     * Creates a directory recursively.
75
+     *
76
+     * @param string|iterable $dirs The directory path
77
+     * @param int             $mode The directory mode
78
+     *
79
+     * @throws IOException On any directory creation failure
80
+     */
81
+    public function mkdir( $dirs, $mode = 0777 ) {
82
+        foreach ( $this->toIterable( $dirs ) as $dir ) {
83
+            if ( \is_dir( $dir ) ) {
84
+                continue;
85
+            }
86
+            if ( ! self::box( 'mkdir', $dir, $mode, \true ) ) {
87
+                if ( ! \is_dir( $dir ) ) {
88
+                    // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
89
+                    if ( self::$lastError ) {
90
+                        throw new IOException( \sprintf( 'Failed to create "%s": ', $dir ) . self::$lastError, 0, null, $dir );
91
+                    }
92
+                    throw new IOException( \sprintf( 'Failed to create "%s".', $dir ), 0, null, $dir );
93
+                }
94
+            }
95
+        }
96
+    }
97
+    /**
98
+     * Checks the existence of files or directories.
99
+     *
100
+     * @param string|iterable $files A filename, an array of files, or a \Traversable instance to check
101
+     *
102
+     * @return bool true if the file exists, false otherwise
103
+     */
104
+    public function exists( $files ) {
105
+        $maxPathLength = \PHP_MAXPATHLEN - 2;
106
+        foreach ( $this->toIterable( $files ) as $file ) {
107
+            if ( \strlen( $file ) > $maxPathLength ) {
108
+                throw new IOException( \sprintf( 'Could not check if file exist because path length exceeds %d characters.', $maxPathLength ), 0, null, $file );
109
+            }
110
+            if ( ! \file_exists( $file ) ) {
111
+                return \false;
112
+            }
113
+        }
114
+        return \true;
115
+    }
116
+    /**
117
+     * Sets access and modification time of file.
118
+     *
119
+     * @param string|iterable $files A filename, an array of files, or a \Traversable instance to create
120
+     * @param int|null        $time  The touch time as a Unix timestamp, if not supplied the current system time is used
121
+     * @param int|null        $atime The access time as a Unix timestamp, if not supplied the current system time is used
122
+     *
123
+     * @throws IOException When touch fails
124
+     */
125
+    public function touch( $files, $time = null, $atime = null ) {
126
+        foreach ( $this->toIterable( $files ) as $file ) {
127
+            $touch = $time ? @\touch( $file, $time, $atime ) : @\touch( $file );
128
+            if ( \true !== $touch ) {
129
+                throw new IOException( \sprintf( 'Failed to touch "%s".', $file ), 0, null, $file );
130
+            }
131
+        }
132
+    }
133
+    /**
134
+     * Removes files or directories.
135
+     *
136
+     * @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove
137
+     *
138
+     * @throws IOException When removal fails
139
+     */
140
+    public function remove( $files ) {
141
+        if ( $files instanceof \Traversable ) {
142
+            $files = \iterator_to_array( $files, \false );
143
+        } elseif ( ! \is_array( $files ) ) {
144
+            $files = array( $files );
145
+        }
146
+        $files = \array_reverse( $files );
147
+        foreach ( $files as $file ) {
148
+            if ( \is_link( $file ) ) {
149
+                // See https://bugs.php.net/52176
150
+                if ( ! ( self::box( 'unlink', $file ) || '\\' !== \DIRECTORY_SEPARATOR || self::box( 'rmdir', $file ) ) && \file_exists( $file ) ) {
151
+                    throw new IOException( \sprintf( 'Failed to remove symlink "%s": ', $file ) . self::$lastError );
152
+                }
153
+            } elseif ( \is_dir( $file ) ) {
154
+                $this->remove( new \FilesystemIterator( $file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS ) );
155
+                if ( ! self::box( 'rmdir', $file ) && \file_exists( $file ) ) {
156
+                    throw new IOException( \sprintf( 'Failed to remove directory "%s": ', $file ) . self::$lastError );
157
+                }
158
+            } elseif ( ! self::box( 'unlink', $file ) && ( \str_contains( self::$lastError, 'Permission denied' ) || \file_exists( $file ) ) ) {
159
+                throw new IOException( \sprintf( 'Failed to remove file "%s": ', $file ) . self::$lastError );
160
+            }
161
+        }
162
+    }
163
+    /**
164
+     * Change mode for an array of files or directories.
165
+     *
166
+     * @param string|iterable $files     A filename, an array of files, or a \Traversable instance to change mode
167
+     * @param int             $mode      The new mode (octal)
168
+     * @param int             $umask     The mode mask (octal)
169
+     * @param bool            $recursive Whether change the mod recursively or not
170
+     *
171
+     * @throws IOException When the change fails
172
+     */
173
+    public function chmod( $files, $mode, $umask = 00, $recursive = \false ) {
174
+        foreach ( $this->toIterable( $files ) as $file ) {
175
+            if ( ( \PHP_VERSION_ID < 80000 || \is_int( $mode ) ) && \true !== @\chmod( $file, $mode & ~$umask ) ) {
176
+                throw new IOException( \sprintf( 'Failed to chmod file "%s".', $file ), 0, null, $file );
177
+            }
178
+            if ( $recursive && \is_dir( $file ) && ! \is_link( $file ) ) {
179
+                $this->chmod( new \FilesystemIterator( $file ), $mode, $umask, \true );
180
+            }
181
+        }
182
+    }
183
+    /**
184
+     * Change the owner of an array of files or directories.
185
+     *
186
+     * @param string|iterable $files     A filename, an array of files, or a \Traversable instance to change owner
187
+     * @param string|int      $user      A user name or number
188
+     * @param bool            $recursive Whether change the owner recursively or not
189
+     *
190
+     * @throws IOException When the change fails
191
+     */
192
+    public function chown( $files, $user, $recursive = \false ) {
193
+        foreach ( $this->toIterable( $files ) as $file ) {
194
+            if ( $recursive && \is_dir( $file ) && ! \is_link( $file ) ) {
195
+                $this->chown( new \FilesystemIterator( $file ), $user, \true );
196
+            }
197
+            if ( \is_link( $file ) && \function_exists( 'lchown' ) ) {
198
+                if ( \true !== @\lchown( $file, $user ) ) {
199
+                    throw new IOException( \sprintf( 'Failed to chown file "%s".', $file ), 0, null, $file );
200
+                }
201
+            } else {
202
+                if ( \true !== @\chown( $file, $user ) ) {
203
+                    throw new IOException( \sprintf( 'Failed to chown file "%s".', $file ), 0, null, $file );
204
+                }
205
+            }
206
+        }
207
+    }
208
+    /**
209
+     * Change the group of an array of files or directories.
210
+     *
211
+     * @param string|iterable $files     A filename, an array of files, or a \Traversable instance to change group
212
+     * @param string|int      $group     A group name or number
213
+     * @param bool            $recursive Whether change the group recursively or not
214
+     *
215
+     * @throws IOException When the change fails
216
+     */
217
+    public function chgrp( $files, $group, $recursive = \false ) {
218
+        foreach ( $this->toIterable( $files ) as $file ) {
219
+            if ( $recursive && \is_dir( $file ) && ! \is_link( $file ) ) {
220
+                $this->chgrp( new \FilesystemIterator( $file ), $group, \true );
221
+            }
222
+            if ( \is_link( $file ) && \function_exists( 'lchgrp' ) ) {
223
+                if ( \true !== @\lchgrp( $file, $group ) ) {
224
+                    throw new IOException( \sprintf( 'Failed to chgrp file "%s".', $file ), 0, null, $file );
225
+                }
226
+            } else {
227
+                if ( \true !== @\chgrp( $file, $group ) ) {
228
+                    throw new IOException( \sprintf( 'Failed to chgrp file "%s".', $file ), 0, null, $file );
229
+                }
230
+            }
231
+        }
232
+    }
233
+    /**
234
+     * Renames a file or a directory.
235
+     *
236
+     * @param string $origin    The origin filename or directory
237
+     * @param string $target    The new filename or directory
238
+     * @param bool   $overwrite Whether to overwrite the target if it already exists
239
+     *
240
+     * @throws IOException When target file or directory already exists
241
+     * @throws IOException When origin cannot be renamed
242
+     */
243
+    public function rename( $origin, $target, $overwrite = \false ) {
244
+        // we check that target does not exist
245
+        if ( ! $overwrite && $this->isReadable( $target ) ) {
246
+            throw new IOException( \sprintf( 'Cannot rename because the target "%s" already exists.', $target ), 0, null, $target );
247
+        }
248
+        if ( \true !== @\rename( $origin, $target ) ) {
249
+            if ( \is_dir( $origin ) ) {
250
+                // See https://bugs.php.net/54097 & https://php.net/rename#113943
251
+                $this->mirror(
252
+                    $origin,
253
+                    $target,
254
+                    null,
255
+                    array(
256
+                        'override' => $overwrite,
257
+                        'delete'   => $overwrite,
258
+                    )
259
+                );
260
+                $this->remove( $origin );
261
+                return;
262
+            }
263
+            throw new IOException( \sprintf( 'Cannot rename "%s" to "%s".', $origin, $target ), 0, null, $target );
264
+        }
265
+    }
266
+    /**
267
+     * Tells whether a file exists and is readable.
268
+     *
269
+     * @throws IOException When windows path is longer than 258 characters
270
+     */
271
+    private function isReadable( string $filename ) : bool {
272
+        $maxPathLength = \PHP_MAXPATHLEN - 2;
273
+        if ( \strlen( $filename ) > $maxPathLength ) {
274
+            throw new IOException( \sprintf( 'Could not check if file is readable because path length exceeds %d characters.', $maxPathLength ), 0, null, $filename );
275
+        }
276
+        return \is_readable( $filename );
277
+    }
278
+    /**
279
+     * Creates a symbolic link or copy a directory.
280
+     *
281
+     * @param string $originDir     The origin directory path
282
+     * @param string $targetDir     The symbolic link name
283
+     * @param bool   $copyOnWindows Whether to copy files if on Windows
284
+     *
285
+     * @throws IOException When symlink fails
286
+     */
287
+    public function symlink( $originDir, $targetDir, $copyOnWindows = \false ) {
288
+        self::assertFunctionExists( 'symlink' );
289
+        if ( '\\' === \DIRECTORY_SEPARATOR ) {
290
+            $originDir = \strtr( $originDir, '/', '\\' );
291
+            $targetDir = \strtr( $targetDir, '/', '\\' );
292
+            if ( $copyOnWindows ) {
293
+                $this->mirror( $originDir, $targetDir );
294
+                return;
295
+            }
296
+        }
297
+        $this->mkdir( \dirname( $targetDir ) );
298
+        if ( \is_link( $targetDir ) ) {
299
+            if ( \readlink( $targetDir ) === $originDir ) {
300
+                return;
301
+            }
302
+            $this->remove( $targetDir );
303
+        }
304
+        if ( ! self::box( 'symlink', $originDir, $targetDir ) ) {
305
+            $this->linkException( $originDir, $targetDir, 'symbolic' );
306
+        }
307
+    }
308
+    /**
309
+     * Creates a hard link, or several hard links to a file.
310
+     *
311
+     * @param string          $originFile  The original file
312
+     * @param string|string[] $targetFiles The target file(s)
313
+     *
314
+     * @throws FileNotFoundException When original file is missing or not a file
315
+     * @throws IOException           When link fails, including if link already exists
316
+     */
317
+    public function hardlink( $originFile, $targetFiles ) {
318
+        self::assertFunctionExists( 'link' );
319
+        if ( ! $this->exists( $originFile ) ) {
320
+            throw new FileNotFoundException( null, 0, null, $originFile );
321
+        }
322
+        if ( ! \is_file( $originFile ) ) {
323
+            throw new FileNotFoundException( \sprintf( 'Origin file "%s" is not a file.', $originFile ) );
324
+        }
325
+        foreach ( $this->toIterable( $targetFiles ) as $targetFile ) {
326
+            if ( \is_file( $targetFile ) ) {
327
+                if ( \fileinode( $originFile ) === \fileinode( $targetFile ) ) {
328
+                    continue;
329
+                }
330
+                $this->remove( $targetFile );
331
+            }
332
+            if ( ! self::box( 'link', $originFile, $targetFile ) ) {
333
+                $this->linkException( $originFile, $targetFile, 'hard' );
334
+            }
335
+        }
336
+    }
337
+    /**
338
+     * @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
339
+     */
340
+    private function linkException( string $origin, string $target, string $linkType ) {
341
+        if ( self::$lastError ) {
342
+            if ( '\\' === \DIRECTORY_SEPARATOR && \str_contains( self::$lastError, 'error code(1314)' ) ) {
343
+                throw new IOException( \sprintf( 'Unable to create "%s" link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType ), 0, null, $target );
344
+            }
345
+        }
346
+        throw new IOException( \sprintf( 'Failed to create "%s" link from "%s" to "%s".', $linkType, $origin, $target ), 0, null, $target );
347
+    }
348
+    /**
349
+     * Resolves links in paths.
350
+     *
351
+     * With $canonicalize = false (default)
352
+     *      - if $path does not exist or is not a link, returns null
353
+     *      - if $path is a link, returns the next direct target of the link without considering the existence of the target
354
+     *
355
+     * With $canonicalize = true
356
+     *      - if $path does not exist, returns null
357
+     *      - if $path exists, returns its absolute fully resolved final version
358
+     *
359
+     * @param string $path         A filesystem path
360
+     * @param bool   $canonicalize Whether or not to return a canonicalized path
361
+     *
362
+     * @return string|null
363
+     */
364
+    public function readlink( $path, $canonicalize = \false ) {
365
+        if ( ! $canonicalize && ! \is_link( $path ) ) {
366
+            return null;
367
+        }
368
+        if ( $canonicalize ) {
369
+            if ( ! $this->exists( $path ) ) {
370
+                return null;
371
+            }
372
+            if ( '\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70410 ) {
373
+                $path = \readlink( $path );
374
+            }
375
+            return \realpath( $path );
376
+        }
377
+        if ( '\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70400 ) {
378
+            return \realpath( $path );
379
+        }
380
+        return \readlink( $path );
381
+    }
382
+    /**
383
+     * Given an existing path, convert it to a path relative to a given starting path.
384
+     *
385
+     * @param string $endPath   Absolute path of target
386
+     * @param string $startPath Absolute path where traversal begins
387
+     *
388
+     * @return string Path of target relative to starting path
389
+     */
390
+    public function makePathRelative( $endPath, $startPath ) {
391
+        if ( ! $this->isAbsolutePath( $startPath ) ) {
392
+            throw new InvalidArgumentException( \sprintf( 'The start path "%s" is not absolute.', $startPath ) );
393
+        }
394
+        if ( ! $this->isAbsolutePath( $endPath ) ) {
395
+            throw new InvalidArgumentException( \sprintf( 'The end path "%s" is not absolute.', $endPath ) );
396
+        }
397
+        // Normalize separators on Windows
398
+        if ( '\\' === \DIRECTORY_SEPARATOR ) {
399
+            $endPath   = \str_replace( '\\', '/', $endPath );
400
+            $startPath = \str_replace( '\\', '/', $startPath );
401
+        }
402
+        $splitDriveLetter                      = function ( $path ) {
403
+            return \strlen( $path ) > 2 && ':' === $path[1] && '/' === $path[2] && \ctype_alpha( $path[0] ) ? array( \substr( $path, 2 ), \strtoupper( $path[0] ) ) : array( $path, null );
404
+        };
405
+        $splitPath                             = function ( $path ) {
406
+            $result = array();
407
+            foreach ( \explode( '/', \trim( $path, '/' ) ) as $segment ) {
408
+                if ( '..' === $segment ) {
409
+                    \array_pop( $result );
410
+                } elseif ( '.' !== $segment && '' !== $segment ) {
411
+                    $result[] = $segment;
412
+                }
413
+            }
414
+            return $result;
415
+        };
416
+        [ $endPath, $endDriveLetter ]     = $splitDriveLetter( $endPath );
417
+        [ $startPath, $startDriveLetter ] = $splitDriveLetter( $startPath );
418
+        $startPathArr                          = $splitPath( $startPath );
419
+        $endPathArr                            = $splitPath( $endPath );
420
+        if ( $endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter ) {
421
+            // End path is on another drive, so no relative path exists
422
+            return $endDriveLetter . ':/' . ( $endPathArr ? \implode( '/', $endPathArr ) . '/' : '' );
423
+        }
424
+        // Find for which directory the common path stops
425
+        $index = 0;
426
+        while ( isset( $startPathArr[ $index ] ) && isset( $endPathArr[ $index ] ) && $startPathArr[ $index ] === $endPathArr[ $index ] ) {
427
+            ++$index;
428
+        }
429
+        // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
430
+        if ( 1 === \count( $startPathArr ) && '' === $startPathArr[0] ) {
431
+            $depth = 0;
432
+        } else {
433
+            $depth = \count( $startPathArr ) - $index;
434
+        }
435
+        // Repeated "../" for each level need to reach the common path
436
+        $traverser        = \str_repeat( '../', $depth );
437
+        $endPathRemainder = \implode( '/', \array_slice( $endPathArr, $index ) );
438
+        // Construct $endPath from traversing to the common path, then to the remaining $endPath
439
+        $relativePath = $traverser . ( '' !== $endPathRemainder ? $endPathRemainder . '/' : '' );
440
+        return '' === $relativePath ? './' : $relativePath;
441
+    }
442
+    /**
443
+     * Mirrors a directory to another.
444
+     *
445
+     * Copies files and directories from the origin directory into the target directory. By default:
446
+     *
447
+     *  - existing files in the target directory will be overwritten, except if they are newer (see the `override` option)
448
+     *  - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option)
449
+     *
450
+     * @param string            $originDir The origin directory
451
+     * @param string            $targetDir The target directory
452
+     * @param \Traversable|null $iterator  Iterator that filters which files and directories to copy, if null a recursive iterator is created
453
+     * @param array             $options   An array of boolean options
454
+     *                                     Valid options are:
455
+     *                                     - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false)
456
+     *                                     - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
457
+     *                                     - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
458
+     *
459
+     * @throws IOException When file type is unknown
460
+     */
461
+    public function mirror( $originDir, $targetDir, \Traversable $iterator = null, $options = array() ) {
462
+        $targetDir    = \rtrim( $targetDir, '/\\' );
463
+        $originDir    = \rtrim( $originDir, '/\\' );
464
+        $originDirLen = \strlen( $originDir );
465
+        if ( ! $this->exists( $originDir ) ) {
466
+            throw new IOException( \sprintf( 'The origin directory specified "%s" was not found.', $originDir ), 0, null, $originDir );
467
+        }
468
+        // Iterate in destination folder to remove obsolete entries
469
+        if ( $this->exists( $targetDir ) && isset( $options['delete'] ) && $options['delete'] ) {
470
+            $deleteIterator = $iterator;
471
+            if ( null === $deleteIterator ) {
472
+                $flags          = \FilesystemIterator::SKIP_DOTS;
473
+                $deleteIterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $targetDir, $flags ), \RecursiveIteratorIterator::CHILD_FIRST );
474
+            }
475
+            $targetDirLen = \strlen( $targetDir );
476
+            foreach ( $deleteIterator as $file ) {
477
+                $origin = $originDir . \substr( $file->getPathname(), $targetDirLen );
478
+                if ( ! $this->exists( $origin ) ) {
479
+                    $this->remove( $file );
480
+                }
481
+            }
482
+        }
483
+        $copyOnWindows = $options['copy_on_windows'] ?? \false;
484
+        if ( null === $iterator ) {
485
+            $flags    = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS;
486
+            $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $originDir, $flags ), \RecursiveIteratorIterator::SELF_FIRST );
487
+        }
488
+        $this->mkdir( $targetDir );
489
+        $filesCreatedWhileMirroring = array();
490
+        foreach ( $iterator as $file ) {
491
+            if ( $file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset( $filesCreatedWhileMirroring[ $file->getRealPath() ] ) ) {
492
+                continue;
493
+            }
494
+            $target                                = $targetDir . \substr( $file->getPathname(), $originDirLen );
495
+            $filesCreatedWhileMirroring[ $target ] = \true;
496
+            if ( ! $copyOnWindows && \is_link( $file ) ) {
497
+                $this->symlink( $file->getLinkTarget(), $target );
498
+            } elseif ( \is_dir( $file ) ) {
499
+                $this->mkdir( $target );
500
+            } elseif ( \is_file( $file ) ) {
501
+                $this->copy( $file, $target, $options['override'] ?? \false );
502
+            } else {
503
+                throw new IOException( \sprintf( 'Unable to guess "%s" file type.', $file ), 0, null, $file );
504
+            }
505
+        }
506
+    }
507
+    /**
508
+     * Returns whether the file path is an absolute path.
509
+     *
510
+     * @param string $file A file path
511
+     *
512
+     * @return bool
513
+     */
514
+    public function isAbsolutePath( $file ) {
515
+        if ( null === $file ) {
516
+            @\trigger_error( \sprintf( 'Calling "%s()" with a null in the $file argument is deprecated since Symfony 4.4.', __METHOD__ ), \E_USER_DEPRECATED );
517
+        }
518
+        return '' !== (string) $file && ( \strspn( $file, '/\\', 0, 1 ) || \strlen( $file ) > 3 && \ctype_alpha( $file[0] ) && ':' === $file[1] && \strspn( $file, '/\\', 2, 1 ) || null !== \parse_url( $file, \PHP_URL_SCHEME ) );
519
+    }
520
+    /**
521
+     * Creates a temporary file with support for custom stream wrappers.
522
+     *
523
+     * @param string $dir    The directory where the temporary filename will be created
524
+     * @param string $prefix The prefix of the generated temporary filename
525
+     *                       Note: Windows uses only the first three characters of prefix
526
+     *
527
+     * @return string The new temporary filename (with path), or throw an exception on failure
528
+     */
529
+    public function tempnam( $dir, $prefix ) {
530
+        [ $scheme, $hierarchy ] = $this->getSchemeAndHierarchy( $dir );
531
+        // If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem
532
+        if ( null === $scheme || 'file' === $scheme || 'gs' === $scheme ) {
533
+            $tmpFile = @\tempnam( $hierarchy, $prefix );
534
+            // If tempnam failed or no scheme return the filename otherwise prepend the scheme
535
+            if ( \false !== $tmpFile ) {
536
+                if ( null !== $scheme && 'gs' !== $scheme ) {
537
+                    return $scheme . '://' . $tmpFile;
538
+                }
539
+                return $tmpFile;
540
+            }
541
+            throw new IOException( 'A temporary file could not be created.' );
542
+        }
543
+        // Loop until we create a valid temp file or have reached 10 attempts
544
+        for ( $i = 0; $i < 10; ++$i ) {
545
+            // Create a unique filename
546
+            $tmpFile = $dir . '/' . $prefix . \uniqid( \mt_rand(), \true );
547
+            // Use fopen instead of file_exists as some streams do not support stat
548
+            // Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
549
+            $handle = @\fopen( $tmpFile, 'x+' );
550
+            // If unsuccessful restart the loop
551
+            if ( \false === $handle ) {
552
+                continue;
553
+            }
554
+            // Close the file if it was successfully opened
555
+            @\fclose( $handle );
556
+            return $tmpFile;
557
+        }
558
+        throw new IOException( 'A temporary file could not be created.' );
559
+    }
560
+    /**
561
+     * Atomically dumps content into a file.
562
+     *
563
+     * @param string          $filename The file to be written to
564
+     * @param string|resource $content  The data to write into the file
565
+     *
566
+     * @throws IOException if the file cannot be written to
567
+     */
568
+    public function dumpFile( $filename, $content ) {
569
+        if ( \is_array( $content ) ) {
570
+            @\trigger_error( \sprintf( 'Calling "%s()" with an array in the $content argument is deprecated since Symfony 4.3.', __METHOD__ ), \E_USER_DEPRECATED );
571
+        }
572
+        $dir = \dirname( $filename );
573
+        if ( ! \is_dir( $dir ) ) {
574
+            $this->mkdir( $dir );
575
+        }
576
+        // Will create a temp file with 0600 access rights
577
+        // when the filesystem supports chmod.
578
+        $tmpFile = $this->tempnam( $dir, \basename( $filename ) );
579
+        try {
580
+            if ( \false === @\file_put_contents( $tmpFile, $content ) ) {
581
+                throw new IOException( \sprintf( 'Failed to write file "%s".', $filename ), 0, null, $filename );
582
+            }
583
+            @\chmod( $tmpFile, \file_exists( $filename ) ? \fileperms( $filename ) : 0666 & ~\umask() );
584
+            $this->rename( $tmpFile, $filename, \true );
585
+        } finally {
586
+            if ( \file_exists( $tmpFile ) ) {
587
+                @\unlink( $tmpFile );
588
+            }
589
+        }
590
+    }
591
+    /**
592
+     * Appends content to an existing file.
593
+     *
594
+     * @param string          $filename The file to which to append content
595
+     * @param string|resource $content  The content to append
596
+     *
597
+     * @throws IOException If the file is not writable
598
+     */
599
+    public function appendToFile( $filename, $content ) {
600
+        if ( \is_array( $content ) ) {
601
+            @\trigger_error( \sprintf( 'Calling "%s()" with an array in the $content argument is deprecated since Symfony 4.3.', __METHOD__ ), \E_USER_DEPRECATED );
602
+        }
603
+        $dir = \dirname( $filename );
604
+        if ( ! \is_dir( $dir ) ) {
605
+            $this->mkdir( $dir );
606
+        }
607
+        if ( \false === @\file_put_contents( $filename, $content, \FILE_APPEND ) ) {
608
+            throw new IOException( \sprintf( 'Failed to write file "%s".', $filename ), 0, null, $filename );
609
+        }
610
+    }
611
+    private function toIterable( $files ) : iterable {
612
+        return \is_iterable( $files ) ? $files : array( $files );
613
+    }
614
+    /**
615
+     * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]).
616
+     */
617
+    private function getSchemeAndHierarchy( string $filename ) : array {
618
+        $components = \explode( '://', $filename, 2 );
619
+        return 2 === \count( $components ) ? array( $components[0], $components[1] ) : array( null, $components[0] );
620
+    }
621
+    private static function assertFunctionExists( string $func ) : void {
622
+        if ( ! \function_exists( $func ) ) {
623
+            throw new IOException( \sprintf( 'Unable to perform filesystem operation because the "%s()" function has been disabled.', $func ) );
624
+        }
625
+    }
626
+    /**
627
+     * @param mixed ...$args
628
+     *
629
+     * @return mixed
630
+     */
631
+    private static function box( string $func, ...$args ) {
632
+        self::assertFunctionExists( $func );
633
+        self::$lastError = null;
634
+        \set_error_handler( __CLASS__ . '::handleError' );
635
+        try {
636
+            $result = $func( ...$args );
637
+            \restore_error_handler();
638
+            return $result;
639
+        } catch ( \Throwable $e ) {
640
+        }
641
+        \restore_error_handler();
642
+        throw $e;
643
+    }
644
+    /**
645
+     * @internal
646
+     */
647
+    public static function handleError( int $type, string $msg ) {
648
+        self::$lastError = $msg;
649
+    }
650 650
 }
Please login to merge, or discard this patch.
src/modules/common/third-party/vendor/symfony/polyfill-php80/PhpToken.php 1 patch
Indentation   +64 added lines, -64 removed lines patch added patch discarded remove patch
@@ -17,68 +17,68 @@
 block discarded – undo
17 17
  */
18 18
 class PhpToken implements \Stringable {
19 19
 
20
-	/**
21
-	 * @var int
22
-	 */
23
-	public $id;
24
-	/**
25
-	 * @var string
26
-	 */
27
-	public $text;
28
-	/**
29
-	 * @var int
30
-	 */
31
-	public $line;
32
-	/**
33
-	 * @var int
34
-	 */
35
-	public $pos;
36
-	public function __construct( int $id, string $text, int $line = -1, int $position = -1 ) {
37
-		$this->id   = $id;
38
-		$this->text = $text;
39
-		$this->line = $line;
40
-		$this->pos  = $position;
41
-	}
42
-	public function getTokenName() : ?string {
43
-		if ( 'UNKNOWN' === ( $name = \token_name( $this->id ) ) ) {
44
-			$name = \strlen( $this->text ) > 1 || \ord( $this->text ) < 32 ? null : $this->text;
45
-		}
46
-		return $name;
47
-	}
48
-	/**
49
-	 * @param int|string|array $kind
50
-	 */
51
-	public function is( $kind ) : bool {
52
-		foreach ( (array) $kind as $value ) {
53
-			if ( \in_array( $value, array( $this->id, $this->text ), \true ) ) {
54
-				return \true;
55
-			}
56
-		}
57
-		return \false;
58
-	}
59
-	public function isIgnorable() : bool {
60
-		return \in_array( $this->id, array( \T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG ), \true );
61
-	}
62
-	public function __toString() : string {
63
-		return (string) $this->text;
64
-	}
65
-	/**
66
-	 * @return static[]
67
-	 */
68
-	public static function tokenize( string $code, int $flags = 0 ) : array {
69
-		$line     = 1;
70
-		$position = 0;
71
-		$tokens   = \token_get_all( $code, $flags );
72
-		foreach ( $tokens as $index => $token ) {
73
-			if ( \is_string( $token ) ) {
74
-				$id   = \ord( $token );
75
-				$text = $token;
76
-			} else {
77
-				[ $id, $text, $line ] = $token;
78
-			}
79
-			$tokens[ $index ] = new static( $id, $text, $line, $position );
80
-			$position        += \strlen( $text );
81
-		}
82
-		return $tokens;
83
-	}
20
+    /**
21
+     * @var int
22
+     */
23
+    public $id;
24
+    /**
25
+     * @var string
26
+     */
27
+    public $text;
28
+    /**
29
+     * @var int
30
+     */
31
+    public $line;
32
+    /**
33
+     * @var int
34
+     */
35
+    public $pos;
36
+    public function __construct( int $id, string $text, int $line = -1, int $position = -1 ) {
37
+        $this->id   = $id;
38
+        $this->text = $text;
39
+        $this->line = $line;
40
+        $this->pos  = $position;
41
+    }
42
+    public function getTokenName() : ?string {
43
+        if ( 'UNKNOWN' === ( $name = \token_name( $this->id ) ) ) {
44
+            $name = \strlen( $this->text ) > 1 || \ord( $this->text ) < 32 ? null : $this->text;
45
+        }
46
+        return $name;
47
+    }
48
+    /**
49
+     * @param int|string|array $kind
50
+     */
51
+    public function is( $kind ) : bool {
52
+        foreach ( (array) $kind as $value ) {
53
+            if ( \in_array( $value, array( $this->id, $this->text ), \true ) ) {
54
+                return \true;
55
+            }
56
+        }
57
+        return \false;
58
+    }
59
+    public function isIgnorable() : bool {
60
+        return \in_array( $this->id, array( \T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG ), \true );
61
+    }
62
+    public function __toString() : string {
63
+        return (string) $this->text;
64
+    }
65
+    /**
66
+     * @return static[]
67
+     */
68
+    public static function tokenize( string $code, int $flags = 0 ) : array {
69
+        $line     = 1;
70
+        $position = 0;
71
+        $tokens   = \token_get_all( $code, $flags );
72
+        foreach ( $tokens as $index => $token ) {
73
+            if ( \is_string( $token ) ) {
74
+                $id   = \ord( $token );
75
+                $text = $token;
76
+            } else {
77
+                [ $id, $text, $line ] = $token;
78
+            }
79
+            $tokens[ $index ] = new static( $id, $text, $line, $position );
80
+            $position        += \strlen( $text );
81
+        }
82
+        return $tokens;
83
+    }
84 84
 }
Please login to merge, or discard this patch.