* Resolve a (possibly nested) relative folder like "invoices/2025" to a real path * under UPLOAD_DIR. Validates each path segment against REGEX_FOLDER_NAME, enforces * containment, and (optionally) creates the folder. * * @param string $folder Relative folder or "root" * @param bool $create Create the folder if missing * @return array [string|null $realPath, strin
(string $folder, bool $create = false)
| 1107 | * @return array [string|null $realPath, string $relative, string|null $error] |
| 1108 | */ |
| 1109 | private static function resolveFolderPath(string $folder, bool $create = false): array |
| 1110 | { |
| 1111 | $folder = trim($folder) ?: 'root'; |
| 1112 | $relative = 'root'; |
| 1113 | |
| 1114 | $storage = self::storage(); |
| 1115 | $isLocal = $storage->isLocal(); |
| 1116 | $base = $isLocal ? realpath(self::uploadRoot()) : rtrim(self::uploadRoot(), '/\\'); |
| 1117 | if ($base === false || $base === '') { |
| 1118 | return [null, 'root', "Uploads directory not configured correctly."]; |
| 1119 | } |
| 1120 | |
| 1121 | if (strtolower($folder) === 'root') { |
| 1122 | $dir = $base; |
| 1123 | } else { |
| 1124 | $parts = self::splitFolderSegments($folder); |
| 1125 | if ($parts === null || $parts === []) { |
| 1126 | return [null, 'root', "Invalid folder name."]; |
| 1127 | } |
| 1128 | $relative = implode('/', $parts); |
| 1129 | $dir = $base . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $parts); |
| 1130 | } |
| 1131 | |
| 1132 | $dirStat = $isLocal ? null : $storage->stat($dir); |
| 1133 | $dirExists = $isLocal ? is_dir($dir) : ($dirStat !== null && ($dirStat['type'] ?? '') === 'dir'); |
| 1134 | if (!$dirExists) { |
| 1135 | if ($create) { |
| 1136 | if (!$storage->mkdir($dir, 0775, true)) { |
| 1137 | return [null, $relative, "Failed to create folder."]; |
| 1138 | } |
| 1139 | } else { |
| 1140 | return [null, $relative, "Folder does not exist."]; |
| 1141 | } |
| 1142 | } |
| 1143 | |
| 1144 | if ($isLocal) { |
| 1145 | $real = realpath($dir); |
| 1146 | if ($real === false || !self::isPathInsideOrSame($base, $real)) { |
| 1147 | return [null, $relative, "Invalid folder path."]; |
| 1148 | } |
| 1149 | return [$real, $relative, null]; |
| 1150 | } |
| 1151 | |
| 1152 | return [$dir, $relative, null]; |
| 1153 | } |
| 1154 | |
| 1155 | private static function resolveFolderPathForAdapter(StorageAdapterInterface $storage, string $root, string $folder, bool $create = false): array |
| 1156 | { |
no test coverage detected