isDir() && !$file->isDot()) { $result[] = $file->getFilename(); } } return $result; } /** * Подсчет размера файлов(поддиректорий) в директории * @param string $path путь к директории * @return int */ public static function getDirSize(string $path): int { $size = 0; foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)) as $file) { $size += $file->getSize(); } return $size; } /** * Рекурсивный обход директорий * @param string $directory корневая директория * @param string $base путь относительно корневой директории * @param bool $recursive рекурсивный обход * @param bool $fullPath true - возвращать полный путь, false - только имя файла/директории * @param array $fileTypes типы файлов * @param array $exclude список исключений * @return array|bool */ public static function getFiles( string $directory, string $base = '', bool $recursive = true, bool $fullPath = true, array $fileTypes = [], array $exclude = [] ) { if (is_file($directory) || ! is_dir($directory)) { return false; } $directory = rtrim($directory, '\\/'); if (! is_dir($directory)) { return false; } # Открываем директорию if ($dir = opendir($directory)) { # Формируем список найденных файлов $tmp = []; $sep = DIRECTORY_SEPARATOR; # Добавляем файлы while (($file = readdir($dir)) !== false) { if ($file === '.' || $file === '..') { continue; } $isFile = is_file($directory . $sep . $file); if (! static::validatePath($base, $file, $isFile, $fileTypes, $exclude)) { continue; } if ($isFile) { array_push($tmp, ($fullPath ? $directory . $sep . $file : $file)); } else { # Если директория -> ищем в ней if ($recursive) { $tmpSub = static::getFiles( $directory . $sep . $file, $base . $sep . $file, $recursive, $fullPath, $fileTypes, $exclude ); if (! empty($tmpSub)) { $tmp = array_merge($tmp, $tmpSub); } } } } closedir($dir); return $tmp; } return []; } /** * Копирование директориий (файлов) с вложенностью * @param string $source * @param string $dest * @param int $permissions * @param Closure|null $filter * @return bool */ public static function copyRecursive(string $source, string $dest, int $permissions = 0755, ?Closure $filter = null): bool { if (is_link($source)) { return symlink(readlink($source), $dest); } if (is_file($source)) { return copy($source, $dest); } if (! is_dir($dest)) { mkdir($dest, $permissions); } $dir = dir($source); while (false !== $entry = $dir->read()) { if ($entry == '.' || $entry == '..') { continue; } if ( $filter && $filter($source . DIRECTORY_SEPARATOR . $entry, $dest . DIRECTORY_SEPARATOR . $entry) === false ) { continue; } static::copyRecursive( $source . DIRECTORY_SEPARATOR . $entry, $dest . DIRECTORY_SEPARATOR . $entry, $permissions, $filter ); } $dir->close(); return true; } /** * Удаление директории с вложенностью * @param string $dir * @param bool $deleteSelf * @param bool $traverseSymlinks false - удаляем только симлинки, без удаления реального файла * @return bool */ public static function deleteRecursive(string $dir, bool $deleteSelf = true, bool $traverseSymlinks = false): bool { if (! is_dir($dir)) { return false; } $iterator = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS); $files = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST); foreach ($files as $file) { $realPath = $file->getRealPath(); if ($file->isDir() && ! $file->isLink()) { rmdir($realPath); } else { if ($file->isLink()) { if ($traverseSymlinks && $realPath !== false) { unlink($realPath); } else { unlink($file->getPathname()); } } else { unlink($realPath); } } } if ($deleteSelf) { rmdir($dir); } return true; } /** * Валидация пути файла / директории * @param string $base путь относительно корневой директории * @param string $file имя файла / директории * @param bool $isFile * @param array $fileTypes список суфиксов типов файлов (без точки). Проходят только файлы с указанными суфиксами. * @param array $exclude список исключений * @return bool true - файл / директория валидны */ protected static function validatePath( string $base, string $file, bool $isFile, array $fileTypes, array $exclude = [] ): bool { foreach ($exclude as $e) { if ($file === $e || strpos($base . '/' . $file, $e) === 0) { return false; } } if (! $isFile || empty($fileTypes)) { return true; } if (($pos = strrpos($file, '.')) !== false) { $type = substr($file, $pos + 1); return in_array($type, $fileTypes); } else { return false; } } /** * Получение содержимого файла в виде строки * @param string $path путь к файлу * @return string|bool */ public static function getFileContent(string $path) { return file_get_contents($path); } /** * Запись строки в файл * @param string $path путь к файлу * @param mixed $data данные * @return bool */ public static function putFileContent(string $path, $data): bool { return (file_put_contents($path, $data) !== false); } /** * Достаточно ли прав на запись * @param string $path путь к файлу / директории * @param bool $triggerError сообщить об ошибке * @return bool */ public static function haveWriteAccess(string $path, bool $triggerError = false): bool { if (! is_writable($path) && ! chmod($path, 775)) { if ($triggerError) { trigger_error(sprintf('Unable to write to "%s"', realpath((is_dir($path) ? $path : dirname($path))))); } return false; } return true; } /** * Чистим имя файла от запрещенных символов * @param string $fileName имя файла * @param bool $relative относительный путь (true) * @return string очищенное имя файла */ public static function cleanFilename(string $fileName, bool $relative = false): string { $bad = [ '../', '', '<', '>', "'", '"', '&', '$', '#', '{', '}', '[', ']', '=', ';', '?', "%20", "%22", "%3c", // < "%253c", // < "%3e", // > "%0e", // > "%28", // ( "%29", // ) "%2528", // ( "%26", // & "%24", // $ "%3f", // ? "%3b", // ; "%3d", // = ]; if (! $relative) { $bad[] = './'; $bad[] = '/'; } $bad = bff::filter('utils.files.cleanFilename.blacklist', $bad, $relative, $fileName); return stripslashes(str_replace($bad, '', $fileName)); } /** * Создание директории * @param string $directoryName имя директории * @param int $permissions права (@see chmod) * @param bool $recursive рекурсивное создание директории * @return bool **/ public static function makeDir(string $directoryName, int $permissions = 0775, bool $recursive = false): bool { $parent = dirname($directoryName); if (! $recursive || file_exists($parent)) { if (! static::haveWriteAccess($parent, true)) { return false; } } $umask = umask(0); $res = mkdir($directoryName, $permissions, $recursive); umask($umask); return $res; } /** * Получение расширения файла (без точки) * @param string $path путь к файлу * @param bool $isUrl путь является URL * @return string расширение без точки */ public static function getExtension(string $path, bool $isUrl = false): string { if ($isUrl) { $url = parse_url($path); if (! empty($url['path'])) { $path = $url['path']; } } $res = mb_strtolower(pathinfo($path, PATHINFO_EXTENSION)); return ($res === 'jpeg' ? 'jpg' : $res); } /** * Проверка, является ли файл изображением * @param string $filePath путь к файлу * @param bool $checkExtension проверять расширение файла * @return bool */ public static function isImageFile(string $filePath, bool $checkExtension = false): bool { if ($checkExtension && ! in_array(static::getExtension($filePath), ['gif','jpg','png'])) { return false; } $imageSize = getimagesize($filePath); if (empty($imageSize)) { return false; } if (in_array($imageSize[2], [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG])) { return true; } return false; } /** * Загрузка файла по URL * @param string $url URL файла * @param string|bool $path полный путь для сохранения файла, false - вернуть содержимое * @param array|bool $options * setErrors - фиксировать ошибки * timeout - максимально допустимое время выполнения * @return bool|mixed файл был успешно загружен, false - ошибка загрузки файла, содержимое файла */ public static function downloadFile(string $url, $path, $options = true) { $timeout = bff::filter('utils.files.downloadFile.timeout', 30); $setErrors = false; if (is_bool($options)) { $setErrors = $options; } elseif (is_array($options)) { $setErrors = (isset($options['setErrors']) ? ! empty($options['setErrors']) : true); if (isset($options['timeout'])) { $timeout = $options['timeout']; } } if (empty($url)) { if ($setErrors) { Errors::set(_t('system', 'Incorrect URL')); } return false; } $returnContent = ($path === false); if (! $returnContent) { $dir = $path; if (! is_dir($dir)) { $dir = pathinfo($dir, PATHINFO_DIRNAME); } if (! is_writable($dir)) { if ($setErrors) { Errors::set(_t('system', 'Specify the path to the writable directory')); } return false; } } if (extension_loaded('curl')) { $max_redirects = 5; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FAILONERROR, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_MAXREDIRS, $max_redirects); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); # fix CURLOPT_FOLLOWLOCATION + open_basedir if (ini_get('open_basedir') !== '') { curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); $url2 = $url_original = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); $ch2 = curl_copy_handle($ch); curl_setopt($ch2, CURLOPT_HEADER, 1); curl_setopt($ch2, CURLOPT_NOBODY, 1); curl_setopt($ch2, CURLOPT_FORBID_REUSE, 0); curl_setopt($ch2, CURLOPT_RETURNTRANSFER, 1); do { curl_setopt($ch2, CURLOPT_URL, $url2); $header = curl_exec($ch2); if (curl_errno($ch2)) { $code = 0; } else { $code = curl_getinfo($ch2, CURLINFO_HTTP_CODE); if ($code == 301 || $code == 302) { preg_match('/Location:(.*?)\n/i', $header, $matches); $url2 = trim(array_pop($matches)); // if no scheme is present then the new url is a // relative path and thus needs some extra care if (! preg_match("/^https?:/i", $url2)) { $url2 = $url_original . $url2; } } else { $code = 0; } } } while ($code && --$max_redirects); curl_close($ch2); curl_setopt($ch, CURLOPT_URL, $url2); } else { curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); } if ($returnContent) { curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); } else { $file = fopen($path, 'w+'); curl_setopt($ch, CURLOPT_FILE, $file); } $res = curl_exec($ch); if ($res === false) { bff::log(sprintf(__METHOD__ . ' curl error (code %s): %s', curl_errno($ch), curl_error($ch))); } if (is_array($options) && ! empty($options['returnCode'])) { $options['returnCode'] = curl_getinfo($ch, CURLINFO_HTTP_CODE); } curl_close($ch); if ($returnContent) { return $res; } fclose($file); } elseif (ini_get('allow_url_fopen')) { if ($returnContent) { return file_get_contents($url); } else { $res = file_put_contents($path, fopen($url, 'r')); } } else { $res = false; } if (empty($res)) { if ($setErrors) { Errors::set(_t('system', 'Error uploading file')); } return false; } return true; } /** * Создание временного файла доступного на запись * @param string|bool $dir директория хранения или false (директория по-умолчанию) * @param string|null $prefix дополнительный префикс в названии файла * @return string|bool путь к файлу или false */ public static function tempFile($dir = false, ?string $prefix = null) { if ($dir === false) { $dir = bff()->cachePath(); } elseif (! (is_string($dir) && is_dir($dir))) { $dir = sys_get_temp_dir(); } if (empty($prefix)) { $prefix = strval(mt_rand(100, 999)); } $file = tempnam($dir, $prefix); if ($file !== false) { return $file; } $file = tmpfile(); if ($file !== false) { return stream_get_meta_data($file)['uri']; } error_log(__METHOD__ . ': unable to create temporary file'); return false; } /** * Проверка прав записи в директорию/файл * @param array $files список директорий/файлов для проверки наличия прав записи * @param bool $onlyFordev выполнять проверку только при включенном режиме разработчика * @return void */ public static function writableCheck(array $files, bool $onlyFordev = true) { if ($onlyFordev && ! bff()->adminFordev()) { return; } if (empty($files)) { return; } $basePath = bff()->basePath(); foreach ($files as $file) { if (empty($file)) { continue; } if (! file_exists($file)) { Errors::set(_t('files', 'Check if the directory/file exists "[file]"', [ 'file' => str_replace($basePath, DIRECTORY_SEPARATOR, $file), ])); Errors::autohide(false); } else { if (! is_writable($file)) { if (is_dir($file)) { Errors::set(_t('files', 'Not enough permissions to write to directory "[path]"', [ 'path' => str_replace($basePath, DIRECTORY_SEPARATOR, $file), ])); } else { Errors::set(_t('files', 'Not enough permissions to write to file "[path]"', [ 'path' => str_replace($basePath, DIRECTORY_SEPARATOR, $file), ])); } Errors::autohide(false); } } } } }