196 lines
5.5 KiB
PHP
196 lines
5.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Utilities;
|
|
|
|
use FilesystemIterator;
|
|
use InvalidArgumentException;
|
|
use RecursiveDirectoryIterator;
|
|
use RecursiveIteratorIterator;
|
|
use SplFileInfo;
|
|
use Symfony\Component\Filesystem\Filesystem;
|
|
use Symfony\Component\Filesystem\Path;
|
|
|
|
use function stripos;
|
|
use function strlen;
|
|
|
|
/**
|
|
* Static class that facilitates the uploading, reading and deletion of files in a controlled directory.
|
|
*/
|
|
final class File
|
|
{
|
|
/**
|
|
* @param string $path
|
|
*/
|
|
public static function sanitizePathPrefix(string $path): string
|
|
{
|
|
$pattern = '/:\/\//';
|
|
$path = preg_replace($pattern, '', $path) ?? $path;
|
|
|
|
if (preg_match($pattern, $path)) {
|
|
return self::sanitizePathPrefix($path);
|
|
}
|
|
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Sanitize a user-specified filename for storage.
|
|
* Credit to: http://stackoverflow.com/a/19018736
|
|
*
|
|
* @param string $str
|
|
*/
|
|
public static function sanitizeFileName(string $str): string
|
|
{
|
|
return Strings::getProgrammaticString($str);
|
|
}
|
|
|
|
public static function generateTempPath(string $pattern = ''): string
|
|
{
|
|
$prefix = Path::getFilenameWithoutExtension($pattern) ?: 'temp';
|
|
$extension = Path::getExtension($pattern) ?: 'log';
|
|
|
|
return (new Filesystem())->tempnam(
|
|
sys_get_temp_dir(),
|
|
$prefix . '_',
|
|
'.' . $extension
|
|
);
|
|
}
|
|
|
|
public static function validateTempPath(string $path): string
|
|
{
|
|
$tempDir = sys_get_temp_dir();
|
|
$fullPath = Path::makeAbsolute($path, $tempDir);
|
|
|
|
if (!Path::isBasePath($tempDir, $fullPath)) {
|
|
throw new InvalidArgumentException(
|
|
sprintf('Path "%s" is not within "%s".', $fullPath, $tempDir)
|
|
);
|
|
}
|
|
|
|
return $fullPath;
|
|
}
|
|
|
|
/**
|
|
* Given a "from" and "to" directory, update the given path to be relative to the "to" directory.
|
|
* If $preserveFolderStructure is set to "true" (default), this will preserve the folder structure
|
|
* that's underneath "fromDir". If it's set to "false", it will use the basename of the files and
|
|
* drop them directly into the "toDir".
|
|
*/
|
|
public static function renameDirectoryInPath(
|
|
string $path,
|
|
string $fromDir,
|
|
string $toDir,
|
|
bool $preserveFolderStructure = true
|
|
): string {
|
|
if (!$preserveFolderStructure) {
|
|
$basename = basename($path);
|
|
return ('' === $toDir)
|
|
? $basename
|
|
: $toDir . '/' . $basename;
|
|
}
|
|
|
|
if ('' === $fromDir && '' !== $toDir) {
|
|
// Just prepend the new directory.
|
|
return $toDir . '/' . $path;
|
|
}
|
|
|
|
if (0 === stripos($path, $fromDir)) {
|
|
$newBasePath = ltrim(substr($path, strlen($fromDir)), '/');
|
|
if ('' !== $toDir) {
|
|
return $toDir . '/' . $newBasePath;
|
|
}
|
|
return $newBasePath;
|
|
}
|
|
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Clear all contents of a directory, without removing the directory itself.
|
|
*/
|
|
public static function clearDirectoryContents(string $targetDir): void
|
|
{
|
|
$targetDir = rtrim($targetDir, '/\\');
|
|
|
|
$flags = FilesystemIterator::SKIP_DOTS;
|
|
$deleteIterator = new RecursiveIteratorIterator(
|
|
new RecursiveDirectoryIterator($targetDir, $flags),
|
|
RecursiveIteratorIterator::CHILD_FIRST
|
|
);
|
|
|
|
$fsUtils = new Filesystem();
|
|
|
|
/** @var SplFileInfo $file */
|
|
foreach ($deleteIterator as $file) {
|
|
$fsUtils->remove((string)$file);
|
|
}
|
|
}
|
|
|
|
public static function moveDirectoryContents(
|
|
string $originDir,
|
|
string $targetDir,
|
|
bool $clearDirectoryFirst = false
|
|
): void {
|
|
if ($clearDirectoryFirst) {
|
|
self::clearDirectoryContents($targetDir);
|
|
}
|
|
|
|
$targetDir = rtrim($targetDir, '/\\');
|
|
$originDir = rtrim($originDir, '/\\');
|
|
$originDirLen = strlen($originDir);
|
|
|
|
$flags = FilesystemIterator::SKIP_DOTS;
|
|
$iterator = new RecursiveIteratorIterator(
|
|
new RecursiveDirectoryIterator($originDir, $flags),
|
|
RecursiveIteratorIterator::SELF_FIRST
|
|
);
|
|
|
|
$fsUtils = new Filesystem();
|
|
$fsUtils->mkdir($targetDir);
|
|
|
|
/** @var SplFileInfo $file */
|
|
foreach ($iterator as $file) {
|
|
if (
|
|
$file->getPathname() === $targetDir
|
|
|| $file->getRealPath() === $targetDir
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
$target = $targetDir . substr($file->getPathname(), $originDirLen);
|
|
|
|
if (is_link((string)$file)) {
|
|
$fsUtils->symlink($file->getLinkTarget(), $target);
|
|
} elseif (is_dir((string)$file)) {
|
|
$fsUtils->mkdir($target);
|
|
} elseif (is_file((string)$file)) {
|
|
$fsUtils->rename((string)$file, $target, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function getFirstExistingFile(array $files): string
|
|
{
|
|
foreach ($files as $file) {
|
|
if (file_exists($file)) {
|
|
return $file;
|
|
}
|
|
}
|
|
|
|
throw new InvalidArgumentException('No existing files found.');
|
|
}
|
|
|
|
public static function getFirstExistingDirectory(array $dirs): string
|
|
{
|
|
foreach ($dirs as $dir) {
|
|
if (is_dir($dir)) {
|
|
return $dir;
|
|
}
|
|
}
|
|
|
|
throw new InvalidArgumentException('No existing directories found.');
|
|
}
|
|
}
|