Completed
Push — master ( f3a6aa...8848c4 )
by Naveen
01:08
created
src/modules/common/third-party/vendor/symfony/filesystem/Filesystem.php 2 patches
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.
Spacing   +244 added lines, -244 removed lines patch added patch discarded remove patch
@@ -35,37 +35,37 @@  discard block
 block discarded – undo
35 35
 	 * @throws FileNotFoundException When originFile doesn't exist
36 36
 	 * @throws IOException           When copy fails
37 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 );
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 42
 		}
43
-		$this->mkdir( \dirname( $targetFile ) );
43
+		$this->mkdir(\dirname($targetFile));
44 44
 		$doCopy = \true;
45
-		if ( ! $overwriteNewerFiles && null === \parse_url( $originFile, \PHP_URL_HOST ) && \is_file( $targetFile ) ) {
46
-			$doCopy = \filemtime( $originFile ) > \filemtime( $targetFile );
45
+		if ( ! $overwriteNewerFiles && null === \parse_url($originFile, \PHP_URL_HOST) && \is_file($targetFile)) {
46
+			$doCopy = \filemtime($originFile) > \filemtime($targetFile);
47 47
 		}
48
-		if ( $doCopy ) {
48
+		if ($doCopy) {
49 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 );
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 52
 			}
53 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 );
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 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 );
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 63
 			}
64
-			if ( $originIsLocal ) {
64
+			if ($originIsLocal) {
65 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 );
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 69
 				}
70 70
 			}
71 71
 		}
@@ -78,18 +78,18 @@  discard block
 block discarded – undo
78 78
 	 *
79 79
 	 * @throws IOException On any directory creation failure
80 80
 	 */
81
-	public function mkdir( $dirs, $mode = 0777 ) {
82
-		foreach ( $this->toIterable( $dirs ) as $dir ) {
83
-			if ( \is_dir( $dir ) ) {
81
+	public function mkdir($dirs, $mode = 0777) {
82
+		foreach ($this->toIterable($dirs) as $dir) {
83
+			if (\is_dir($dir)) {
84 84
 				continue;
85 85
 			}
86
-			if ( ! self::box( 'mkdir', $dir, $mode, \true ) ) {
87
-				if ( ! \is_dir( $dir ) ) {
86
+			if ( ! self::box('mkdir', $dir, $mode, \true)) {
87
+				if ( ! \is_dir($dir)) {
88 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 );
89
+					if (self::$lastError) {
90
+						throw new IOException(\sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir);
91 91
 					}
92
-					throw new IOException( \sprintf( 'Failed to create "%s".', $dir ), 0, null, $dir );
92
+					throw new IOException(\sprintf('Failed to create "%s".', $dir), 0, null, $dir);
93 93
 				}
94 94
 			}
95 95
 		}
@@ -101,13 +101,13 @@  discard block
 block discarded – undo
101 101
 	 *
102 102
 	 * @return bool true if the file exists, false otherwise
103 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 );
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 109
 			}
110
-			if ( ! \file_exists( $file ) ) {
110
+			if ( ! \file_exists($file)) {
111 111
 				return \false;
112 112
 			}
113 113
 		}
@@ -122,11 +122,11 @@  discard block
 block discarded – undo
122 122
 	 *
123 123
 	 * @throws IOException When touch fails
124 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 );
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 130
 			}
131 131
 		}
132 132
 	}
@@ -137,26 +137,26 @@  discard block
 block discarded – undo
137 137
 	 *
138 138
 	 * @throws IOException When removal fails
139 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 ) ) {
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 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 );
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 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 );
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 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 );
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 160
 			}
161 161
 		}
162 162
 	}
@@ -170,13 +170,13 @@  discard block
 block discarded – undo
170 170
 	 *
171 171
 	 * @throws IOException When the change fails
172 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 );
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 177
 			}
178
-			if ( $recursive && \is_dir( $file ) && ! \is_link( $file ) ) {
179
-				$this->chmod( new \FilesystemIterator( $file ), $mode, $umask, \true );
178
+			if ($recursive && \is_dir($file) && ! \is_link($file)) {
179
+				$this->chmod(new \FilesystemIterator($file), $mode, $umask, \true);
180 180
 			}
181 181
 		}
182 182
 	}
@@ -189,18 +189,18 @@  discard block
 block discarded – undo
189 189
 	 *
190 190
 	 * @throws IOException When the change fails
191 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 );
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 200
 				}
201 201
 			} else {
202
-				if ( \true !== @\chown( $file, $user ) ) {
203
-					throw new IOException( \sprintf( 'Failed to chown file "%s".', $file ), 0, null, $file );
202
+				if (\true !== @\chown($file, $user)) {
203
+					throw new IOException(\sprintf('Failed to chown file "%s".', $file), 0, null, $file);
204 204
 				}
205 205
 			}
206 206
 		}
@@ -214,18 +214,18 @@  discard block
 block discarded – undo
214 214
 	 *
215 215
 	 * @throws IOException When the change fails
216 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 );
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 225
 				}
226 226
 			} else {
227
-				if ( \true !== @\chgrp( $file, $group ) ) {
228
-					throw new IOException( \sprintf( 'Failed to chgrp file "%s".', $file ), 0, null, $file );
227
+				if (\true !== @\chgrp($file, $group)) {
228
+					throw new IOException(\sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
229 229
 				}
230 230
 			}
231 231
 		}
@@ -240,13 +240,13 @@  discard block
 block discarded – undo
240 240
 	 * @throws IOException When target file or directory already exists
241 241
 	 * @throws IOException When origin cannot be renamed
242 242
 	 */
243
-	public function rename( $origin, $target, $overwrite = \false ) {
243
+	public function rename($origin, $target, $overwrite = \false) {
244 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 );
245
+		if ( ! $overwrite && $this->isReadable($target)) {
246
+			throw new IOException(\sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
247 247
 		}
248
-		if ( \true !== @\rename( $origin, $target ) ) {
249
-			if ( \is_dir( $origin ) ) {
248
+		if (\true !== @\rename($origin, $target)) {
249
+			if (\is_dir($origin)) {
250 250
 				// See https://bugs.php.net/54097 & https://php.net/rename#113943
251 251
 				$this->mirror(
252 252
 					$origin,
@@ -257,10 +257,10 @@  discard block
 block discarded – undo
257 257
 						'delete'   => $overwrite,
258 258
 					)
259 259
 				);
260
-				$this->remove( $origin );
260
+				$this->remove($origin);
261 261
 				return;
262 262
 			}
263
-			throw new IOException( \sprintf( 'Cannot rename "%s" to "%s".', $origin, $target ), 0, null, $target );
263
+			throw new IOException(\sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target);
264 264
 		}
265 265
 	}
266 266
 	/**
@@ -268,12 +268,12 @@  discard block
 block discarded – undo
268 268
 	 *
269 269
 	 * @throws IOException When windows path is longer than 258 characters
270 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 );
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 275
 		}
276
-		return \is_readable( $filename );
276
+		return \is_readable($filename);
277 277
 	}
278 278
 	/**
279 279
 	 * Creates a symbolic link or copy a directory.
@@ -284,25 +284,25 @@  discard block
 block discarded – undo
284 284
 	 *
285 285
 	 * @throws IOException When symlink fails
286 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 );
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 294
 				return;
295 295
 			}
296 296
 		}
297
-		$this->mkdir( \dirname( $targetDir ) );
298
-		if ( \is_link( $targetDir ) ) {
299
-			if ( \readlink( $targetDir ) === $originDir ) {
297
+		$this->mkdir(\dirname($targetDir));
298
+		if (\is_link($targetDir)) {
299
+			if (\readlink($targetDir) === $originDir) {
300 300
 				return;
301 301
 			}
302
-			$this->remove( $targetDir );
302
+			$this->remove($targetDir);
303 303
 		}
304
-		if ( ! self::box( 'symlink', $originDir, $targetDir ) ) {
305
-			$this->linkException( $originDir, $targetDir, 'symbolic' );
304
+		if ( ! self::box('symlink', $originDir, $targetDir)) {
305
+			$this->linkException($originDir, $targetDir, 'symbolic');
306 306
 		}
307 307
 	}
308 308
 	/**
@@ -314,36 +314,36 @@  discard block
 block discarded – undo
314 314
 	 * @throws FileNotFoundException When original file is missing or not a file
315 315
 	 * @throws IOException           When link fails, including if link already exists
316 316
 	 */
317
-	public function hardlink( $originFile, $targetFiles ) {
318
-		self::assertFunctionExists( 'link' );
319
-		if ( ! $this->exists( $originFile ) ) {
320
-			throw new FileNotFoundException( null, 0, null, $originFile );
317
+	public function hardlink($originFile, $targetFiles) {
318
+		self::assertFunctionExists('link');
319
+		if ( ! $this->exists($originFile)) {
320
+			throw new FileNotFoundException(null, 0, null, $originFile);
321 321
 		}
322
-		if ( ! \is_file( $originFile ) ) {
323
-			throw new FileNotFoundException( \sprintf( 'Origin file "%s" is not a file.', $originFile ) );
322
+		if ( ! \is_file($originFile)) {
323
+			throw new FileNotFoundException(\sprintf('Origin file "%s" is not a file.', $originFile));
324 324
 		}
325
-		foreach ( $this->toIterable( $targetFiles ) as $targetFile ) {
326
-			if ( \is_file( $targetFile ) ) {
327
-				if ( \fileinode( $originFile ) === \fileinode( $targetFile ) ) {
325
+		foreach ($this->toIterable($targetFiles) as $targetFile) {
326
+			if (\is_file($targetFile)) {
327
+				if (\fileinode($originFile) === \fileinode($targetFile)) {
328 328
 					continue;
329 329
 				}
330
-				$this->remove( $targetFile );
330
+				$this->remove($targetFile);
331 331
 			}
332
-			if ( ! self::box( 'link', $originFile, $targetFile ) ) {
333
-				$this->linkException( $originFile, $targetFile, 'hard' );
332
+			if ( ! self::box('link', $originFile, $targetFile)) {
333
+				$this->linkException($originFile, $targetFile, 'hard');
334 334
 			}
335 335
 		}
336 336
 	}
337 337
 	/**
338 338
 	 * @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
339 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 );
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 344
 			}
345 345
 		}
346
-		throw new IOException( \sprintf( 'Failed to create "%s" link from "%s" to "%s".', $linkType, $origin, $target ), 0, null, $target );
346
+		throw new IOException(\sprintf('Failed to create "%s" link from "%s" to "%s".', $linkType, $origin, $target), 0, null, $target);
347 347
 	}
348 348
 	/**
349 349
 	 * Resolves links in paths.
@@ -361,23 +361,23 @@  discard block
 block discarded – undo
361 361
 	 *
362 362
 	 * @return string|null
363 363
 	 */
364
-	public function readlink( $path, $canonicalize = \false ) {
365
-		if ( ! $canonicalize && ! \is_link( $path ) ) {
364
+	public function readlink($path, $canonicalize = \false) {
365
+		if ( ! $canonicalize && ! \is_link($path)) {
366 366
 			return null;
367 367
 		}
368
-		if ( $canonicalize ) {
369
-			if ( ! $this->exists( $path ) ) {
368
+		if ($canonicalize) {
369
+			if ( ! $this->exists($path)) {
370 370
 				return null;
371 371
 			}
372
-			if ( '\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70410 ) {
373
-				$path = \readlink( $path );
372
+			if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70410) {
373
+				$path = \readlink($path);
374 374
 			}
375
-			return \realpath( $path );
375
+			return \realpath($path);
376 376
 		}
377
-		if ( '\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70400 ) {
378
-			return \realpath( $path );
377
+		if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70400) {
378
+			return \realpath($path);
379 379
 		}
380
-		return \readlink( $path );
380
+		return \readlink($path);
381 381
 	}
382 382
 	/**
383 383
 	 * Given an existing path, convert it to a path relative to a given starting path.
@@ -387,56 +387,56 @@  discard block
 block discarded – undo
387 387
 	 *
388 388
 	 * @return string Path of target relative to starting path
389 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 ) );
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 393
 		}
394
-		if ( ! $this->isAbsolutePath( $endPath ) ) {
395
-			throw new InvalidArgumentException( \sprintf( 'The end path "%s" is not absolute.', $endPath ) );
394
+		if ( ! $this->isAbsolutePath($endPath)) {
395
+			throw new InvalidArgumentException(\sprintf('The end path "%s" is not absolute.', $endPath));
396 396
 		}
397 397
 		// Normalize separators on Windows
398
-		if ( '\\' === \DIRECTORY_SEPARATOR ) {
399
-			$endPath   = \str_replace( '\\', '/', $endPath );
400
-			$startPath = \str_replace( '\\', '/', $startPath );
398
+		if ('\\' === \DIRECTORY_SEPARATOR) {
399
+			$endPath   = \str_replace('\\', '/', $endPath);
400
+			$startPath = \str_replace('\\', '/', $startPath);
401 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 );
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 404
 		};
405
-		$splitPath                             = function ( $path ) {
405
+		$splitPath                             = function($path) {
406 406
 			$result = array();
407
-			foreach ( \explode( '/', \trim( $path, '/' ) ) as $segment ) {
408
-				if ( '..' === $segment ) {
409
-					\array_pop( $result );
410
-				} elseif ( '.' !== $segment && '' !== $segment ) {
407
+			foreach (\explode('/', \trim($path, '/')) as $segment) {
408
+				if ('..' === $segment) {
409
+					\array_pop($result);
410
+				} elseif ('.' !== $segment && '' !== $segment) {
411 411
 					$result[] = $segment;
412 412
 				}
413 413
 			}
414 414
 			return $result;
415 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 ) {
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 421
 			// End path is on another drive, so no relative path exists
422
-			return $endDriveLetter . ':/' . ( $endPathArr ? \implode( '/', $endPathArr ) . '/' : '' );
422
+			return $endDriveLetter.':/'.($endPathArr ? \implode('/', $endPathArr).'/' : '');
423 423
 		}
424 424
 		// Find for which directory the common path stops
425 425
 		$index = 0;
426
-		while ( isset( $startPathArr[ $index ] ) && isset( $endPathArr[ $index ] ) && $startPathArr[ $index ] === $endPathArr[ $index ] ) {
426
+		while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
427 427
 			++$index;
428 428
 		}
429 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] ) {
430
+		if (1 === \count($startPathArr) && '' === $startPathArr[0]) {
431 431
 			$depth = 0;
432 432
 		} else {
433
-			$depth = \count( $startPathArr ) - $index;
433
+			$depth = \count($startPathArr) - $index;
434 434
 		}
435 435
 		// Repeated "../" for each level need to reach the common path
436
-		$traverser        = \str_repeat( '../', $depth );
437
-		$endPathRemainder = \implode( '/', \array_slice( $endPathArr, $index ) );
436
+		$traverser        = \str_repeat('../', $depth);
437
+		$endPathRemainder = \implode('/', \array_slice($endPathArr, $index));
438 438
 		// Construct $endPath from traversing to the common path, then to the remaining $endPath
439
-		$relativePath = $traverser . ( '' !== $endPathRemainder ? $endPathRemainder . '/' : '' );
439
+		$relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');
440 440
 		return '' === $relativePath ? './' : $relativePath;
441 441
 	}
442 442
 	/**
@@ -458,49 +458,49 @@  discard block
 block discarded – undo
458 458
 	 *
459 459
 	 * @throws IOException When file type is unknown
460 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 );
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 467
 		}
468 468
 		// Iterate in destination folder to remove obsolete entries
469
-		if ( $this->exists( $targetDir ) && isset( $options['delete'] ) && $options['delete'] ) {
469
+		if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) {
470 470
 			$deleteIterator = $iterator;
471
-			if ( null === $deleteIterator ) {
471
+			if (null === $deleteIterator) {
472 472
 				$flags          = \FilesystemIterator::SKIP_DOTS;
473
-				$deleteIterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $targetDir, $flags ), \RecursiveIteratorIterator::CHILD_FIRST );
473
+				$deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
474 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 );
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 480
 				}
481 481
 			}
482 482
 		}
483 483
 		$copyOnWindows = $options['copy_on_windows'] ?? \false;
484
-		if ( null === $iterator ) {
484
+		if (null === $iterator) {
485 485
 			$flags    = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS;
486
-			$iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $originDir, $flags ), \RecursiveIteratorIterator::SELF_FIRST );
486
+			$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST);
487 487
 		}
488
-		$this->mkdir( $targetDir );
488
+		$this->mkdir($targetDir);
489 489
 		$filesCreatedWhileMirroring = array();
490
-		foreach ( $iterator as $file ) {
491
-			if ( $file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset( $filesCreatedWhileMirroring[ $file->getRealPath() ] ) ) {
490
+		foreach ($iterator as $file) {
491
+			if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) {
492 492
 				continue;
493 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 );
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 502
 			} else {
503
-				throw new IOException( \sprintf( 'Unable to guess "%s" file type.', $file ), 0, null, $file );
503
+				throw new IOException(\sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
504 504
 			}
505 505
 		}
506 506
 	}
@@ -511,11 +511,11 @@  discard block
 block discarded – undo
511 511
 	 *
512 512
 	 * @return bool
513 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 );
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 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 ) );
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 519
 	}
520 520
 	/**
521 521
 	 * Creates a temporary file with support for custom stream wrappers.
@@ -526,36 +526,36 @@  discard block
 block discarded – undo
526 526
 	 *
527 527
 	 * @return string The new temporary filename (with path), or throw an exception on failure
528 528
 	 */
529
-	public function tempnam( $dir, $prefix ) {
530
-		[ $scheme, $hierarchy ] = $this->getSchemeAndHierarchy( $dir );
529
+	public function tempnam($dir, $prefix) {
530
+		[$scheme, $hierarchy] = $this->getSchemeAndHierarchy($dir);
531 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 );
532
+		if (null === $scheme || 'file' === $scheme || 'gs' === $scheme) {
533
+			$tmpFile = @\tempnam($hierarchy, $prefix);
534 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;
535
+			if (\false !== $tmpFile) {
536
+				if (null !== $scheme && 'gs' !== $scheme) {
537
+					return $scheme.'://'.$tmpFile;
538 538
 				}
539 539
 				return $tmpFile;
540 540
 			}
541
-			throw new IOException( 'A temporary file could not be created.' );
541
+			throw new IOException('A temporary file could not be created.');
542 542
 		}
543 543
 		// Loop until we create a valid temp file or have reached 10 attempts
544
-		for ( $i = 0; $i < 10; ++$i ) {
544
+		for ($i = 0; $i < 10; ++$i) {
545 545
 			// Create a unique filename
546
-			$tmpFile = $dir . '/' . $prefix . \uniqid( \mt_rand(), \true );
546
+			$tmpFile = $dir.'/'.$prefix.\uniqid(\mt_rand(), \true);
547 547
 			// Use fopen instead of file_exists as some streams do not support stat
548 548
 			// Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
549
-			$handle = @\fopen( $tmpFile, 'x+' );
549
+			$handle = @\fopen($tmpFile, 'x+');
550 550
 			// If unsuccessful restart the loop
551
-			if ( \false === $handle ) {
551
+			if (\false === $handle) {
552 552
 				continue;
553 553
 			}
554 554
 			// Close the file if it was successfully opened
555
-			@\fclose( $handle );
555
+			@\fclose($handle);
556 556
 			return $tmpFile;
557 557
 		}
558
-		throw new IOException( 'A temporary file could not be created.' );
558
+		throw new IOException('A temporary file could not be created.');
559 559
 	}
560 560
 	/**
561 561
 	 * Atomically dumps content into a file.
@@ -565,26 +565,26 @@  discard block
 block discarded – undo
565 565
 	 *
566 566
 	 * @throws IOException if the file cannot be written to
567 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 );
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 571
 		}
572
-		$dir = \dirname( $filename );
573
-		if ( ! \is_dir( $dir ) ) {
574
-			$this->mkdir( $dir );
572
+		$dir = \dirname($filename);
573
+		if ( ! \is_dir($dir)) {
574
+			$this->mkdir($dir);
575 575
 		}
576 576
 		// Will create a temp file with 0600 access rights
577 577
 		// when the filesystem supports chmod.
578
-		$tmpFile = $this->tempnam( $dir, \basename( $filename ) );
578
+		$tmpFile = $this->tempnam($dir, \basename($filename));
579 579
 		try {
580
-			if ( \false === @\file_put_contents( $tmpFile, $content ) ) {
581
-				throw new IOException( \sprintf( 'Failed to write file "%s".', $filename ), 0, null, $filename );
580
+			if (\false === @\file_put_contents($tmpFile, $content)) {
581
+				throw new IOException(\sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
582 582
 			}
583
-			@\chmod( $tmpFile, \file_exists( $filename ) ? \fileperms( $filename ) : 0666 & ~\umask() );
584
-			$this->rename( $tmpFile, $filename, \true );
583
+			@\chmod($tmpFile, \file_exists($filename) ? \fileperms($filename) : 0666 & ~\umask());
584
+			$this->rename($tmpFile, $filename, \true);
585 585
 		} finally {
586
-			if ( \file_exists( $tmpFile ) ) {
587
-				@\unlink( $tmpFile );
586
+			if (\file_exists($tmpFile)) {
587
+				@\unlink($tmpFile);
588 588
 			}
589 589
 		}
590 590
 	}
@@ -596,31 +596,31 @@  discard block
 block discarded – undo
596 596
 	 *
597 597
 	 * @throws IOException If the file is not writable
598 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 );
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 602
 		}
603
-		$dir = \dirname( $filename );
604
-		if ( ! \is_dir( $dir ) ) {
605
-			$this->mkdir( $dir );
603
+		$dir = \dirname($filename);
604
+		if ( ! \is_dir($dir)) {
605
+			$this->mkdir($dir);
606 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 );
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 609
 		}
610 610
 	}
611
-	private function toIterable( $files ) : iterable {
612
-		return \is_iterable( $files ) ? $files : array( $files );
611
+	private function toIterable($files) : iterable {
612
+		return \is_iterable($files) ? $files : array($files);
613 613
 	}
614 614
 	/**
615 615
 	 * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]).
616 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] );
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 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 ) );
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 624
 		}
625 625
 	}
626 626
 	/**
@@ -628,15 +628,15 @@  discard block
 block discarded – undo
628 628
 	 *
629 629
 	 * @return mixed
630 630
 	 */
631
-	private static function box( string $func, ...$args ) {
632
-		self::assertFunctionExists( $func );
631
+	private static function box(string $func, ...$args) {
632
+		self::assertFunctionExists($func);
633 633
 		self::$lastError = null;
634
-		\set_error_handler( __CLASS__ . '::handleError' );
634
+		\set_error_handler(__CLASS__.'::handleError');
635 635
 		try {
636
-			$result = $func( ...$args );
636
+			$result = $func(...$args);
637 637
 			\restore_error_handler();
638 638
 			return $result;
639
-		} catch ( \Throwable $e ) {
639
+		} catch (\Throwable $e) {
640 640
 		}
641 641
 		\restore_error_handler();
642 642
 		throw $e;
@@ -644,7 +644,7 @@  discard block
 block discarded – undo
644 644
 	/**
645 645
 	 * @internal
646 646
 	 */
647
-	public static function handleError( int $type, string $msg ) {
647
+	public static function handleError(int $type, string $msg) {
648 648
 		self::$lastError = $msg;
649 649
 	}
650 650
 }
Please login to merge, or discard this patch.
src/modules/common/third-party/vendor/symfony/polyfill-php80/PhpToken.php 2 patches
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.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -33,31 +33,31 @@  discard block
 block discarded – undo
33 33
 	 * @var int
34 34
 	 */
35 35
 	public $pos;
36
-	public function __construct( int $id, string $text, int $line = -1, int $position = -1 ) {
36
+	public function __construct(int $id, string $text, int $line = -1, int $position = -1) {
37 37
 		$this->id   = $id;
38 38
 		$this->text = $text;
39 39
 		$this->line = $line;
40 40
 		$this->pos  = $position;
41 41
 	}
42 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;
43
+		if ('UNKNOWN' === ($name = \token_name($this->id))) {
44
+			$name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text;
45 45
 		}
46 46
 		return $name;
47 47
 	}
48 48
 	/**
49 49
 	 * @param int|string|array $kind
50 50
 	 */
51
-	public function is( $kind ) : bool {
52
-		foreach ( (array) $kind as $value ) {
53
-			if ( \in_array( $value, array( $this->id, $this->text ), \true ) ) {
51
+	public function is($kind) : bool {
52
+		foreach ((array) $kind as $value) {
53
+			if (\in_array($value, array($this->id, $this->text), \true)) {
54 54
 				return \true;
55 55
 			}
56 56
 		}
57 57
 		return \false;
58 58
 	}
59 59
 	public function isIgnorable() : bool {
60
-		return \in_array( $this->id, array( \T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG ), \true );
60
+		return \in_array($this->id, array(\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG), \true);
61 61
 	}
62 62
 	public function __toString() : string {
63 63
 		return (string) $this->text;
@@ -65,19 +65,19 @@  discard block
 block discarded – undo
65 65
 	/**
66 66
 	 * @return static[]
67 67
 	 */
68
-	public static function tokenize( string $code, int $flags = 0 ) : array {
68
+	public static function tokenize(string $code, int $flags = 0) : array {
69 69
 		$line     = 1;
70 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 );
71
+		$tokens   = \token_get_all($code, $flags);
72
+		foreach ($tokens as $index => $token) {
73
+			if (\is_string($token)) {
74
+				$id   = \ord($token);
75 75
 				$text = $token;
76 76
 			} else {
77
-				[ $id, $text, $line ] = $token;
77
+				[$id, $text, $line] = $token;
78 78
 			}
79
-			$tokens[ $index ] = new static( $id, $text, $line, $position );
80
-			$position        += \strlen( $text );
79
+			$tokens[$index] = new static($id, $text, $line, $position);
80
+			$position        += \strlen($text);
81 81
 		}
82 82
 		return $tokens;
83 83
 	}
Please login to merge, or discard this patch.